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 = [(