diff --git a/sentry-core/src/performance.rs b/sentry-core/src/performance.rs index 062899ae..e8713659 100644 --- a/sentry-core/src/performance.rs +++ b/sentry-core/src/performance.rs @@ -73,7 +73,9 @@ pub struct TransactionContext { } impl TransactionContext { - /// Creates a new Transaction Context with the given `name` and `op`. + /// Creates a new Transaction Context with the given `name` and `op`. A random + /// `trace_id` is assigned. Use [`TransactionContext::new_with_trace_id`] to + /// specify a custom trace ID. /// /// See /// for an explanation of a Transaction's `name`, and @@ -84,13 +86,32 @@ impl TransactionContext { /// can be used for distributed tracing. #[must_use = "this must be used with `start_transaction`"] pub fn new(name: &str, op: &str) -> Self { - Self::continue_from_headers(name, op, std::iter::empty()) + Self::new_with_trace_id(name, op, protocol::TraceId::default()) + } + + /// Creates a new Transaction Context with the given `name`, `op`, and `trace_id`. + /// + /// See + /// for an explanation of a Transaction's `name`, and + /// for conventions + /// around an `operation`'s value. + #[must_use = "this must be used with `start_transaction`"] + pub fn new_with_trace_id(name: &str, op: &str, trace_id: protocol::TraceId) -> Self { + Self { + name: name.into(), + op: op.into(), + trace_id, + parent_span_id: None, + sampled: None, + custom: None, + } } /// Creates a new Transaction Context based on the distributed tracing `headers`. /// - /// The `headers` in particular need to include the `sentry-trace` header, - /// which is used to associate the transaction with a distributed trace. + /// The `headers` in particular need to include either the `sentry-trace` or W3C + /// `traceparent` header, which is used to associate the transaction with a distributed + /// trace. If both are present, `sentry-trace` takes precedence. #[must_use = "this must be used with `start_transaction`"] pub fn continue_from_headers<'a, I: IntoIterator>( name: &str, @@ -101,6 +122,11 @@ impl TransactionContext { for (k, v) in headers.into_iter() { if k.eq_ignore_ascii_case("sentry-trace") { trace = parse_sentry_trace(v); + break; + } + + if k.eq_ignore_ascii_case("traceparent") { + trace = parse_w3c_traceparent(v); } } @@ -808,6 +834,23 @@ fn parse_sentry_trace(header: &str) -> Option { Some(SentryTrace(trace_id, parent_span_id, parent_sampled)) } +/// Parses a W3C traceparent header. +/// Reference: +fn parse_w3c_traceparent(header: &str) -> Option { + let header = header.trim(); + let mut parts = header.splitn(4, '-'); + + let _version = parts.next()?; + let trace_id = parts.next()?.parse().ok()?; + let parent_span_id = parts.next()?.parse().ok()?; + let parent_sampled = parts + .next() + .and_then(|sampled| u8::from_str_radix(sampled, 16).ok()) + .map(|flag| flag & 1 != 0); + + Some(SentryTrace(trace_id, parent_span_id, parent_sampled)) +} + impl std::fmt::Display for SentryTrace { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}-{}", self.0, self.1)?; @@ -840,6 +883,26 @@ mod tests { assert_eq!(parsed, Some(trace)); } + #[test] + fn parses_traceparent() { + let trace_id = protocol::TraceId::from_str("4bf92f3577b34da6a3ce929d0e0e4736").unwrap(); + let parent_trace_id = protocol::SpanId::from_str("00f067aa0ba902b7").unwrap(); + + let trace = + parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"); + assert_eq!( + trace, + Some(SentryTrace(trace_id, parent_trace_id, Some(true))) + ); + + let trace = + parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"); + assert_eq!( + trace, + Some(SentryTrace(trace_id, parent_trace_id, Some(false))) + ); + } + #[test] fn disabled_forwards_trace_id() { let headers = [(