use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceContext {
pub trace_id: String,
pub span_id: String,
pub trace_flags: Option<u8>,
pub trace_state: Option<String>,
}
impl TraceContext {
pub fn new() -> Self {
Self {
trace_id: Self::generate_trace_id(),
span_id: Self::generate_span_id(),
trace_flags: Some(1), trace_state: None,
}
}
pub fn child_span(&self) -> Self {
Self {
trace_id: self.trace_id.clone(),
span_id: Self::generate_span_id(),
trace_flags: self.trace_flags,
trace_state: self.trace_state.clone(),
}
}
pub fn from_traceparent(header: &str) -> Option<Self> {
let parts: Vec<&str> = header.split('-').collect();
if parts.len() >= 3 {
Some(Self {
trace_id: parts[1].to_string(),
span_id: parts[2].to_string(),
trace_flags: parts.get(3).and_then(|f| u8::from_str_radix(f, 16).ok()),
trace_state: None,
})
} else {
None
}
}
pub fn to_traceparent(&self) -> String {
format!(
"00-{}-{}-{:02x}",
self.trace_id,
self.span_id,
self.trace_flags.unwrap_or(0)
)
}
fn generate_trace_id() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
format!("{:032x}", now)
}
fn generate_span_id() -> String {
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
format!("{:016x}", now ^ (counter << 48))
}
}
impl Default for TraceContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trace_context_new() {
let ctx = TraceContext::new();
assert_eq!(ctx.trace_id.len(), 32);
assert_eq!(ctx.span_id.len(), 16);
}
#[test]
fn test_child_span() {
let parent = TraceContext::new();
let child = parent.child_span();
assert_eq!(child.trace_id, parent.trace_id);
assert_ne!(child.span_id, parent.span_id);
}
#[test]
fn test_traceparent_roundtrip() {
let ctx = TraceContext::new();
let header = ctx.to_traceparent();
let parsed = TraceContext::from_traceparent(&header).unwrap();
assert_eq!(parsed.trace_id, ctx.trace_id);
assert_eq!(parsed.span_id, ctx.span_id);
}
}