use std::collections::HashMap;
use uuid::Uuid;
#[derive(Clone, Debug)]
pub struct TraceContext {
pub trace_id: String,
pub span_id: String,
pub parent_span_id: Option<String>,
pub baggage: HashMap<String, String>,
pub trace_flags: u8,
}
impl TraceContext {
pub fn new() -> Self {
Self {
trace_id: Self::generate_trace_id(),
span_id: Self::generate_span_id(),
parent_span_id: None,
baggage: HashMap::new(),
trace_flags: 0x01, }
}
pub fn child(&self) -> Self {
Self {
trace_id: self.trace_id.clone(),
span_id: Self::generate_span_id(),
parent_span_id: Some(self.span_id.clone()),
baggage: self.baggage.clone(),
trace_flags: self.trace_flags,
}
}
fn generate_trace_id() -> String {
let uuid = Uuid::new_v4();
let hex = uuid.as_bytes();
let mut id = String::with_capacity(32);
for byte in hex.iter().take(16) {
id.push_str(&format!("{:02x}", byte));
}
id
}
fn generate_span_id() -> String {
let uuid = Uuid::new_v4();
let hex = uuid.as_bytes();
let mut id = String::with_capacity(16);
for byte in hex.iter().take(8) {
id.push_str(&format!("{:02x}", byte));
}
id
}
pub fn traceparent_header(&self) -> String {
format!("00-{}-{}-{:02x}", self.trace_id, self.span_id, self.trace_flags)
}
pub fn with_baggage(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.baggage.insert(key.into(), value.into());
self
}
pub fn from_traceparent(header: &str) -> Result<Self, String> {
let parts: Vec<&str> = header.split('-').collect();
if parts.len() != 4 {
return Err("Invalid traceparent format".to_string());
}
Ok(Self {
trace_id: parts[1].to_string(),
span_id: parts[2].to_string(),
parent_span_id: None,
baggage: HashMap::new(),
trace_flags: u8::from_str_radix(parts[3], 16).map_err(|e| e.to_string())?,
})
}
}
impl Default for TraceContext {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static CONTEXT: std::cell::RefCell<Option<TraceContext>> = const { std::cell::RefCell::new(None) };
}
pub fn get_context() -> Option<TraceContext> {
CONTEXT.with(|ctx| ctx.borrow().clone())
}
pub fn set_context(context: TraceContext) {
CONTEXT.with(|ctx| *ctx.borrow_mut() = Some(context));
}
pub fn clear_context() {
CONTEXT.with(|ctx| *ctx.borrow_mut() = None);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trace_context_creation() {
let ctx = TraceContext::new();
assert_eq!(ctx.trace_id.len(), 32);
assert_eq!(ctx.span_id.len(), 16);
}
#[test]
fn test_child_context() {
let parent = TraceContext::new();
let child = parent.child();
assert_eq!(child.trace_id, parent.trace_id);
assert_ne!(child.span_id, parent.span_id);
assert_eq!(child.parent_span_id, Some(parent.span_id.clone()));
}
#[test]
fn test_context_get_set() {
clear_context();
assert!(get_context().is_none());
let ctx = TraceContext::new();
set_context(ctx.clone());
assert!(get_context().is_some());
}
}