Skip to content

Commit 05fc7de

Browse files
feat(sentry-core): add support for w3c traceparent (#687)
1 parent 180a6b1 commit 05fc7de

File tree

1 file changed

+151
-4
lines changed

1 file changed

+151
-4
lines changed

sentry-core/src/performance.rs

Lines changed: 151 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,15 @@ pub struct TransactionContext {
6969
op: String,
7070
trace_id: protocol::TraceId,
7171
parent_span_id: Option<protocol::SpanId>,
72+
span_id: protocol::SpanId,
7273
sampled: Option<bool>,
7374
custom: Option<CustomTransactionContext>,
7475
}
7576

7677
impl TransactionContext {
77-
/// Creates a new Transaction Context with the given `name` and `op`.
78+
/// Creates a new Transaction Context with the given `name` and `op`. A random
79+
/// `trace_id` is assigned. Use [`TransactionContext::new_with_trace_id`] to
80+
/// specify a custom trace ID.
7881
///
7982
/// See <https://docs.sentry.io/platforms/native/enriching-events/transaction-name/>
8083
/// for an explanation of a Transaction's `name`, and
@@ -85,13 +88,33 @@ impl TransactionContext {
8588
/// can be used for distributed tracing.
8689
#[must_use = "this must be used with `start_transaction`"]
8790
pub fn new(name: &str, op: &str) -> Self {
88-
Self::continue_from_headers(name, op, std::iter::empty())
91+
Self::new_with_trace_id(name, op, protocol::TraceId::default())
92+
}
93+
94+
/// Creates a new Transaction Context with the given `name`, `op`, and `trace_id`.
95+
///
96+
/// See <https://docs.sentry.io/platforms/native/enriching-events/transaction-name/>
97+
/// for an explanation of a Transaction's `name`, and
98+
/// <https://develop.sentry.dev/sdk/performance/span-operations/> for conventions
99+
/// around an `operation`'s value.
100+
#[must_use = "this must be used with `start_transaction`"]
101+
pub fn new_with_trace_id(name: &str, op: &str, trace_id: protocol::TraceId) -> Self {
102+
Self {
103+
name: name.into(),
104+
op: op.into(),
105+
trace_id,
106+
parent_span_id: None,
107+
span_id: Default::default(),
108+
sampled: None,
109+
custom: None,
110+
}
89111
}
90112

91113
/// Creates a new Transaction Context based on the distributed tracing `headers`.
92114
///
93-
/// The `headers` in particular need to include the `sentry-trace` header,
94-
/// which is used to associate the transaction with a distributed trace.
115+
/// The `headers` in particular need to include either the `sentry-trace` or W3C
116+
/// `traceparent` header, which is used to associate the transaction with a distributed
117+
/// trace. If both are present, `sentry-trace` takes precedence.
95118
#[must_use = "this must be used with `start_transaction`"]
96119
pub fn continue_from_headers<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(
97120
name: &str,
@@ -102,6 +125,11 @@ impl TransactionContext {
102125
for (k, v) in headers.into_iter() {
103126
if k.eq_ignore_ascii_case("sentry-trace") {
104127
trace = parse_sentry_trace(v);
128+
break;
129+
}
130+
131+
if k.eq_ignore_ascii_case("traceparent") {
132+
trace = parse_w3c_traceparent(v);
105133
}
106134
}
107135

@@ -115,6 +143,7 @@ impl TransactionContext {
115143
op: op.into(),
116144
trace_id,
117145
parent_span_id,
146+
span_id: Default::default(),
118147
sampled,
119148
custom: None,
120149
}
@@ -152,6 +181,7 @@ impl TransactionContext {
152181
op: op.into(),
153182
trace_id,
154183
parent_span_id: Some(parent_span_id),
184+
span_id: protocol::SpanId::default(),
155185
sampled,
156186
custom: None,
157187
}
@@ -185,6 +215,11 @@ impl TransactionContext {
185215
self.trace_id
186216
}
187217

218+
/// Get the Span ID of this Transaction.
219+
pub fn span_id(&self) -> protocol::SpanId {
220+
self.span_id
221+
}
222+
188223
/// Get the custom context of this Transaction.
189224
pub fn custom(&self) -> Option<&CustomTransactionContext> {
190225
self.custom.as_ref()
@@ -220,6 +255,80 @@ impl TransactionContext {
220255
std::mem::swap(&mut self.custom, &mut Some(custom));
221256
existing_value
222257
}
258+
259+
/// Creates a transaction context builder initialized with the given `name` and `op`.
260+
///
261+
/// See <https://docs.sentry.io/platforms/native/enriching-events/transaction-name/>
262+
/// for an explanation of a Transaction's `name`, and
263+
/// <https://develop.sentry.dev/sdk/performance/span-operations/> for conventions
264+
/// around an `operation`'s value.
265+
#[must_use]
266+
pub fn builder(name: &str, op: &str) -> TransactionContextBuilder {
267+
TransactionContextBuilder {
268+
ctx: TransactionContext::new(name, op),
269+
}
270+
}
271+
}
272+
273+
/// A transaction context builder created by [`TransactionContext::builder`].
274+
pub struct TransactionContextBuilder {
275+
ctx: TransactionContext,
276+
}
277+
278+
impl TransactionContextBuilder {
279+
/// Defines the name of the transaction.
280+
#[must_use]
281+
pub fn with_name(mut self, name: String) -> Self {
282+
self.ctx.name = name;
283+
self
284+
}
285+
286+
/// Defines the operation of the transaction.
287+
#[must_use]
288+
pub fn with_op(mut self, op: String) -> Self {
289+
self.ctx.op = op;
290+
self
291+
}
292+
293+
/// Defines the trace ID.
294+
#[must_use]
295+
pub fn with_trace_id(mut self, trace_id: protocol::TraceId) -> Self {
296+
self.ctx.trace_id = trace_id;
297+
self
298+
}
299+
300+
/// Defines a parent span ID for the created transaction.
301+
#[must_use]
302+
pub fn with_parent_span_id(mut self, parent_span_id: Option<protocol::SpanId>) -> Self {
303+
self.ctx.parent_span_id = parent_span_id;
304+
self
305+
}
306+
307+
/// Defines the span ID to be used when creating the transaction.
308+
#[must_use]
309+
pub fn with_span_id(mut self, span_id: protocol::SpanId) -> Self {
310+
self.ctx.span_id = span_id;
311+
self
312+
}
313+
314+
/// Defines whether the transaction will be sampled.
315+
#[must_use]
316+
pub fn with_sampled(mut self, sampled: Option<bool>) -> Self {
317+
self.ctx.sampled = sampled;
318+
self
319+
}
320+
321+
/// Adds a custom key and value to the transaction context.
322+
#[must_use]
323+
pub fn with_custom(mut self, key: String, value: serde_json::Value) -> Self {
324+
self.ctx.custom_insert(key, value);
325+
self
326+
}
327+
328+
/// Finishes building a transaction.
329+
pub fn finish(self) -> TransactionContext {
330+
self.ctx
331+
}
223332
}
224333

225334
/// A function to be run for each new transaction, to determine the rate at which
@@ -467,6 +576,7 @@ impl Transaction {
467576
let context = protocol::TraceContext {
468577
trace_id: ctx.trace_id,
469578
parent_span_id: ctx.parent_span_id,
579+
span_id: ctx.span_id,
470580
op: Some(ctx.op),
471581
..Default::default()
472582
};
@@ -864,6 +974,23 @@ fn parse_sentry_trace(header: &str) -> Option<SentryTrace> {
864974
Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
865975
}
866976

977+
/// Parses a W3C traceparent header.
978+
/// Reference: <https://w3c.github.io/trace-context/#traceparent-header-field-values>
979+
fn parse_w3c_traceparent(header: &str) -> Option<SentryTrace> {
980+
let header = header.trim();
981+
let mut parts = header.splitn(4, '-');
982+
983+
let _version = parts.next()?;
984+
let trace_id = parts.next()?.parse().ok()?;
985+
let parent_span_id = parts.next()?.parse().ok()?;
986+
let parent_sampled = parts
987+
.next()
988+
.and_then(|sampled| u8::from_str_radix(sampled, 16).ok())
989+
.map(|flag| flag & 1 != 0);
990+
991+
Some(SentryTrace(trace_id, parent_span_id, parent_sampled))
992+
}
993+
867994
impl std::fmt::Display for SentryTrace {
868995
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
869996
write!(f, "{}-{}", self.0, self.1)?;
@@ -896,6 +1023,26 @@ mod tests {
8961023
assert_eq!(parsed, Some(trace));
8971024
}
8981025

1026+
#[test]
1027+
fn parses_traceparent() {
1028+
let trace_id = protocol::TraceId::from_str("4bf92f3577b34da6a3ce929d0e0e4736").unwrap();
1029+
let parent_trace_id = protocol::SpanId::from_str("00f067aa0ba902b7").unwrap();
1030+
1031+
let trace =
1032+
parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
1033+
assert_eq!(
1034+
trace,
1035+
Some(SentryTrace(trace_id, parent_trace_id, Some(true)))
1036+
);
1037+
1038+
let trace =
1039+
parse_w3c_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00");
1040+
assert_eq!(
1041+
trace,
1042+
Some(SentryTrace(trace_id, parent_trace_id, Some(false)))
1043+
);
1044+
}
1045+
8991046
#[test]
9001047
fn disabled_forwards_trace_id() {
9011048
let headers = [(

0 commit comments

Comments
 (0)