#[doc(hidden)]
pub mod internal;
pub(crate) mod init;
#[cfg(any(test, feature = "testing"))]
pub(crate) mod testing;
#[cfg(feature = "metrics")]
pub mod metrics;
mod channel;
mod live;
mod output_jaeger_thrift_udp;
mod rate_limit;
#[cfg(feature = "telemetry-otlp-grpc")]
mod output_otlp_grpc;
#[cfg(feature = "user-tracing")]
mod output_otlp_uds;
#[cfg(feature = "user-tracing")]
mod traceparent;
use self::init::TracingHarness;
#[cfg(feature = "user-tracing")]
use self::internal::current_user_span;
use self::internal::{SharedSpan, create_span, current_span, shared_span, span_trace_id};
use super::TelemetryContext;
use super::scope::Scope;
use std::borrow::Cow;
use std::sync::Arc;
#[cfg(any(test, feature = "testing"))]
pub use self::testing::{TestSpan, TestTrace, TestTraceIterator, TestTraceOptions};
pub use cf_rustracing::tag::TagValue;
pub use cf_rustracing_jaeger::span::{Span, SpanContextState as SerializableTraceState, TraceId};
#[cfg(feature = "user-tracing")]
pub use self::traceparent::TraceparentContext;
#[cfg(feature = "user-tracing")]
pub use cf_rustracing::span::RoutingMetadata;
pub fn get_active_traces() -> String {
TracingHarness::get().active_roots.get_active_traces()
}
pub use foundations_macros::span_fn;
#[must_use]
pub struct SpanScope {
span: SharedSpan,
_inner: Scope<SharedSpan>,
}
impl SpanScope {
#[inline]
pub(crate) fn new(span: SharedSpan) -> Self {
Self {
span: span.clone(),
_inner: Scope::new(&TracingHarness::get().span_scope_stack, span),
}
}
pub fn into_context(self) -> TelemetryContext {
let mut ctx = TelemetryContext::current();
ctx.span = Some(self.span);
ctx
}
}
#[cfg(feature = "user-tracing")]
#[must_use]
pub struct UserSpanScope {
span: SharedSpan,
_inner: Scope<SharedSpan>,
}
#[cfg(feature = "user-tracing")]
impl UserSpanScope {
#[inline]
pub(crate) fn new(span: SharedSpan) -> Self {
Self {
span: span.clone(),
_inner: Scope::new(&TracingHarness::get_user().span_scope_stack, span),
}
}
pub fn into_context(self) -> TelemetryContext {
let mut ctx = TelemetryContext::current();
ctx.user_span = Some(self.span);
ctx
}
}
#[cfg(feature = "user-tracing")]
#[must_use]
pub struct DualSpanScope {
inner: SpanScope,
user: Option<UserSpanScope>,
}
#[cfg(feature = "user-tracing")]
impl DualSpanScope {
pub fn into_context(self) -> TelemetryContext {
let mut ctx = self.inner.into_context();
if let Some(user) = self.user {
ctx.user_span = Some(user.span);
}
ctx
}
}
#[derive(Default, Debug)]
pub struct StartTraceOptions {
pub stitch_with_trace: Option<SerializableTraceState>,
pub override_sampling_ratio: Option<f64>,
}
pub fn span_is_sampled() -> bool {
matches!(current_span(), Some(span) if span.is_sampled)
}
pub fn trace_id() -> Option<String> {
current_span()?.inner.with_read(span_trace_id)
}
pub fn state_for_trace_stitching() -> Option<SerializableTraceState> {
current_span()?
.inner
.with_read(|s| Some(s.context()?.state().clone()))
}
pub fn w3c_traceparent() -> Option<String> {
state_for_trace_stitching().map(|state| {
format!(
"00-{:0>16x}{:0>16x}-{:0>16x}-{:0>2x}",
state.trace_id().high,
state.trace_id().low,
state.span_id(),
state.flags()
)
})
}
pub fn span(name: impl Into<Cow<'static, str>>) -> SpanScope {
SpanScope::new(create_span(name))
}
#[cfg(feature = "user-tracing")]
pub fn dual_span(name: impl Into<Cow<'static, str>>) -> DualSpanScope {
let name = name.into();
let inner = span(name.clone());
let user = current_user_span()
.is_some()
.then(|| user_tracing::span(name));
DualSpanScope { inner, user }
}
pub fn start_trace(
root_span_name: impl Into<Cow<'static, str>>,
options: StartTraceOptions,
) -> SpanScope {
SpanScope::new(shared_span(internal::start_trace(root_span_name, options)))
}
#[cfg(feature = "user-tracing")]
pub mod user_tracing {
use super::internal::{self, create_user_span, current_user_span, user_shared_span};
use super::{RoutingMetadata, TraceparentContext, UserSpanScope};
use std::borrow::Cow;
pub fn start_trace(
name: impl Into<Cow<'static, str>>,
routing: impl RoutingMetadata + 'static,
inbound: Option<TraceparentContext>,
) -> UserSpanScope {
UserSpanScope::new(user_shared_span(internal::start_user_trace(
name,
std::sync::Arc::new(routing),
inbound,
)))
}
pub fn span(name: impl Into<Cow<'static, str>>) -> UserSpanScope {
UserSpanScope::new(create_user_span(name))
}
pub fn w3c_traceparent() -> Option<String> {
current_user_span()?.inner.with_read(|s| {
let state = s.context()?.state();
Some(format!(
"00-{:0>16x}{:0>16x}-{:0>16x}-{:0>2x}",
state.trace_id().high,
state.trace_id().low,
state.span_id(),
state.flags()
))
})
}
#[doc(inline)]
pub use crate::{
__add_user_span_log_fields as add_span_log_fields, __add_user_span_tags as add_span_tags,
__set_user_span_finish_callback as set_span_finish_callback,
};
}
pub fn rustracing_span() -> Option<Arc<parking_lot::RwLock<Span>>> {
current_span().map(|span| span.inner.into())
}
#[macro_export]
#[doc(hidden)]
macro_rules! __add_span_tags {
( $( $name:expr => $val:expr ),+ ) => {
$crate::telemetry::tracing::internal::write_current_span(|span| {
span.set_tags(|| {
vec![ $($crate::reexports_for_macros::cf_rustracing::tag::Tag::new($name, $val)),+ ]
});
});
};
( $tags:expr ) => {
$crate::telemetry::tracing::internal::write_current_span(|span| {
span.set_tags(|| {
$tags
.into_iter()
.map(|(name, val)| {
$crate::reexports_for_macros::cf_rustracing::tag::Tag::new(name, val)
})
});
});
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __add_span_log_fields {
( $( $field:expr => $val:expr ),+ ) => {
$crate::telemetry::tracing::internal::write_current_span(|span| {
span.log(|builder| {
$(
builder.field(($field, $val));
)+
});
});
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __set_span_start_time {
( $time:expr ) => {
$crate::telemetry::tracing::internal::write_current_span(|span| {
span.set_start_time(|| $time)
})
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __set_span_finish_time {
( $time:expr ) => {
$crate::telemetry::tracing::internal::write_current_span(|span| {
span.set_finish_time(|| $time)
})
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __set_span_finish_callback {
( None ) => {
$crate::telemetry::tracing::internal::write_current_span(|span| {
span.take_finish_callback();
})
};
( $cb:expr ) => {{
let cb = $cb;
$crate::telemetry::tracing::internal::write_current_span(move |span| {
span.set_finish_callback(cb);
})
}};
}
#[cfg(feature = "user-tracing")]
#[macro_export]
#[doc(hidden)]
macro_rules! __add_user_span_tags {
( $( $name:expr => $val:expr ),+ ) => {
$crate::telemetry::tracing::internal::write_current_user_span(|span| {
span.set_tags(|| {
vec![ $($crate::reexports_for_macros::cf_rustracing::tag::Tag::new($name, $val)),+ ]
});
});
};
( $tags:expr ) => {
$crate::telemetry::tracing::internal::write_current_user_span(|span| {
span.set_tags(|| {
$tags
.into_iter()
.map(|(name, val)| {
$crate::reexports_for_macros::cf_rustracing::tag::Tag::new(name, val)
})
});
});
};
}
#[cfg(feature = "user-tracing")]
#[macro_export]
#[doc(hidden)]
macro_rules! __add_user_span_log_fields {
( $( $field:expr => $val:expr ),+ ) => {
$crate::telemetry::tracing::internal::write_current_user_span(|span| {
span.log(|builder| {
$(
builder.field(($field, $val));
)+
});
});
};
}
#[cfg(feature = "user-tracing")]
#[macro_export]
#[doc(hidden)]
macro_rules! __set_user_span_finish_callback {
( None ) => {
$crate::telemetry::tracing::internal::write_current_user_span(|span| {
span.take_finish_callback();
})
};
( $cb:expr ) => {{
let cb = $cb;
$crate::telemetry::tracing::internal::write_current_user_span(move |span| {
span.set_finish_callback(cb);
})
}};
}
#[macro_export]
#[doc(hidden)]
#[cfg(feature = "testing")]
macro_rules! __test_trace {
( $name:expr $( ; $logs_tags:tt )? $( => $children:tt )? ) => {
$crate::telemetry::tracing::TestTrace(
$crate::telemetry::tracing::test_trace!(
@span $name $(; $logs_tags)? $( => $children )?
)
)
};
( @span $name:expr $( ; {
$( logs: [ $( ( $log_field:expr, $log_value:expr ) ),* ] )?
$( tags: [ $( ( $tag_name:expr, $tag_value:expr ) ),* ] )?
})? $( => $children:tt )? ) => {{
let mut logs = vec![ $( $( $( ( $log_field.into(), $log_value.into() ) ),* )? )? ];
logs.sort_by(|(f1, _), (f2, _)| std::cmp::Ord::cmp(f1, f2));
$crate::telemetry::tracing::TestSpan {
name: $name.to_string(),
children: $crate::telemetry::tracing::test_trace!( @children $( $children )? ),
logs,
tags: vec![ $( $( $( ( $tag_name.into(), $tag_value.into() ) ),* )? )? ],
start_time: std::time::SystemTime::UNIX_EPOCH,
finish_time: std::time::SystemTime::UNIX_EPOCH,
}}
};
( @children { $( $name:expr $( ; $logs_tags:tt )? $( => $children:tt )? ),* } ) => {
vec![
$(
$crate::telemetry::tracing::test_trace!(
@span $name $(; $logs_tags)? $( => $children )?
)
),*
]
};
( @children ) => { vec![] };
}
#[doc(inline)]
pub use {
__add_span_log_fields as add_span_log_fields, __add_span_tags as add_span_tags,
__set_span_finish_callback as set_span_finish_callback,
__set_span_finish_time as set_span_finish_time, __set_span_start_time as set_span_start_time,
};
#[cfg(feature = "testing")]
#[doc(inline)]
pub use __test_trace as test_trace;
#[cfg(all(test, feature = "user-tracing", feature = "testing"))]
mod user_tracing_tests {
use super::{
RoutingMetadata, StartTraceOptions, TraceparentContext, dual_span, span, start_trace,
test_trace, user_tracing,
};
use crate::telemetry::TelemetryContext;
use crate::telemetry::tracing::{Span, TestTraceOptions};
use cf_rustracing::tag::{Tag, TagValue};
#[derive(Debug)]
struct TestRouting {
zone_id: u64,
account_id: u64,
}
impl RoutingMetadata for TestRouting {
fn group_key(&self) -> String {
format!("{}|{}", self.zone_id, self.account_id)
}
fn encode(&self) -> String {
format!("zone={};account={}", self.zone_id, self.account_id)
}
}
fn routing() -> TestRouting {
TestRouting {
zone_id: 1,
account_id: 2,
}
}
#[test]
fn creation_and_nesting() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
let _child = user_tracing::span("child");
let _grandchild = user_tracing::span("grandchild");
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! {
"request" => {
"child" => {
"grandchild"
}
}
}]
);
assert!(ctx.traces(Default::default()).is_empty());
}
#[test]
fn tags_and_logs() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
user_tracing::add_span_tags!("cache.status" => "HIT");
user_tracing::add_span_log_fields!("event" => "lookup");
}
let opts = TestTraceOptions {
include_tags: true,
include_logs: true,
..Default::default()
};
let traces = ctx.user_traces(opts);
let root = &traces[0].0;
assert!(
root.tags
.contains(&("cache.status".to_string(), TagValue::String("HIT".into())))
);
assert!(
root.logs
.contains(&("event".to_string(), "lookup".to_string()))
);
}
#[test]
fn dual_span_is_parallel() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
let _s = dual_span("op");
}
assert_eq!(ctx.traces(Default::default()), vec![test_trace! { "op" }]);
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "op" } }]
);
}
#[test]
fn dual_span_names_span_when_internal_trace_sampled_out() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _internal_root = start_trace(
"internal_root",
StartTraceOptions {
override_sampling_ratio: Some(0.0),
..Default::default()
},
);
let _user_root = user_tracing::start_trace("request", routing(), None);
let _s = dual_span("op");
}
assert!(ctx.traces(Default::default()).is_empty());
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "op" } }]
);
}
#[tokio::test]
async fn dual_span_carried_across_await() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
dual_span("a")
.into_context()
.apply(async {
let _c = dual_span("c");
})
.await;
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "a" => { "c" } } }]
);
assert_eq!(
ctx.traces(Default::default()),
vec![test_trace! { "a" => { "c" } }]
);
}
#[tokio::test]
async fn dual_span_carried_via_internal_span() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
let _a = dual_span("a");
span("b")
.into_context()
.apply(async {
let _c = dual_span("c");
})
.await;
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "a" => { "c" } } }]
);
assert_eq!(
ctx.traces(Default::default()),
vec![test_trace! { "a" => { "b" => { "c" } } }]
);
}
#[test]
fn continues_inbound_trace() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
let inbound =
TraceparentContext::parse(b"00-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-01")
.unwrap();
let _root = user_tracing::start_trace("request", routing(), Some(inbound));
let out = user_tracing::w3c_traceparent().unwrap();
assert!(out.starts_with("00-11223344556677889900aabbccddeeff-"));
}
#[test]
fn outbound_traceparent_is_span_derived() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
let _root = user_tracing::start_trace("request", routing(), None);
let root_tp = user_tracing::w3c_traceparent().expect("root traceparent");
let _child = user_tracing::span("child");
let child_tp = user_tracing::w3c_traceparent().expect("child traceparent");
assert_eq!(&root_tp[..35], &child_tp[..35]);
assert_ne!(root_tp, child_tp);
}
#[test]
fn no_op_without_activation() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _child = user_tracing::span("child");
user_tracing::add_span_tags!("k" => "v");
}
assert!(ctx.user_traces(Default::default()).is_empty());
}
#[test]
fn finish_callback_runs() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
user_tracing::set_span_finish_callback!(|span: &mut Span| {
span.set_tag(|| Tag::new("finished", true));
});
}
let opts = TestTraceOptions {
include_tags: true,
..Default::default()
};
let traces = ctx.user_traces(opts);
assert!(traces[0].0.tags.iter().any(|(k, _)| k == "finished"));
}
#[tokio::test]
async fn propagates_across_await() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let root_ctx = user_tracing::start_trace("request", routing(), None).into_context();
root_ctx
.apply(async {
let _child = user_tracing::span("child");
})
.await;
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "child" } }]
);
}
#[tokio::test]
async fn user_span_carried_by_internal_context() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
span("internal")
.into_context()
.apply(async {
let _user_child = user_tracing::span("user_child");
})
.await;
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "user_child" } }]
);
assert_eq!(
ctx.traces(Default::default()),
vec![test_trace! { "internal" }]
);
}
#[tokio::test]
async fn internal_context_carries_the_current_user_span() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
let _child = user_tracing::span("child");
span("internal")
.into_context()
.apply(async {
let _grandchild = user_tracing::span("grandchild");
})
.await;
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "child" => { "grandchild" } } }]
);
}
#[tokio::test]
async fn user_span_carried_across_spawn() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let root_ctx = user_tracing::start_trace("request", routing(), None).into_context();
tokio::spawn(root_ctx.apply(async {
let _child = user_tracing::span("child");
}))
.await
.unwrap();
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "child" } }]
);
}
#[test]
fn user_span_survives_forked_trace() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
let _forked = TelemetryContext::current()
.with_forked_trace("fork")
.scope();
let _child = user_tracing::span("child");
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "child" } }]
);
}
#[cfg(feature = "logging")]
#[test]
fn user_span_survives_forked_log() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
let _forked = TelemetryContext::current().with_forked_log().scope();
let _child = user_tracing::span("child");
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "child" } }]
);
}
#[crate::telemetry::tracing::span_fn("internal_fn", crate_path = "crate")]
async fn internal_fn() {
let _user_child = user_tracing::span("user_child");
}
#[tokio::test]
async fn user_span_carried_by_span_fn() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
internal_fn().await;
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "user_child" } }]
);
assert_eq!(
ctx.traces(Default::default()),
vec![test_trace! { "internal_fn" }]
);
}
#[test]
fn dual_span_no_op_when_inactive() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _s = dual_span("op");
}
assert_eq!(ctx.traces(Default::default()), vec![test_trace! { "op" }]);
assert!(ctx.user_traces(Default::default()).is_empty());
}
#[crate::telemetry::tracing::span_fn("user_fn", user = true, crate_path = "crate")]
async fn user_fn() {}
#[tokio::test]
async fn span_fn_user_no_op_when_inactive() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
user_fn().await;
assert_eq!(
ctx.traces(Default::default()),
vec![test_trace! { "user_fn" }]
);
assert!(ctx.user_traces(Default::default()).is_empty());
}
#[tokio::test]
async fn span_fn_user_creates_parallel_span() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let _root = user_tracing::start_trace("request", routing(), None);
user_fn().await;
}
assert_eq!(
ctx.traces(Default::default()),
vec![test_trace! { "user_fn" }]
);
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "user_fn" } }]
);
}
}