opentelemetry_sdk/propagation/
trace_context.rs

1//! # W3C Trace Context Propagator
2//!
3
4use once_cell::sync::Lazy;
5use opentelemetry::{
6    propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
7    trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState},
8    Context,
9};
10use std::str::FromStr;
11
12const SUPPORTED_VERSION: u8 = 0;
13const MAX_VERSION: u8 = 254;
14const TRACEPARENT_HEADER: &str = "traceparent";
15const TRACESTATE_HEADER: &str = "tracestate";
16
17static TRACE_CONTEXT_HEADER_FIELDS: Lazy<[String; 2]> =
18    Lazy::new(|| [TRACEPARENT_HEADER.to_owned(), TRACESTATE_HEADER.to_owned()]);
19
20/// Propagates `SpanContext`s in [W3C TraceContext] format under `traceparent` and `tracestate` header.
21///
22/// The `traceparent` header represents the incoming request in a
23/// tracing system in a common format, understood by all vendors.
24/// Here’s an example of a `traceparent` header.
25///
26/// `traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01`
27///
28/// The `traceparent` HTTP header field identifies the incoming request in a
29/// tracing system. It has four fields:
30///
31///    - version
32///    - trace-id
33///    - parent-id
34///    - trace-flags
35///
36/// The `tracestate` header provides additional vendor-specific trace
37/// identification information across different distributed tracing systems.
38/// Here's an example of a `tracestate` header
39///
40/// `tracestate: vendorname1=opaqueValue1,vendorname2=opaqueValue2`
41///
42/// See the [w3c trace-context docs] for more details.
43///
44/// [w3c trace-context docs]: https://w3c.github.io/trace-context/
45/// [W3C TraceContext]: https://www.w3.org/TR/trace-context/
46#[derive(Clone, Debug, Default)]
47pub struct TraceContextPropagator {
48    _private: (),
49}
50
51impl TraceContextPropagator {
52    /// Create a new `TraceContextPropagator`.
53    pub fn new() -> Self {
54        TraceContextPropagator { _private: () }
55    }
56
57    /// Extract span context from w3c trace-context header.
58    fn extract_span_context(&self, extractor: &dyn Extractor) -> Result<SpanContext, ()> {
59        let header_value = extractor.get(TRACEPARENT_HEADER).unwrap_or("").trim();
60        let parts = header_value.split_terminator('-').collect::<Vec<&str>>();
61        // Ensure parts are not out of range.
62        if parts.len() < 4 {
63            return Err(());
64        }
65
66        // Ensure version is within range, for version 0 there must be 4 parts.
67        let version = u8::from_str_radix(parts[0], 16).map_err(|_| ())?;
68        if version > MAX_VERSION || version == 0 && parts.len() != 4 {
69            return Err(());
70        }
71
72        // Ensure trace id is lowercase
73        if parts[1].chars().any(|c| c.is_ascii_uppercase()) {
74            return Err(());
75        }
76
77        // Parse trace id section
78        let trace_id = TraceId::from_hex(parts[1]).map_err(|_| ())?;
79
80        // Ensure span id is lowercase
81        if parts[2].chars().any(|c| c.is_ascii_uppercase()) {
82            return Err(());
83        }
84
85        // Parse span id section
86        let span_id = SpanId::from_hex(parts[2]).map_err(|_| ())?;
87
88        // Parse trace flags section
89        let opts = u8::from_str_radix(parts[3], 16).map_err(|_| ())?;
90
91        // Ensure opts are valid for version 0
92        if version == 0 && opts > 2 {
93            return Err(());
94        }
95
96        // Build trace flags clearing all flags other than the trace-context
97        // supported sampling bit.
98        let trace_flags = TraceFlags::new(opts) & TraceFlags::SAMPLED;
99
100        let trace_state = match extractor.get(TRACESTATE_HEADER) {
101            Some(trace_state_str) => {
102                TraceState::from_str(trace_state_str).unwrap_or_else(|_| TraceState::default())
103            }
104            None => TraceState::default(),
105        };
106
107        // create context
108        let span_context = SpanContext::new(trace_id, span_id, trace_flags, true, trace_state);
109
110        // Ensure span is valid
111        if !span_context.is_valid() {
112            return Err(());
113        }
114
115        Ok(span_context)
116    }
117}
118
119impl TextMapPropagator for TraceContextPropagator {
120    /// Properly encodes the values of the `SpanContext` and injects them
121    /// into the `Injector`.
122    fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
123        let span = cx.span();
124        let span_context = span.span_context();
125        if span_context.is_valid() {
126            let header_value = format!(
127                "{:02x}-{}-{}-{:02x}",
128                SUPPORTED_VERSION,
129                span_context.trace_id(),
130                span_context.span_id(),
131                span_context.trace_flags() & TraceFlags::SAMPLED
132            );
133            injector.set(TRACEPARENT_HEADER, header_value);
134            injector.set(TRACESTATE_HEADER, span_context.trace_state().header());
135        }
136    }
137
138    /// Retrieves encoded `SpanContext`s using the `Extractor`. It decodes
139    /// the `SpanContext` and returns it. If no `SpanContext` was retrieved
140    /// OR if the retrieved SpanContext is invalid then an empty `SpanContext`
141    /// is returned.
142    fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
143        self.extract_span_context(extractor)
144            .map(|sc| cx.with_remote_span_context(sc))
145            .unwrap_or_else(|_| cx.clone())
146    }
147
148    fn fields(&self) -> FieldIter<'_> {
149        FieldIter::new(TRACE_CONTEXT_HEADER_FIELDS.as_ref())
150    }
151}
152
153#[cfg(all(test, feature = "testing", feature = "trace"))]
154mod tests {
155    use super::*;
156    use crate::testing::trace::TestSpan;
157    use std::collections::HashMap;
158
159    #[rustfmt::skip]
160    fn extract_data() -> Vec<(&'static str, &'static str, SpanContext)> {
161        vec![
162            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::default(), true, TraceState::from_str("foo=bar").unwrap())),
163            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
164            ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
165            ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
166            ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-08", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::default(), true, TraceState::from_str("foo=bar").unwrap())),
167            ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-XYZxsf09", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
168            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
169            ("01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
170        ]
171    }
172
173    #[rustfmt::skip]
174    fn extract_data_invalid() -> Vec<(&'static str, &'static str)> {
175        vec![
176            ("0000-00000000000000000000000000000000-0000000000000000-01", "wrong version length"),
177            ("00-ab00000000000000000000000000000000-cd00000000000000-01", "wrong trace ID length"),
178            ("00-ab000000000000000000000000000000-cd0000000000000000-01", "wrong span ID length"),
179            ("00-ab000000000000000000000000000000-cd00000000000000-0100", "wrong trace flag length"),
180            ("qw-00000000000000000000000000000000-0000000000000000-01",   "bogus version"),
181            ("00-qw000000000000000000000000000000-cd00000000000000-01",   "bogus trace ID"),
182            ("00-ab000000000000000000000000000000-qw00000000000000-01",   "bogus span ID"),
183            ("00-ab000000000000000000000000000000-cd00000000000000-qw",   "bogus trace flag"),
184            ("A0-00000000000000000000000000000000-0000000000000000-01",   "upper case version"),
185            ("00-AB000000000000000000000000000000-cd00000000000000-01",   "upper case trace ID"),
186            ("00-ab000000000000000000000000000000-CD00000000000000-01",   "upper case span ID"),
187            ("00-ab000000000000000000000000000000-cd00000000000000-A1",   "upper case trace flag"),
188            ("00-00000000000000000000000000000000-0000000000000000-01",   "zero trace ID and span ID"),
189            ("00-ab000000000000000000000000000000-cd00000000000000-09",   "trace-flag unused bits set"),
190            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7",      "missing options"),
191            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-",     "empty options"),
192        ]
193    }
194
195    #[rustfmt::skip]
196    fn inject_data() -> Vec<(&'static str, &'static str, SpanContext)> {
197        vec![
198            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
199            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::default(), true, TraceState::from_str("foo=bar").unwrap())),
200            ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from_u128(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from_u64(0x00f0_67aa_0ba9_02b7), TraceFlags::new(0xff), true, TraceState::from_str("foo=bar").unwrap())),
201            ("", "", SpanContext::empty_context()),
202        ]
203    }
204
205    #[test]
206    fn extract_w3c() {
207        let propagator = TraceContextPropagator::new();
208
209        for (trace_parent, trace_state, expected_context) in extract_data() {
210            let mut extractor = HashMap::new();
211            extractor.insert(TRACEPARENT_HEADER.to_string(), trace_parent.to_string());
212            extractor.insert(TRACESTATE_HEADER.to_string(), trace_state.to_string());
213
214            assert_eq!(
215                propagator.extract(&extractor).span().span_context(),
216                &expected_context
217            )
218        }
219    }
220
221    #[test]
222    fn extract_w3c_tracestate() {
223        let propagator = TraceContextPropagator::new();
224        let state = "foo=bar".to_string();
225        let parent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00".to_string();
226
227        let mut extractor = HashMap::new();
228        extractor.insert(TRACEPARENT_HEADER.to_string(), parent);
229        extractor.insert(TRACESTATE_HEADER.to_string(), state.clone());
230
231        assert_eq!(
232            propagator
233                .extract(&extractor)
234                .span()
235                .span_context()
236                .trace_state()
237                .header(),
238            state
239        )
240    }
241
242    #[test]
243    fn extract_w3c_reject_invalid() {
244        let propagator = TraceContextPropagator::new();
245
246        for (invalid_header, reason) in extract_data_invalid() {
247            let mut extractor = HashMap::new();
248            extractor.insert(TRACEPARENT_HEADER.to_string(), invalid_header.to_string());
249
250            assert_eq!(
251                propagator.extract(&extractor).span().span_context(),
252                &SpanContext::empty_context(),
253                "{}",
254                reason
255            )
256        }
257    }
258
259    #[test]
260    fn inject_w3c() {
261        let propagator = TraceContextPropagator::new();
262
263        for (expected_trace_parent, expected_trace_state, context) in inject_data() {
264            let mut injector = HashMap::new();
265            propagator.inject_context(
266                &Context::current_with_span(TestSpan(context)),
267                &mut injector,
268            );
269
270            assert_eq!(
271                Extractor::get(&injector, TRACEPARENT_HEADER).unwrap_or(""),
272                expected_trace_parent
273            );
274
275            assert_eq!(
276                Extractor::get(&injector, TRACESTATE_HEADER).unwrap_or(""),
277                expected_trace_state
278            );
279        }
280    }
281
282    #[test]
283    fn inject_w3c_tracestate() {
284        let propagator = TraceContextPropagator::new();
285        let state = "foo=bar";
286
287        let mut injector: HashMap<String, String> = HashMap::new();
288        injector.set(TRACESTATE_HEADER, state.to_string());
289
290        Context::map_current(|cx| propagator.inject_context(cx, &mut injector));
291
292        assert_eq!(Extractor::get(&injector, TRACESTATE_HEADER), Some(state))
293    }
294}