1use 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#[derive(Clone, Debug, Default)]
47pub struct TraceContextPropagator {
48 _private: (),
49}
50
51impl TraceContextPropagator {
52 pub fn new() -> Self {
54 TraceContextPropagator { _private: () }
55 }
56
57 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 if parts.len() < 4 {
63 return Err(());
64 }
65
66 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 if parts[1].chars().any(|c| c.is_ascii_uppercase()) {
74 return Err(());
75 }
76
77 let trace_id = TraceId::from_hex(parts[1]).map_err(|_| ())?;
79
80 if parts[2].chars().any(|c| c.is_ascii_uppercase()) {
82 return Err(());
83 }
84
85 let span_id = SpanId::from_hex(parts[2]).map_err(|_| ())?;
87
88 let opts = u8::from_str_radix(parts[3], 16).map_err(|_| ())?;
90
91 if version == 0 && opts > 2 {
93 return Err(());
94 }
95
96 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 let span_context = SpanContext::new(trace_id, span_id, trace_flags, true, trace_state);
109
110 if !span_context.is_valid() {
112 return Err(());
113 }
114
115 Ok(span_context)
116 }
117}
118
119impl TextMapPropagator for TraceContextPropagator {
120 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 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}