1use http::HeaderMap;
8
9pub const TRACEPARENT: &str = "traceparent";
11
12#[must_use]
18pub fn get_traceparent(headers: &HeaderMap) -> Option<&str> {
19 headers.get(TRACEPARENT)?.to_str().ok()
20}
21
22#[must_use]
24pub fn parse_trace_id(traceparent: &str) -> Option<String> {
25 let parts: Vec<&str> = traceparent.split('-').collect();
26 if parts.len() >= 4 && parts[0] == "00" {
27 Some(parts[1].to_owned())
28 } else {
29 None
30 }
31}
32
33#[cfg(feature = "otel")]
38mod imp {
39 use super::{get_traceparent, parse_trace_id};
40 use http::{HeaderMap, HeaderName, HeaderValue};
41 use opentelemetry::{
42 Context, global,
43 propagation::{Extractor, Injector},
44 };
45 use tracing::Span;
46 use tracing_opentelemetry::OpenTelemetrySpanExt;
47
48 struct HeadersExtractor<'a>(&'a HeaderMap);
50
51 impl Extractor for HeadersExtractor<'_> {
52 fn get(&self, key: &str) -> Option<&str> {
53 self.0.get(key).and_then(|v| v.to_str().ok())
54 }
55
56 fn keys(&self) -> Vec<&str> {
57 self.0.keys().map(http::HeaderName::as_str).collect()
58 }
59 }
60
61 struct HeadersInjector<'a>(&'a mut HeaderMap);
63
64 impl Injector for HeadersInjector<'_> {
65 fn set(&mut self, key: &str, value: String) {
66 if let Ok(name) = HeaderName::from_bytes(key.as_bytes())
67 && let Ok(val) = HeaderValue::from_str(&value)
68 {
69 self.0.insert(name, val);
70 }
71 }
72 }
73
74 pub fn inject_current_span(headers: &mut HeaderMap) {
77 let cx = Context::current();
78 global::get_text_map_propagator(|propagator| {
79 propagator.inject_context(&cx, &mut HeadersInjector(headers));
80 });
81 }
82
83 pub fn set_parent_from_headers(span: &Span, headers: &HeaderMap) {
86 let parent_cx = global::get_text_map_propagator(|propagator| {
88 propagator.extract(&HeadersExtractor(headers))
89 });
90
91 let _ = span.set_parent(parent_cx);
93
94 if let Some(traceparent) = get_traceparent(headers)
96 && let Some(trace_id) = parse_trace_id(traceparent)
97 {
98 span.record("trace_id", &trace_id);
99 span.record("parent.trace_id", &trace_id);
100 }
101 }
102}
103
104#[cfg(not(feature = "otel"))]
105mod imp {
106 use super::{get_traceparent, parse_trace_id};
107 use http::HeaderMap;
108 use tracing::Span;
109
110 pub fn inject_current_span(_headers: &mut HeaderMap) {
112 }
114
115 pub fn set_parent_from_headers(span: &Span, headers: &HeaderMap) {
118 if let Some(traceparent) = get_traceparent(headers)
120 && let Some(trace_id) = parse_trace_id(traceparent)
121 {
122 span.record("trace_id", &trace_id);
123 span.record("parent.trace_id", &trace_id);
124 }
125 }
126}
127
128pub use imp::{inject_current_span, set_parent_from_headers};
133
134#[cfg(test)]
135#[cfg_attr(coverage_nightly, coverage(off))]
136mod tests {
137 use super::*;
138 use tracing::info_span;
139
140 #[test]
141 fn test_get_traceparent_none() {
142 let headers = HeaderMap::new();
143 assert!(get_traceparent(&headers).is_none());
144 }
145
146 #[test]
147 fn test_get_traceparent_ok() {
148 let mut headers = HeaderMap::new();
149 headers.insert(
150 TRACEPARENT,
151 "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
152 .parse()
153 .unwrap(),
154 );
155
156 let tp = get_traceparent(&headers);
157 assert!(tp.is_some());
158 assert_eq!(
159 tp.unwrap(),
160 "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
161 );
162 }
163
164 #[test]
165 fn test_parse_trace_id_ok() {
166 let traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
167 let trace_id = parse_trace_id(traceparent);
168 assert_eq!(
169 trace_id,
170 Some("4bf92f3577b34da6a3ce929d0e0e4736".to_owned())
171 );
172 }
173
174 #[test]
175 fn test_parse_trace_id_invalid() {
176 assert!(parse_trace_id("invalid").is_none());
177 assert!(parse_trace_id("").is_none());
178 }
179
180 #[test]
181 #[cfg(not(feature = "otel"))]
182 fn test_inject_current_span_noop() {
183 let mut headers = HeaderMap::new();
184 inject_current_span(&mut headers);
185 assert!(headers.is_empty());
187 }
188
189 #[test]
190 #[cfg(feature = "otel")]
191 fn test_inject_current_span_no_panic() {
192 use opentelemetry::global;
193 use opentelemetry_sdk::propagation::TraceContextPropagator;
194
195 global::set_text_map_propagator(TraceContextPropagator::new());
196
197 let mut headers = http::HeaderMap::new();
198 let _span = tracing::info_span!("test").entered();
199 inject_current_span(&mut headers);
201 }
202
203 #[test]
204 fn test_set_parent_from_headers_no_panic() {
205 let mut headers = HeaderMap::new();
206 headers.insert(
207 TRACEPARENT,
208 "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
209 .parse()
210 .unwrap(),
211 );
212
213 let span = info_span!(
214 "test",
215 trace_id = tracing::field::Empty,
216 parent.trace_id = tracing::field::Empty
217 );
218
219 set_parent_from_headers(&span, &headers);
221 }
222}