Skip to content

Commit 0e021ba

Browse files
authored
sentry-core: Add traces_sampler client option (getsentry#510)
* sentry-core: add traces_sampler client option
1 parent 8e8f7c3 commit 0e021ba

File tree

14 files changed

+106
-23
lines changed

14 files changed

+106
-23
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
**Breaking Changes**:
66

77
- The minimum supported Rust version was bumped to **1.60.0** due to requirements from dependencies. ([#498](https://github.com/getsentry/sentry-rust/pull/498))
8+
- Added the `traces_sampler` option to `ClientOptions`. This allows the user to customise sampling rates on a per-transaction basis. ([#510](https://github.com/getsentry/sentry-rust/pull/510))
89

910
**Features**:
1011

sentry-contexts/src/utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ mod model_support {
2626
return None;
2727
}
2828

29-
let mut buf = vec![0u8; size as usize];
29+
let mut buf = vec![0u8; size];
3030
let res = libc::sysctlbyname(
3131
c_name.as_ptr() as _,
3232
buf.as_mut_ptr() as *mut c_void,

sentry-core/src/clientoptions.rs

+14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::sync::Arc;
44
use std::time::Duration;
55

66
use crate::constants::USER_AGENT;
7+
use crate::performance::TracesSampler;
78
use crate::protocol::{Breadcrumb, Event};
89
use crate::types::Dsn;
910
use crate::{Integration, IntoDsn, TransportFactory};
@@ -74,6 +75,11 @@ pub struct ClientOptions {
7475
pub sample_rate: f32,
7576
/// The sample rate for tracing transactions. (0.0 - 1.0, defaults to 0.0)
7677
pub traces_sample_rate: f32,
78+
/// If given, called with a SamplingContext for each transaction to determine the sampling rate.
79+
///
80+
/// Return a sample rate between 0.0 and 1.0 for the transaction in question.
81+
/// Takes priority over the `sample_rate`.
82+
pub traces_sampler: Option<Arc<TracesSampler>>,
7783
/// Enables profiling
7884
pub enable_profiling: bool,
7985
/// The sample rate for profiling a transactions. (0.0 - 1.0, defaults to 0.0)
@@ -196,6 +202,13 @@ impl fmt::Debug for ClientOptions {
196202
.field("environment", &self.environment)
197203
.field("sample_rate", &self.sample_rate)
198204
.field("traces_sample_rate", &self.traces_sample_rate)
205+
.field(
206+
"traces_sampler",
207+
&self
208+
.traces_sampler
209+
.as_ref()
210+
.map(|arc| std::ptr::addr_of!(**arc)),
211+
)
199212
.field("enable_profiling", &self.enable_profiling)
200213
.field("profiles_sample_rate", &self.profiles_sample_rate)
201214
.field("max_breadcrumbs", &self.max_breadcrumbs)
@@ -231,6 +244,7 @@ impl Default for ClientOptions {
231244
environment: None,
232245
sample_rate: 1.0,
233246
traces_sample_rate: 0.0,
247+
traces_sampler: None,
234248
enable_profiling: false,
235249
profiles_sample_rate: 0.0,
236250
max_breadcrumbs: 100,

sentry-core/src/hub.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ pub(crate) struct HubImpl {
4242
impl HubImpl {
4343
pub(crate) fn with<F: FnOnce(&Stack) -> R, R>(&self, f: F) -> R {
4444
let guard = self.stack.read().unwrap_or_else(PoisonError::into_inner);
45-
f(&*guard)
45+
f(&guard)
4646
}
4747

4848
fn with_mut<F: FnOnce(&mut Stack) -> R, R>(&self, f: F) -> R {
4949
let mut guard = self.stack.write().unwrap_or_else(PoisonError::into_inner);
50-
f(&mut *guard)
50+
f(&mut guard)
5151
}
5252

5353
fn is_active_and_usage_safe(&self) -> bool {

sentry-core/src/performance.rs

+78-10
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ impl TransactionContext {
156156
}
157157
}
158158

159+
/// A function to be run for each new transaction, to determine the rate at which
160+
/// it should be sampled.
161+
///
162+
/// This function may choose to respect the sampling of the parent transaction (`ctx.sampled`)
163+
/// or ignore it.
164+
pub type TracesSampler = dyn Fn(&TransactionContext) -> f32 + Send + Sync;
165+
159166
// global API types:
160167

161168
/// A wrapper that groups a [`Transaction`] and a [`Span`] together.
@@ -279,6 +286,37 @@ pub(crate) struct TransactionInner {
279286

280287
type TransactionArc = Arc<Mutex<TransactionInner>>;
281288

289+
/// Functional implementation of how a new transation's sample rate is chosen.
290+
///
291+
/// Split out from `Client.is_transaction_sampled` for testing.
292+
#[cfg(feature = "client")]
293+
fn transaction_sample_rate(
294+
traces_sampler: Option<&TracesSampler>,
295+
ctx: &TransactionContext,
296+
traces_sample_rate: f32,
297+
) -> f32 {
298+
match (traces_sampler, traces_sample_rate) {
299+
(Some(traces_sampler), _) => traces_sampler(ctx),
300+
(None, traces_sample_rate) => ctx
301+
.sampled
302+
.map(|sampled| if sampled { 1.0 } else { 0.0 })
303+
.unwrap_or(traces_sample_rate),
304+
}
305+
}
306+
307+
/// Determine whether the new transaction should be sampled.
308+
#[cfg(feature = "client")]
309+
impl Client {
310+
fn is_transaction_sampled(&self, ctx: &TransactionContext) -> bool {
311+
let client_options = self.options();
312+
self.sample_should_send(transaction_sample_rate(
313+
client_options.traces_sampler.as_deref(),
314+
ctx,
315+
client_options.traces_sample_rate,
316+
))
317+
}
318+
}
319+
282320
/// A running Performance Monitoring Transaction.
283321
///
284322
/// The transaction needs to be explicitly finished via [`Transaction::finish`],
@@ -292,18 +330,9 @@ pub struct Transaction {
292330
impl Transaction {
293331
#[cfg(feature = "client")]
294332
fn new(mut client: Option<Arc<Client>>, ctx: TransactionContext) -> Self {
295-
let context = protocol::TraceContext {
296-
trace_id: ctx.trace_id,
297-
parent_span_id: ctx.parent_span_id,
298-
op: Some(ctx.op),
299-
..Default::default()
300-
};
301-
302333
let (sampled, mut transaction) = match client.as_ref() {
303334
Some(client) => (
304-
ctx.sampled.unwrap_or_else(|| {
305-
client.sample_should_send(client.options().traces_sample_rate)
306-
}),
335+
client.is_transaction_sampled(&ctx),
307336
Some(protocol::Transaction {
308337
name: Some(ctx.name),
309338
#[cfg(all(feature = "profiling", target_family = "unix"))]
@@ -314,6 +343,13 @@ impl Transaction {
314343
None => (ctx.sampled.unwrap_or(false), None),
315344
};
316345

346+
let context = protocol::TraceContext {
347+
trace_id: ctx.trace_id,
348+
parent_span_id: ctx.parent_span_id,
349+
op: Some(ctx.op),
350+
..Default::default()
351+
};
352+
317353
// throw away the transaction here, which means there is nothing to send
318354
// on `finish`.
319355
if !sampled {
@@ -679,4 +715,36 @@ mod tests {
679715
assert_eq!(&parsed.0.to_string(), "09e04486820349518ac7b5d2adbf6ba5");
680716
assert_eq!(parsed.2, Some(true));
681717
}
718+
719+
#[cfg(feature = "client")]
720+
#[test]
721+
fn compute_transaction_sample_rate() {
722+
// Global rate used as fallback.
723+
let ctx = TransactionContext::new("noop", "noop");
724+
assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.3);
725+
assert_eq!(transaction_sample_rate(None, &ctx, 0.7), 0.7);
726+
727+
// If only global rate, setting sampled overrides it
728+
let mut ctx = TransactionContext::new("noop", "noop");
729+
ctx.set_sampled(true);
730+
assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 1.0);
731+
ctx.set_sampled(false);
732+
assert_eq!(transaction_sample_rate(None, &ctx, 0.3), 0.0);
733+
734+
// If given, sampler function overrides everything else.
735+
let mut ctx = TransactionContext::new("noop", "noop");
736+
assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
737+
ctx.set_sampled(false);
738+
assert_eq!(transaction_sample_rate(Some(&|_| { 0.7 }), &ctx, 0.3), 0.7);
739+
// But the sampler may choose to inspect parent sampling
740+
let sampler = |ctx: &TransactionContext| match ctx.sampled {
741+
Some(true) => 0.8,
742+
Some(false) => 0.4,
743+
None => 0.6,
744+
};
745+
ctx.set_sampled(true);
746+
assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.8);
747+
ctx.set_sampled(None);
748+
assert_eq!(transaction_sample_rate(Some(&sampler), &ctx, 0.3), 0.6);
749+
}
682750
}

sentry-core/src/profiling.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ fn get_profile_from_report(
241241
timestamp: rep.timing.start_time,
242242
transactions: vec![TransactionMetadata {
243243
id: transaction.event_id,
244-
name: transaction.name.clone().unwrap_or_else(|| "".to_string()),
244+
name: transaction.name.clone().unwrap_or_default(),
245245
trace_id,
246246
relative_start_ns: 0,
247247
relative_end_ns: transaction

sentry-panic/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl Integration for PanicIntegration {
7777
/// Extract the message of a panic.
7878
pub fn message_from_panic_info<'a>(info: &'a PanicInfo<'_>) -> &'a str {
7979
match info.payload().downcast_ref::<&'static str>() {
80-
Some(s) => *s,
80+
Some(s) => s,
8181
None => match info.payload().downcast_ref::<String>() {
8282
Some(s) => &s[..],
8383
None => "Box<Any>",

sentry-types/src/protocol/envelope.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ impl Envelope {
388388
let payload_start = std::cmp::min(header_end + 1, slice.len());
389389
let payload_end = match header.length {
390390
Some(len) => {
391-
let payload_end = payload_start + len as usize;
391+
let payload_end = payload_start + len;
392392
if slice.len() < payload_end {
393393
return Err(EnvelopeError::UnexpectedEof);
394394
}

sentry-types/src/protocol/v7.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1360,7 +1360,7 @@ impl Default for SpanId {
13601360

13611361
impl fmt::Display for SpanId {
13621362
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1363-
write!(fmt, "{}", hex::encode(&self.0))
1363+
write!(fmt, "{}", hex::encode(self.0))
13641364
}
13651365
}
13661366

@@ -1406,7 +1406,7 @@ impl Default for TraceId {
14061406

14071407
impl fmt::Display for TraceId {
14081408
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
1409-
write!(fmt, "{}", hex::encode(&self.0))
1409+
write!(fmt, "{}", hex::encode(self.0))
14101410
}
14111411
}
14121412

sentry/src/transports/curl.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl CurlHttpTransport {
3131
let http_proxy = options.http_proxy.as_ref().map(ToString::to_string);
3232
let https_proxy = options.https_proxy.as_ref().map(ToString::to_string);
3333
let dsn = options.dsn.as_ref().unwrap();
34-
let user_agent = options.user_agent.to_owned();
34+
let user_agent = options.user_agent.clone();
3535
let auth = dsn.to_auth(Some(&user_agent)).to_string();
3636
let url = dsn.envelope_api_url().to_string();
3737
let scheme = dsn.scheme();

sentry/src/transports/reqwest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl ReqwestHttpTransport {
5656
builder.build().unwrap()
5757
});
5858
let dsn = options.dsn.as_ref().unwrap();
59-
let user_agent = options.user_agent.to_owned();
59+
let user_agent = options.user_agent.clone();
6060
let auth = dsn.to_auth(Some(&user_agent)).to_string();
6161
let url = dsn.envelope_api_url().to_string();
6262

sentry/src/transports/surf.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl SurfHttpTransport {
4343
}
4444

4545
let dsn = options.dsn.as_ref().unwrap();
46-
let user_agent = options.user_agent.to_owned();
46+
let user_agent = options.user_agent.clone();
4747
let auth = dsn.to_auth(Some(&user_agent)).to_string();
4848
let url = dsn.envelope_api_url().to_string();
4949

sentry/src/transports/ureq.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl UreqHttpTransport {
112112

113113
builder.build()
114114
});
115-
let user_agent = options.user_agent.to_owned();
115+
let user_agent = options.user_agent.clone();
116116
let auth = dsn.to_auth(Some(&user_agent)).to_string();
117117
let url = dsn.envelope_api_url().to_string();
118118

sentry/tests/test_tracing.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ fn test_tracing() {
106106

107107
#[tracing::instrument(fields(span_field))]
108108
fn function() {
109-
tracing::Span::current().record("span_field", &"some data");
109+
tracing::Span::current().record("span_field", "some data");
110110
}
111111

112112
#[test]

0 commit comments

Comments
 (0)