fast_telemetry/span/
context.rs1use std::cell::Cell;
12
13use super::ids::{SpanId, TraceId};
14
15#[derive(Clone, Copy, Debug)]
17pub(crate) struct SpanContext {
18 pub trace_id: TraceId,
19 pub span_id: SpanId,
20 pub trace_flags: u8,
21}
22
23impl SpanContext {
24 pub const INVALID: Self = Self {
25 trace_id: TraceId::INVALID,
26 span_id: SpanId::INVALID,
27 trace_flags: 0,
28 };
29
30 pub fn from_traceparent(header: &str) -> Option<Self> {
35 let bytes = header.as_bytes();
36
37 if bytes.len() < 55 {
39 return None;
40 }
41
42 if bytes[0] != b'0' || bytes[1] != b'0' {
44 return None;
45 }
46 if bytes[2] != b'-' || bytes[35] != b'-' || bytes[52] != b'-' {
47 return None;
48 }
49
50 let trace_id = TraceId::from_hex(&header[3..35])?;
51 let span_id = SpanId::from_hex(&header[36..52])?;
52
53 let flags_hi = hex_digit(bytes[53])?;
54 let flags_lo = hex_digit(bytes[54])?;
55 let trace_flags = (flags_hi << 4) | flags_lo;
56
57 if trace_id.is_invalid() || span_id.is_invalid() {
59 return None;
60 }
61
62 Some(Self {
63 trace_id,
64 span_id,
65 trace_flags,
66 })
67 }
68
69 pub fn to_traceparent(self) -> String {
71 format!(
72 "00-{}-{}-{:02x}",
73 self.trace_id, self.span_id, self.trace_flags
74 )
75 }
76}
77
78fn hex_digit(c: u8) -> Option<u8> {
79 match c {
80 b'0'..=b'9' => Some(c - b'0'),
81 b'a'..=b'f' => Some(c - b'a' + 10),
82 b'A'..=b'F' => Some(c - b'A' + 10),
83 _ => None,
84 }
85}
86
87thread_local! {
92 static CURRENT: Cell<SpanContext> = const { Cell::new(SpanContext::INVALID) };
93}
94
95pub fn current_trace_id() -> Option<TraceId> {
100 CURRENT.with(|cell| {
101 let ctx = cell.get();
102 if ctx.trace_id.is_invalid() {
103 None
104 } else {
105 Some(ctx.trace_id)
106 }
107 })
108}
109
110pub fn current_span_id() -> Option<SpanId> {
114 CURRENT.with(|cell| {
115 let ctx = cell.get();
116 if ctx.span_id.is_invalid() {
117 None
118 } else {
119 Some(ctx.span_id)
120 }
121 })
122}
123
124pub(crate) struct SpanEnterGuard {
127 prev: SpanContext,
128}
129
130impl SpanEnterGuard {
131 pub fn enter(ctx: SpanContext) -> Self {
134 let prev = CURRENT.with(|cell| cell.replace(ctx));
135 Self { prev }
136 }
137}
138
139impl Drop for SpanEnterGuard {
140 fn drop(&mut self) {
141 CURRENT.with(|cell| cell.set(self.prev));
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn traceparent_roundtrip() {
151 let ctx = SpanContext {
152 trace_id: TraceId::from_hex("4bf92f3577b34da6a3ce929d0e0e4736").expect("valid"),
153 span_id: SpanId::from_hex("00f067aa0ba902b7").expect("valid"),
154 trace_flags: 0x01,
155 };
156 let header = ctx.to_traceparent();
157 assert_eq!(
158 header,
159 "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
160 );
161
162 let parsed = SpanContext::from_traceparent(&header).expect("should parse");
163 assert_eq!(parsed.trace_id, ctx.trace_id);
164 assert_eq!(parsed.span_id, ctx.span_id);
165 assert_eq!(parsed.trace_flags, ctx.trace_flags);
166 }
167
168 #[test]
169 fn traceparent_flags_zero() {
170 let ctx = SpanContext {
171 trace_id: TraceId::from_hex("aaaabbbbccccdddd1111222233334444").expect("valid"),
172 span_id: SpanId::from_hex("1234567890abcdef").expect("valid"),
173 trace_flags: 0x00,
174 };
175 let header = ctx.to_traceparent();
176 assert!(header.ends_with("-00"));
177
178 let parsed = SpanContext::from_traceparent(&header).expect("should parse");
179 assert_eq!(parsed.trace_flags, 0x00);
180 }
181
182 #[test]
183 fn traceparent_rejects_invalid() {
184 assert!(SpanContext::from_traceparent("00-abc-def-01").is_none());
186 assert!(
188 SpanContext::from_traceparent(
189 "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
190 )
191 .is_none()
192 );
193 assert!(
195 SpanContext::from_traceparent(
196 "00x4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
197 )
198 .is_none()
199 );
200 assert!(
202 SpanContext::from_traceparent(
203 "00-00000000000000000000000000000000-00f067aa0ba902b7-01"
204 )
205 .is_none()
206 );
207 assert!(
209 SpanContext::from_traceparent(
210 "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01"
211 )
212 .is_none()
213 );
214 }
215
216 #[test]
217 fn thread_local_enter_exit() {
218 assert!(current_trace_id().is_none());
219 assert!(current_span_id().is_none());
220
221 let ctx = SpanContext {
222 trace_id: TraceId::random(),
223 span_id: SpanId::random(),
224 trace_flags: 1,
225 };
226
227 {
228 let _guard = SpanEnterGuard::enter(ctx);
229 assert_eq!(current_trace_id(), Some(ctx.trace_id));
230 assert_eq!(current_span_id(), Some(ctx.span_id));
231
232 let inner_ctx = SpanContext {
234 trace_id: ctx.trace_id,
235 span_id: SpanId::random(),
236 trace_flags: 1,
237 };
238 {
239 let _inner_guard = SpanEnterGuard::enter(inner_ctx);
240 assert_eq!(current_span_id(), Some(inner_ctx.span_id));
241 }
242 assert_eq!(current_span_id(), Some(ctx.span_id));
244 }
245
246 assert!(current_trace_id().is_none());
248 assert!(current_span_id().is_none());
249 }
250}