use super::TraceState;
use crate::{extension::HeaderMapExt, Uuid};
use http::header::HeaderMap;
use tracing::Span;
const FLAG_SAMPLED: u8 = 1;
const FLAG_RANDOM_TRACE_ID: u8 = 2;
#[derive(Debug, Clone)]
pub struct TraceContext {
span_id: u64,
version: u8,
trace_id: u128,
parent_id: Option<u64>,
trace_flags: u8,
trace_state: TraceState,
}
impl TraceContext {
pub fn new() -> Self {
let span_id = Span::current()
.id()
.map(|id| id.into_u64())
.unwrap_or_else(rand::random);
Self {
span_id,
version: 0,
trace_id: Uuid::now_v7().as_u128(),
parent_id: None,
trace_flags: FLAG_SAMPLED | FLAG_RANDOM_TRACE_ID,
trace_state: TraceState::new(),
}
}
pub fn with_trace_id(trace_id: Uuid) -> Self {
let span_id = Span::current()
.id()
.map(|id| id.into_u64())
.unwrap_or_else(rand::random);
Self {
span_id,
version: 0,
trace_id: trace_id.as_u128(),
parent_id: None,
trace_flags: FLAG_SAMPLED | FLAG_RANDOM_TRACE_ID,
trace_state: TraceState::new(),
}
}
pub fn child(&self) -> Self {
let span_id = Span::current()
.id()
.map(|id| id.into_u64())
.unwrap_or_else(rand::random);
Self {
span_id,
version: self.version,
trace_id: self.trace_id,
parent_id: Some(self.span_id),
trace_flags: self.trace_flags,
trace_state: self.trace_state.clone(),
}
}
pub fn from_traceparent(traceparent: &str) -> Option<Self> {
let span_id = Span::current()
.id()
.map(|id| id.into_u64())
.unwrap_or_else(rand::random);
let parts = traceparent.split('-').collect::<Vec<_>>();
(parts.len() == 4).then_some(Self {
span_id,
version: u8::from_str_radix(parts[0], 16).ok()?,
trace_id: u128::from_str_radix(parts[1], 16).ok()?,
parent_id: Some(u64::from_str_radix(parts[2], 16).ok()?),
trace_flags: u8::from_str_radix(parts[3], 16).ok()?,
trace_state: TraceState::new(),
})
}
pub fn from_headers(headers: &HeaderMap) -> Option<Self> {
let traceparent = headers.get_str("traceparent")?;
let mut trace_context = Self::from_traceparent(traceparent)?;
if let Some(tracestate) = headers.get_str("tracestate") {
trace_context.trace_state = TraceState::from_tracestate(tracestate);
}
Some(trace_context)
}
#[inline]
pub fn span_id(&self) -> u64 {
self.span_id
}
#[inline]
pub fn version(&self) -> u8 {
self.version
}
#[inline]
pub fn trace_id(&self) -> u128 {
self.trace_id
}
#[inline]
pub fn parent_id(&self) -> Option<u64> {
self.parent_id
}
#[inline]
pub fn trace_flags(&self) -> u8 {
self.trace_flags
}
#[inline]
pub fn sampled(&self) -> bool {
(self.trace_flags & FLAG_SAMPLED) == FLAG_SAMPLED
}
#[inline]
pub fn random_trace_id(&self) -> bool {
(self.trace_flags & FLAG_RANDOM_TRACE_ID) == FLAG_RANDOM_TRACE_ID
}
#[inline]
pub fn set_sampled(&mut self, sampled: bool) {
self.trace_flags ^= ((sampled as u8) ^ self.trace_flags) & FLAG_SAMPLED;
}
#[inline]
pub fn set_random_trace_id(&mut self, random: bool) {
self.trace_flags ^= ((random as u8) ^ self.trace_flags) & FLAG_RANDOM_TRACE_ID;
}
#[inline]
pub fn trace_state_mut(&mut self) -> &mut TraceState {
&mut self.trace_state
}
#[inline]
pub fn traceparent(&self) -> String {
format!(
"{:02x}-{:032x}-{:016x}-{:02x}",
self.version, self.trace_id, self.span_id, self.trace_flags
)
}
#[inline]
pub fn tracestate(&self) -> String {
self.trace_state.to_string()
}
}
impl Default for TraceContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::TraceContext;
#[test]
fn it_constructs_trace_context() {
let traceparent = "00-76580b47d0bf430ebbb0d1d966b10f2b-0000004000000001-03";
let trace_context = TraceContext::from_traceparent(traceparent).unwrap();
assert_eq!(trace_context.version(), 0);
assert_eq!(
trace_context.trace_id(),
u128::from_str_radix("76580b47d0bf430ebbb0d1d966b10f2b", 16).unwrap(),
);
assert_eq!(
trace_context.parent_id(),
u64::from_str_radix("0000004000000001", 16).ok(),
);
assert_eq!(trace_context.trace_flags(), 3);
}
}