use crate::span::Span;
use tracing_core::{Dispatch, span::Id};
#[derive(Copy, Clone, Default)]
pub struct DatadogContext {
pub trace_id: u128,
pub parent_id: u64,
}
impl DatadogContext {
pub(crate) fn is_empty(&self) -> bool {
self.trace_id == 0 || self.parent_id == 0
}
}
pub trait TraceContextExt {
fn inject_trace_context<S>(&mut self, context: DatadogContext)
where
S: Strategy<Self>,
{
S::inject(self, context)
}
fn extract_trace_context<S>(&self) -> DatadogContext
where
S: Strategy<Self>,
{
S::extract(self)
}
}
pub trait Strategy<T: ?Sized> {
fn inject(container: &mut T, context: DatadogContext);
fn extract(container: &T) -> DatadogContext;
}
#[derive(Debug)]
pub(crate) struct WithContext(
#[allow(clippy::type_complexity)] pub(crate) fn(&Dispatch, &Id, f: &mut dyn FnMut(&mut Span)),
);
impl WithContext {
pub(crate) fn with_context(
&self,
dispatch: &Dispatch,
id: &Id,
mut f: &mut dyn FnMut(&mut Span),
) {
self.0(dispatch, id, &mut f);
}
}
pub trait TracingContextExt {
fn set_context(&self, context: DatadogContext);
fn get_context(&self) -> DatadogContext;
}
impl TracingContextExt for tracing::Span {
fn set_context(&self, context: DatadogContext) {
if context.is_empty() {
return;
}
self.with_subscriber(move |(id, subscriber)| {
let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
return;
};
get_context.with_context(subscriber, id, &mut |dd_span| {
dd_span.trace_id = context.trace_id;
dd_span.parent_id = context.parent_id;
})
});
}
fn get_context(&self) -> DatadogContext {
let mut ctx = None;
self.with_subscriber(|(id, subscriber)| {
let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
return;
};
get_context.with_context(subscriber, id, &mut |dd_span| {
ctx = Some(DatadogContext {
trace_id: dd_span.trace_id,
parent_id: dd_span.span_id,
})
});
});
ctx.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DatadogTraceLayer;
use rand::random_range;
use tracing::info_span;
use tracing_subscriber::layer::SubscriberExt;
#[test]
fn span_context_round_trip() {
tracing::subscriber::with_default(
tracing_subscriber::registry().with(
DatadogTraceLayer::builder()
.service("test-service")
.env("test")
.version("test-version")
.agent_address("localhost:8126")
.build()
.unwrap(),
),
|| {
let context = DatadogContext {
trace_id: random_range(1..=u128::MAX),
parent_id: random_range(1..=u64::MAX),
};
let span = info_span!("test");
span.set_context(context);
let result = span.get_context();
assert_eq!(context.trace_id, result.trace_id);
assert_eq!(span.id().unwrap().into_u64(), result.parent_id);
},
);
}
#[test]
fn empty_span_context_does_not_erase_trace_id() {
tracing::subscriber::with_default(
tracing_subscriber::registry().with(
DatadogTraceLayer::builder()
.service("test-service")
.env("test")
.version("test-version")
.agent_address("localhost:8126")
.build()
.unwrap(),
),
|| {
let context = DatadogContext::default();
let span = info_span!("test");
span.set_context(context);
let result = span.get_context();
assert_ne!(result.trace_id, 0);
},
);
}
}