#[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;
use self::internal::{SharedSpan, create_span, current_span, shared_span, span_trace_id};
#[cfg(feature = "user-tracing")]
use self::internal::{
SharedSpanHandle, child_user_span, current_user_span, start_user_trace, user_shared_span,
};
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::{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")]
#[derive(Debug)]
#[must_use]
pub struct UserSpan {
span: SharedSpan,
}
#[cfg(feature = "user-tracing")]
impl UserSpan {
#[inline]
pub(crate) fn from_shared(span: SharedSpan) -> Self {
Self { span }
}
pub const fn inactive() -> Self {
Self {
span: SharedSpan {
inner: SharedSpanHandle::Inactive,
is_sampled: false,
},
}
}
pub fn start_trace(
name: impl Into<Cow<'static, str>>,
routing: impl RoutingMetadata + 'static,
inbound: Option<TraceparentContext>,
) -> Self {
Self::from_shared(user_shared_span(start_user_trace(
name,
Arc::new(routing),
inbound,
)))
}
pub fn child(&self, name: impl Into<Cow<'static, str>>) -> Self {
Self::from_shared(child_user_span(&self.span, name))
}
pub fn set_tags<F, I>(&self, f: F)
where
F: FnOnce() -> I,
I: IntoIterator<Item = Tag>,
{
self.span.inner.with_write(|span| span.set_tags(f));
}
pub fn is_sampled(&self) -> bool {
self.span.is_sampled
}
pub fn w3c_traceparent(&self) -> Option<String> {
self.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()
))
})
}
pub fn enter(&self) -> UserSpanScope {
UserSpanScope::new(self.span.clone())
}
pub fn finish(self) {}
}
#[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::{create_user_span, current_user_span};
use super::{RoutingMetadata, TraceparentContext, UserSpanScope};
use std::borrow::Cow;
#[doc(inline)]
pub use super::UserSpan;
pub fn start_trace(
name: impl Into<Cow<'static, str>>,
routing: impl RoutingMetadata + 'static,
inbound: Option<TraceparentContext>,
) -> UserSpanScope {
UserSpan::start_trace(name, routing, inbound).enter()
}
pub fn span(name: impl Into<Cow<'static, str>>) -> UserSpanScope {
UserSpanScope::new(create_user_span(name))
}
pub fn w3c_traceparent() -> Option<String> {
UserSpan::from_shared(current_user_span()?).w3c_traceparent()
}
#[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, UserSpan, 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" } }]
);
}
fn assert_send<T: Send>() {}
#[test]
fn handle_is_send() {
assert_send::<UserSpan>();
}
#[test]
fn handle_creation_and_nesting() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let root = UserSpan::start_trace("request", routing(), None);
let child = root.child("child");
let _grandchild = child.child("grandchild");
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "child" => { "grandchild" } } }]
);
assert!(ctx.traces(Default::default()).is_empty());
}
#[test]
fn live_scope_outlives_the_handle() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
let root = UserSpan::start_trace("request", routing(), None);
let entered = root.enter();
root.finish();
assert!(
ctx.user_traces(Default::default()).is_empty(),
"the live scope should still hold the span open"
);
drop(entered);
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" }]
);
}
#[test]
fn siblings_from_explicit_parent() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let root = UserSpan::start_trace("request", routing(), None);
let first = root.child("first");
let second = root.child("second");
drop(first);
drop(second);
}
assert_eq!(
ctx.user_traces(Default::default()),
vec![test_trace! { "request" => { "first", "second" } }]
);
}
#[test]
fn inactive_handle_is_inert() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let span = UserSpan::inactive();
assert!(!span.is_sampled());
assert!(span.w3c_traceparent().is_none());
span.set_tags(|| vec![Tag::new("k", "v")]);
let child = span.child("child");
assert!(!child.is_sampled());
assert!(child.w3c_traceparent().is_none());
child.set_tags(|| vec![Tag::new("k", "v")]);
}
assert!(ctx.user_traces(Default::default()).is_empty());
}
#[test]
fn handle_set_tags() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let root = UserSpan::start_trace("request", routing(), None);
assert!(root.is_sampled());
root.set_tags(|| vec![Tag::new("cache.status", "HIT")]);
}
let traces = ctx.user_traces(TestTraceOptions {
include_tags: true,
..Default::default()
});
assert!(
traces[0]
.0
.tags
.contains(&("cache.status".to_string(), TagValue::String("HIT".into())))
);
}
#[test]
fn handle_enter_feeds_ambient_helpers() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let root = UserSpan::start_trace("request", routing(), None);
{
let _entered = root.enter();
user_tracing::add_span_tags!("entered" => true);
let _ambient_child = user_tracing::span("ambient_child");
}
}
let traces = ctx.user_traces(TestTraceOptions {
include_tags: true,
..Default::default()
});
assert_eq!(traces[0].0.children[0].name, "ambient_child");
assert!(
traces[0]
.0
.tags
.contains(&("entered".to_string(), TagValue::Boolean(true)))
);
}
#[test]
fn handle_continues_inbound_trace() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
let inbound =
TraceparentContext::parse(b"00-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-01")
.unwrap();
let root = UserSpan::start_trace("request", routing(), Some(inbound));
let root_tp = root.w3c_traceparent().expect("root traceparent");
assert!(root_tp.starts_with("00-11223344556677889900aabbccddeeff-"));
let child_tp = root.child("child").w3c_traceparent().expect("child");
assert_eq!(&root_tp[..35], &child_tp[..35]);
assert_ne!(root_tp, child_tp);
}
#[tokio::test]
async fn child_inherits_routing() {
use super::channel::{PipelineType, unbounded_channel};
use super::internal::{child_user_span, user_shared_span};
use cf_rustracing::Tracer;
use cf_rustracing::sampler::AllSampler;
use std::sync::Arc;
let (sender, span_rx) = unbounded_channel(PipelineType::User);
{
let tracer = Tracer::with_consumer(AllSampler, sender);
let root =
user_shared_span(tracer.span("request").routing(Arc::new(routing())).start());
let _child = child_user_span(&root, "child");
}
let mut finished = Vec::new();
span_rx.recv_many(&mut finished, 8).await;
assert_eq!(finished.len(), 2, "expected the root and its child");
for span in &finished {
assert_eq!(
span.routing().map(|r| r.encode()).as_deref(),
Some("zone=1;account=2"),
"a span reached the exporter without routing and would be dropped"
);
}
}
#[tokio::test]
async fn handle_survives_await_and_thread_move() {
let ctx = TelemetryContext::test();
let _scope = ctx.scope();
{
let root = UserSpan::start_trace("request", routing(), None);
let child = root.child("child");
tokio::spawn(async move {
tokio::task::yield_now().await;
child.set_tags(|| vec![Tag::new("moved", true)]);
child.finish();
})
.await
.unwrap();
}
let traces = ctx.user_traces(TestTraceOptions {
include_tags: true,
..Default::default()
});
assert_eq!(traces[0].0.children[0].name, "child");
assert!(
traces[0].0.children[0]
.tags
.contains(&("moved".to_string(), TagValue::Boolean(true)))
);
}
}