provide_telemetry/
propagation.rs1use serde_json::Value;
7
8use crate::context::{bind_context, ContextGuard};
9use crate::tracer::set_trace_context;
10
11const MAX_HEADER_LENGTH: usize = 512;
12const MAX_TRACESTATE_PAIRS: usize = 32;
13const MAX_BAGGAGE_LENGTH: usize = 8192;
14
15#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct PropagationContext {
17 pub traceparent: Option<String>,
18 pub tracestate: Option<String>,
19 pub baggage: Option<String>,
20 pub trace_id: Option<String>,
21 pub span_id: Option<String>,
22}
23
24pub struct PropagationGuard {
25 trace_guard: Option<ContextGuard>,
26 context_guard: Option<ContextGuard>,
27}
28
29impl Drop for PropagationGuard {
30 #[cfg_attr(test, mutants::skip)] fn drop(&mut self) {
32 drop(self.trace_guard.take());
33 drop(self.context_guard.take());
34 }
35}
36
37fn parse_traceparent(value: Option<&str>) -> (Option<String>, Option<String>, Option<String>) {
38 let Some(raw) = value else {
39 return (None, None, None);
40 };
41 let parts = raw.split('-').collect::<Vec<_>>();
42 if parts.len() != 4 {
43 return (None, None, None);
44 }
45 let version = parts[0];
46 let trace_id = parts[1];
47 let span_id = parts[2];
48 let flags = parts[3];
49 let valid = version.len() == 2
50 && trace_id.len() == 32
51 && span_id.len() == 16
52 && flags.len() == 2
53 && !version.eq_ignore_ascii_case("ff")
54 && trace_id != "00000000000000000000000000000000"
55 && span_id != "0000000000000000"
56 && [version, trace_id, span_id, flags]
57 .iter()
58 .all(|part| part.chars().all(|ch| ch.is_ascii_hexdigit()));
59
60 if !valid {
61 return (None, None, None);
62 }
63
64 (
65 Some(raw.to_string()),
66 Some(trace_id.to_ascii_lowercase()),
67 Some(span_id.to_ascii_lowercase()),
68 )
69}
70
71pub fn extract_w3c_context(
72 traceparent: Option<&str>,
73 tracestate: Option<&str>,
74 baggage: Option<&str>,
75) -> PropagationContext {
76 let traceparent = traceparent.and_then(|value| {
77 if value.len() > MAX_HEADER_LENGTH {
78 None
79 } else {
80 Some(value)
81 }
82 });
83 let tracestate = tracestate.and_then(|value| {
84 if value.len() > MAX_HEADER_LENGTH || value.split(',').count() > MAX_TRACESTATE_PAIRS {
85 None
86 } else {
87 Some(value.to_string())
88 }
89 });
90 let baggage = baggage.and_then(|value| {
91 if value.len() > MAX_BAGGAGE_LENGTH {
92 None
93 } else {
94 Some(value.to_string())
95 }
96 });
97 let (traceparent, trace_id, span_id) = parse_traceparent(traceparent);
98
99 PropagationContext {
100 traceparent,
101 tracestate,
102 baggage,
103 trace_id,
104 span_id,
105 }
106}
107
108pub fn bind_propagation_context(context: PropagationContext) -> PropagationGuard {
109 let mut fields = Vec::new();
110 if let Some(traceparent) = context.traceparent.clone() {
111 fields.push(("traceparent".to_string(), Value::String(traceparent)));
112 }
113 if let Some(tracestate) = context.tracestate.clone() {
114 fields.push(("tracestate".to_string(), Value::String(tracestate)));
115 }
116 if let Some(baggage) = context.baggage.clone() {
117 fields.push(("baggage".to_string(), Value::String(baggage)));
118 }
119
120 let context_guard = if fields.is_empty() {
121 None
122 } else {
123 Some(bind_context(fields))
124 };
125 let trace_guard = if context.trace_id.is_some() || context.span_id.is_some() {
126 Some(set_trace_context(context.trace_id, context.span_id))
127 } else {
128 None
129 };
130
131 PropagationGuard {
132 trace_guard,
133 context_guard,
134 }
135}