1use opentelemetry::{
5 propagation::{text_map_propagator::FieldIter, Extractor, Injector, TextMapPropagator},
6 trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState},
7 Context,
8};
9use std::str::FromStr;
10use std::sync::OnceLock;
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: OnceLock<[String; 2]> = OnceLock::new();
19
20fn trace_context_header_fields() -> &'static [String; 2] {
21 TRACE_CONTEXT_HEADER_FIELDS
22 .get_or_init(|| [TRACEPARENT_HEADER.to_owned(), TRACESTATE_HEADER.to_owned()])
23}
24
25#[derive(Clone, Debug, Default)]
52pub struct TraceContextPropagator {
53 _private: (),
54}
55
56impl TraceContextPropagator {
57 pub fn new() -> Self {
59 TraceContextPropagator { _private: () }
60 }
61
62 fn extract_span_context(&self, extractor: &dyn Extractor) -> Result<SpanContext, ()> {
64 let header_value = extractor.get(TRACEPARENT_HEADER).unwrap_or("").trim();
65 let parts = header_value.split_terminator('-').collect::<Vec<&str>>();
66 if parts.len() < 4 {
68 return Err(());
69 }
70
71 let version = u8::from_str_radix(parts[0], 16).map_err(|_| ())?;
73 if version > MAX_VERSION || version == 0 && parts.len() != 4 {
74 return Err(());
75 }
76
77 if parts[1].chars().any(|c| c.is_ascii_uppercase()) {
79 return Err(());
80 }
81
82 let trace_id = TraceId::from_hex(parts[1]).map_err(|_| ())?;
84
85 if parts[2].chars().any(|c| c.is_ascii_uppercase()) {
87 return Err(());
88 }
89
90 let span_id = SpanId::from_hex(parts[2]).map_err(|_| ())?;
92
93 if parts[3].chars().any(|c| c.is_ascii_uppercase()) {
95 return Err(());
96 }
97
98 let opts = u8::from_str_radix(parts[3], 16).map_err(|_| ())?;
100
101 let trace_flags = TraceFlags::new(opts) & TraceFlags::SAMPLED;
105
106 let trace_state = match extractor.get(TRACESTATE_HEADER) {
107 Some(trace_state_str) => {
108 TraceState::from_str(trace_state_str).unwrap_or_else(|_| TraceState::default())
109 }
110 None => TraceState::default(),
111 };
112
113 let span_context = SpanContext::new(trace_id, span_id, trace_flags, true, trace_state);
115
116 if !span_context.is_valid() {
118 return Err(());
119 }
120
121 Ok(span_context)
122 }
123}
124
125impl TextMapPropagator for TraceContextPropagator {
126 fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
129 let span = cx.span();
130 let span_context = span.span_context();
131 if span_context.is_valid() {
132 let header_value = format!(
133 "{:02x}-{}-{}-{:02x}",
134 SUPPORTED_VERSION,
135 span_context.trace_id(),
136 span_context.span_id(),
137 span_context.trace_flags() & TraceFlags::SAMPLED
138 );
139 injector.set(TRACEPARENT_HEADER, header_value);
140 injector.set(TRACESTATE_HEADER, span_context.trace_state().header());
141 }
142 }
143
144 fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
149 self.extract_span_context(extractor)
150 .map(|sc| cx.with_remote_span_context(sc))
151 .unwrap_or_else(|_| cx.clone())
152 }
153
154 fn fields(&self) -> FieldIter<'_> {
155 FieldIter::new(trace_context_header_fields())
156 }
157}
158
159#[cfg(all(test, feature = "testing", feature = "trace"))]
160mod tests {
161 use super::*;
162 use crate::testing::trace::TestSpan;
163 use std::collections::HashMap;
164
165 #[rustfmt::skip]
166 fn extract_data() -> Vec<(&'static str, &'static str, SpanContext)> {
167 vec![
168 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::default(), true, TraceState::from_str("foo=bar").unwrap())),
169 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
170 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-03", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
171 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
172 ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
173 ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
174 ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-08", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::default(), true, TraceState::from_str("foo=bar").unwrap())),
175 ("02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-XYZxsf09", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
176 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
177 ("01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
178 ]
179 }
180
181 #[rustfmt::skip]
182 fn extract_data_invalid() -> Vec<(&'static str, &'static str)> {
183 vec![
184 ("0000-00000000000000000000000000000000-0000000000000000-01", "wrong version length"),
185 ("00-ab00000000000000000000000000000000-cd00000000000000-01", "wrong trace ID length"),
186 ("00-ab000000000000000000000000000000-cd0000000000000000-01", "wrong span ID length"),
187 ("00-ab000000000000000000000000000000-cd00000000000000-0100", "wrong trace flag length"),
188 ("qw-00000000000000000000000000000000-0000000000000000-01", "bogus version"),
189 ("00-qw000000000000000000000000000000-cd00000000000000-01", "bogus trace ID"),
190 ("00-ab000000000000000000000000000000-qw00000000000000-01", "bogus span ID"),
191 ("00-ab000000000000000000000000000000-cd00000000000000-qw", "bogus trace flag"),
192 ("A0-00000000000000000000000000000000-0000000000000000-01", "upper case version"),
193 ("00-AB000000000000000000000000000000-cd00000000000000-01", "upper case trace ID"),
194 ("00-ab000000000000000000000000000000-CD00000000000000-01", "upper case span ID"),
195 ("00-ab000000000000000000000000000000-cd00000000000000-A1", "upper case trace flag"),
196 ("00-00000000000000000000000000000000-0000000000000000-01", "zero trace ID and span ID"),
197 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7", "missing options"),
198 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-", "empty options"),
199 ]
200 }
201
202 #[rustfmt::skip]
203 fn inject_data() -> Vec<(&'static str, &'static str, SpanContext)> {
204 vec![
205 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::SAMPLED, true, TraceState::from_str("foo=bar").unwrap())),
206 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::default(), true, TraceState::from_str("foo=bar").unwrap())),
207 ("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "foo=bar", SpanContext::new(TraceId::from(0x4bf9_2f35_77b3_4da6_a3ce_929d_0e0e_4736), SpanId::from(0x00f0_67aa_0ba9_02b7), TraceFlags::new(0xff), true, TraceState::from_str("foo=bar").unwrap())),
208 ("", "", SpanContext::empty_context()),
209 ]
210 }
211
212 #[test]
213 fn extract_w3c() {
214 let propagator = TraceContextPropagator::new();
215
216 for (trace_parent, trace_state, expected_context) in extract_data() {
217 let mut extractor = HashMap::new();
218 extractor.insert(TRACEPARENT_HEADER.to_string(), trace_parent.to_string());
219 extractor.insert(TRACESTATE_HEADER.to_string(), trace_state.to_string());
220
221 assert_eq!(
222 propagator.extract(&extractor).span().span_context(),
223 &expected_context
224 )
225 }
226 }
227
228 #[test]
229 fn extract_w3c_tracestate() {
230 let propagator = TraceContextPropagator::new();
231 let state = "foo=bar".to_string();
232 let parent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00".to_string();
233
234 let mut extractor = HashMap::new();
235 extractor.insert(TRACEPARENT_HEADER.to_string(), parent);
236 extractor.insert(TRACESTATE_HEADER.to_string(), state.clone());
237
238 assert_eq!(
239 propagator
240 .extract(&extractor)
241 .span()
242 .span_context()
243 .trace_state()
244 .header(),
245 state
246 )
247 }
248
249 #[test]
250 fn extract_w3c_reject_invalid() {
251 let propagator = TraceContextPropagator::new();
252
253 for (invalid_header, reason) in extract_data_invalid() {
254 let mut extractor = HashMap::new();
255 extractor.insert(TRACEPARENT_HEADER.to_string(), invalid_header.to_string());
256
257 assert_eq!(
258 propagator.extract(&extractor).span().span_context(),
259 &SpanContext::empty_context(),
260 "{reason}"
261 )
262 }
263 }
264
265 #[test]
266 fn inject_w3c() {
267 let propagator = TraceContextPropagator::new();
268
269 for (expected_trace_parent, expected_trace_state, context) in inject_data() {
270 let mut injector = HashMap::new();
271 propagator.inject_context(
272 &Context::current_with_span(TestSpan(context)),
273 &mut injector,
274 );
275
276 assert_eq!(
277 Extractor::get(&injector, TRACEPARENT_HEADER).unwrap_or(""),
278 expected_trace_parent
279 );
280
281 assert_eq!(
282 Extractor::get(&injector, TRACESTATE_HEADER).unwrap_or(""),
283 expected_trace_state
284 );
285 }
286 }
287
288 #[test]
289 fn inject_w3c_tracestate() {
290 let propagator = TraceContextPropagator::new();
291 let state = "foo=bar";
292
293 let mut injector: HashMap<String, String> = HashMap::new();
294 injector.set(TRACESTATE_HEADER, state.to_string());
295
296 Context::map_current(|cx| propagator.inject_context(cx, &mut injector));
297
298 assert_eq!(Extractor::get(&injector, TRACESTATE_HEADER), Some(state))
299 }
300}