use std::cell::Cell;
use super::ids::{SpanId, TraceId};
#[derive(Clone, Copy, Debug)]
pub(crate) struct SpanContext {
pub trace_id: TraceId,
pub span_id: SpanId,
pub trace_flags: u8,
}
impl SpanContext {
pub const INVALID: Self = Self {
trace_id: TraceId::INVALID,
span_id: SpanId::INVALID,
trace_flags: 0,
};
pub fn from_traceparent(header: &str) -> Option<Self> {
let bytes = header.as_bytes();
if bytes.len() < 55 {
return None;
}
if bytes[0] != b'0' || bytes[1] != b'0' {
return None;
}
if bytes[2] != b'-' || bytes[35] != b'-' || bytes[52] != b'-' {
return None;
}
let trace_id = TraceId::from_hex(&header[3..35])?;
let span_id = SpanId::from_hex(&header[36..52])?;
let flags_hi = hex_digit(bytes[53])?;
let flags_lo = hex_digit(bytes[54])?;
let trace_flags = (flags_hi << 4) | flags_lo;
if trace_id.is_invalid() || span_id.is_invalid() {
return None;
}
Some(Self {
trace_id,
span_id,
trace_flags,
})
}
pub fn to_traceparent(self) -> String {
format!(
"00-{}-{}-{:02x}",
self.trace_id, self.span_id, self.trace_flags
)
}
}
fn hex_digit(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
thread_local! {
static CURRENT: Cell<SpanContext> = const { Cell::new(SpanContext::INVALID) };
}
pub fn current_trace_id() -> Option<TraceId> {
CURRENT.with(|cell| {
let ctx = cell.get();
if ctx.trace_id.is_invalid() {
None
} else {
Some(ctx.trace_id)
}
})
}
pub fn current_span_id() -> Option<SpanId> {
CURRENT.with(|cell| {
let ctx = cell.get();
if ctx.span_id.is_invalid() {
None
} else {
Some(ctx.span_id)
}
})
}
pub(crate) struct SpanEnterGuard {
prev: SpanContext,
}
impl SpanEnterGuard {
pub fn enter(ctx: SpanContext) -> Self {
let prev = CURRENT.with(|cell| cell.replace(ctx));
Self { prev }
}
}
impl Drop for SpanEnterGuard {
fn drop(&mut self) {
CURRENT.with(|cell| cell.set(self.prev));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn traceparent_roundtrip() {
let ctx = SpanContext {
trace_id: TraceId::from_hex("4bf92f3577b34da6a3ce929d0e0e4736").expect("valid"),
span_id: SpanId::from_hex("00f067aa0ba902b7").expect("valid"),
trace_flags: 0x01,
};
let header = ctx.to_traceparent();
assert_eq!(
header,
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
);
let parsed = SpanContext::from_traceparent(&header).expect("should parse");
assert_eq!(parsed.trace_id, ctx.trace_id);
assert_eq!(parsed.span_id, ctx.span_id);
assert_eq!(parsed.trace_flags, ctx.trace_flags);
}
#[test]
fn traceparent_flags_zero() {
let ctx = SpanContext {
trace_id: TraceId::from_hex("aaaabbbbccccdddd1111222233334444").expect("valid"),
span_id: SpanId::from_hex("1234567890abcdef").expect("valid"),
trace_flags: 0x00,
};
let header = ctx.to_traceparent();
assert!(header.ends_with("-00"));
let parsed = SpanContext::from_traceparent(&header).expect("should parse");
assert_eq!(parsed.trace_flags, 0x00);
}
#[test]
fn traceparent_rejects_invalid() {
assert!(SpanContext::from_traceparent("00-abc-def-01").is_none());
assert!(
SpanContext::from_traceparent(
"01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
)
.is_none()
);
assert!(
SpanContext::from_traceparent(
"00x4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
)
.is_none()
);
assert!(
SpanContext::from_traceparent(
"00-00000000000000000000000000000000-00f067aa0ba902b7-01"
)
.is_none()
);
assert!(
SpanContext::from_traceparent(
"00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01"
)
.is_none()
);
}
#[test]
fn thread_local_enter_exit() {
assert!(current_trace_id().is_none());
assert!(current_span_id().is_none());
let ctx = SpanContext {
trace_id: TraceId::random(),
span_id: SpanId::random(),
trace_flags: 1,
};
{
let _guard = SpanEnterGuard::enter(ctx);
assert_eq!(current_trace_id(), Some(ctx.trace_id));
assert_eq!(current_span_id(), Some(ctx.span_id));
let inner_ctx = SpanContext {
trace_id: ctx.trace_id,
span_id: SpanId::random(),
trace_flags: 1,
};
{
let _inner_guard = SpanEnterGuard::enter(inner_ctx);
assert_eq!(current_span_id(), Some(inner_ctx.span_id));
}
assert_eq!(current_span_id(), Some(ctx.span_id));
}
assert!(current_trace_id().is_none());
assert!(current_span_id().is_none());
}
}