#![deny(missing_docs)]
use std::{
cell::RefCell,
fmt::{self, Write as _},
mem,
ops::{BitAnd, BitOr, ControlFlow, Not},
str::{self, FromStr},
};
use emit::{
event::ToEvent,
span::{SpanCtxt, SpanId, TraceId},
well_known::{KEY_SPAN_ID, KEY_SPAN_PARENT, KEY_TRACE_ID},
Ctxt, Empty, Filter, Frame, Props, Str, Value,
};
pub fn setup() -> emit::Setup<
emit::setup::DefaultEmitter,
TraceparentFilter,
TraceparentCtxt<emit::setup::DefaultCtxt>,
> {
emit::setup()
.emit_when(TraceparentFilter::new())
.map_ctxt(|ctxt| TraceparentCtxt::new(ctxt))
}
pub fn setup_with_sampler<S: Fn(&SpanCtxt) -> bool + Send + Sync + 'static>(
sampler: S,
) -> emit::Setup<
emit::setup::DefaultEmitter,
TraceparentFilter<S>,
TraceparentCtxt<emit::setup::DefaultCtxt>,
> {
emit::setup()
.emit_when(TraceparentFilter::new_with_sampler(sampler))
.map_ctxt(|ctxt| TraceparentCtxt::new(ctxt))
}
#[derive(Debug)]
pub struct Error {
msg: String,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.msg, f)
}
}
impl std::error::Error for Error {}
pub fn current() -> (Traceparent, Tracestate) {
get_active_traceparent()
.map(|active| (active.traceparent, active.tracestate))
.unwrap_or((Traceparent::empty(), Tracestate::empty()))
}
pub fn push(traceparent: Traceparent, tracestate: Tracestate) -> Frame<TraceparentCtxt> {
let mut frame = Frame::current(TraceparentCtxt::new(Empty));
let slot = if let Some(active) = get_active_traceparent() {
ActiveTraceparent {
span_parent: if active.is_parent_of(traceparent.trace_id) {
active.traceparent.span_id
} else {
None
},
traceparent,
tracestate,
}
} else {
ActiveTraceparent {
traceparent,
tracestate,
span_parent: None,
}
};
frame.inner_mut().slot = Some(slot);
frame.inner_mut().active = true;
frame
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Traceparent {
trace_id: Option<TraceId>,
span_id: Option<SpanId>,
trace_flags: TraceFlags,
}
impl Traceparent {
pub const fn new(
trace_id: Option<TraceId>,
span_id: Option<SpanId>,
trace_flags: TraceFlags,
) -> Self {
Traceparent {
trace_id,
span_id,
trace_flags,
}
}
const fn empty() -> Self {
Traceparent::new(None, None, TraceFlags::SAMPLED)
}
pub fn try_from_str(traceparent: &str) -> Result<Self, Error> {
let bytes = traceparent.as_bytes();
if bytes.len() != 55 {
return Err(Error {
msg: "traceparent headers must be 55 bytes".into(),
});
}
if bytes[2] != b'-' || bytes[35] != b'-' || bytes[52] != b'-' {
return Err(Error {
msg: format!("traceparent contains invalid field separators"),
});
}
let version = &bytes[0..2];
let b"00" = version else {
return Err(Error {
msg: format!("unexpected non '00' traceparent version"),
});
};
let trace_id = &bytes[3..35];
let span_id = &bytes[36..52];
let trace_flags = &bytes[53..55];
let trace_id = if trace_id == b"00000000000000000000000000000000" {
None
} else {
Some(TraceId::try_from_hex_slice(trace_id).map_err(|e| Error { msg: e.to_string() })?)
};
let span_id = if span_id == b"0000000000000000" {
None
} else {
Some(SpanId::try_from_hex_slice(span_id).map_err(|e| Error { msg: e.to_string() })?)
};
let trace_flags = TraceFlags::try_from_hex_slice(trace_flags)?;
Ok(Traceparent::new(trace_id, span_id, trace_flags))
}
pub fn trace_id(&self) -> Option<&TraceId> {
self.trace_id.as_ref()
}
pub fn span_id(&self) -> Option<&SpanId> {
self.span_id.as_ref()
}
pub fn trace_flags(&self) -> &TraceFlags {
&self.trace_flags
}
pub fn is_sampled(&self) -> bool {
self.trace_flags.is_sampled()
}
pub fn current() -> Self {
get_active_traceparent()
.map(|active| active.traceparent)
.unwrap_or(Traceparent::empty())
}
pub fn push(&self) -> Frame<TraceparentCtxt> {
let traceparent = *self;
let mut frame = Frame::current(TraceparentCtxt::new(Empty));
let slot = if let Some(active) = get_active_traceparent() {
ActiveTraceparent {
span_parent: if active.is_parent_of(self.trace_id) {
active.traceparent.span_id
} else {
None
},
traceparent,
tracestate: active.tracestate,
}
} else {
ActiveTraceparent {
traceparent,
tracestate: Tracestate::empty(),
span_parent: None,
}
};
frame.inner_mut().slot = Some(slot);
frame.inner_mut().active = true;
frame
}
pub fn is_valid(&self) -> bool {
self.trace_id.is_some() && self.span_id.is_some()
}
}
impl FromStr for Traceparent {
type Err = Error;
fn from_str(header: &str) -> Result<Self, Error> {
Self::try_from_str(header)
}
}
impl fmt::Display for Traceparent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("00-")?;
if let Some(trace_id) = self.trace_id {
fmt::Display::fmt(&trace_id, f)?;
f.write_char('-')?;
} else {
f.write_str("00000000000000000000000000000000-")?;
}
if let Some(span_id) = self.span_id {
fmt::Display::fmt(&span_id, f)?;
f.write_char('-')?;
} else {
f.write_str("0000000000000000-")?;
}
fmt::Display::fmt(&self.trace_flags, f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TraceFlags(u8);
impl TraceFlags {
pub const EMPTY: Self = TraceFlags(0);
pub const SAMPLED: Self = TraceFlags(1);
const ALL: Self = TraceFlags(!0);
pub fn from_u8(raw: u8) -> Self {
TraceFlags(raw)
}
pub fn to_u8(&self) -> u8 {
self.0
}
pub fn is_sampled(&self) -> bool {
self.0 & Self::SAMPLED.0 == 1
}
pub fn to_hex(&self) -> [u8; 2] {
const HEX_ENCODE_TABLE: [u8; 16] = [
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd',
b'e', b'f',
];
[
HEX_ENCODE_TABLE[(self.0 >> 4) as usize],
HEX_ENCODE_TABLE[(self.0 & 0x0f) as usize],
]
}
pub fn try_from_hex_slice(hex: &[u8]) -> Result<Self, Error> {
const HEX_DECODE_TABLE: &[u8; 256] = &{
let mut buf = [0; 256];
let mut i: u8 = 0;
loop {
buf[i as usize] = match i {
b'0'..=b'9' => i - b'0',
b'a'..=b'f' => i - b'a' + 10,
b'A'..=b'F' => i - b'A' + 10,
_ => 0xff,
};
if i == 255 {
break buf;
}
i += 1
}
};
let hex: &[u8; 2] = hex.try_into().map_err(|_| Error {
msg: "flags must be a 2 digit value".into(),
})?;
let h1 = HEX_DECODE_TABLE[hex[0] as usize];
let h2 = HEX_DECODE_TABLE[hex[1] as usize];
if h1 | h2 == 0xff {
return Err(Error {
msg: "invalid hex character".into(),
});
}
Ok(TraceFlags((h1 << 4) | h2))
}
}
impl BitAnd for TraceFlags {
type Output = Self;
fn bitand(self, other: Self) -> Self {
TraceFlags(self.0 & other.0)
}
}
impl BitOr for TraceFlags {
type Output = Self;
fn bitor(self, other: Self) -> Self {
TraceFlags(self.0 | other.0)
}
}
impl Not for TraceFlags {
type Output = Self;
fn not(self) -> Self {
TraceFlags(!self.0)
}
}
impl FromStr for TraceFlags {
type Err = Error;
fn from_str(flags: &str) -> Result<Self, Error> {
Self::try_from_hex_slice(flags.as_bytes())
}
}
impl fmt::Display for TraceFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(str::from_utf8(&self.to_hex()).unwrap())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tracestate(Str<'static>);
impl fmt::Display for Tracestate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl Tracestate {
pub const fn new_raw(value: &'static str) -> Tracestate {
Tracestate(Str::new(value))
}
pub const fn new_str_raw(value: Str<'static>) -> Tracestate {
Tracestate(value)
}
pub fn new_owned_raw(value: impl Into<Box<str>>) -> Tracestate {
Tracestate(Str::new_owned(value))
}
const fn empty() -> Tracestate {
Tracestate(Str::new(""))
}
pub fn get(&self) -> &str {
self.0.get()
}
pub fn current() -> Self {
get_active_traceparent()
.map(|active| active.tracestate)
.unwrap_or(Tracestate::empty())
}
pub fn push(&self) -> Frame<TraceparentCtxt> {
let tracestate = Tracestate(self.0.to_shared());
let mut frame = Frame::current(TraceparentCtxt::new(Empty));
let slot = if let Some(active) = get_active_traceparent() {
ActiveTraceparent {
span_parent: active.span_parent,
traceparent: active.traceparent,
tracestate,
}
} else {
ActiveTraceparent {
traceparent: Traceparent::empty(),
tracestate,
span_parent: None,
}
};
frame.inner_mut().slot = Some(slot);
frame.inner_mut().active = true;
frame
}
}
#[derive(Debug, Clone)]
struct ActiveTraceparent {
traceparent: Traceparent,
tracestate: Tracestate,
span_parent: Option<SpanId>,
}
impl ActiveTraceparent {
fn is_parent_of(&self, trace_id: Option<TraceId>) -> bool {
self.traceparent.trace_id.is_some() && self.traceparent.trace_id == trace_id
}
}
thread_local! {
static ACTIVE_TRACEPARENT: RefCell<Option<ActiveTraceparent>> = RefCell::new(None);
}
fn set_active_traceparent(traceparent: Option<ActiveTraceparent>) -> Option<ActiveTraceparent> {
ACTIVE_TRACEPARENT.with(|slot| {
let mut slot = slot.borrow_mut();
mem::replace(&mut *slot, traceparent)
})
}
fn get_active_traceparent() -> Option<ActiveTraceparent> {
ACTIVE_TRACEPARENT.with(|slot| slot.borrow().clone())
}
#[derive(Debug, Clone, Copy)]
pub struct TraceparentCtxt<C = Empty> {
inner: C,
}
#[derive(Debug, Clone)]
pub struct TraceparentCtxtFrame<F = Empty> {
inner: F,
active: bool,
slot: Option<ActiveTraceparent>,
}
pub struct TraceparentCtxtProps<P: ?Sized> {
inner: *const P,
ctxt: SpanCtxt,
}
impl<P: Props + ?Sized> Props for TraceparentCtxtProps<P> {
fn for_each<'kv, F: FnMut(Str<'kv>, Value<'kv>) -> ControlFlow<()>>(
&'kv self,
mut for_each: F,
) -> ControlFlow<()> {
self.ctxt.for_each(&mut for_each)?;
unsafe { &*self.inner }.for_each(for_each)
}
}
impl<C> TraceparentCtxt<C> {
pub const fn new(inner: C) -> Self {
TraceparentCtxt { inner }
}
}
impl<C: Ctxt> Ctxt for TraceparentCtxt<C> {
type Current = TraceparentCtxtProps<C::Current>;
type Frame = TraceparentCtxtFrame<C::Frame>;
fn with_current<R, F: FnOnce(&Self::Current) -> R>(&self, with: F) -> R {
let ctxt = get_active_traceparent()
.and_then(|active| {
if active.traceparent.trace_flags.is_sampled() {
Some(SpanCtxt::new(
active.traceparent.trace_id,
active.span_parent,
active.traceparent.span_id,
))
} else {
None
}
})
.unwrap_or(SpanCtxt::empty());
self.inner.with_current(|props| {
let props = TraceparentCtxtProps {
ctxt,
inner: props as *const C::Current,
};
with(&props)
})
}
fn open_root<P: Props>(&self, props: P) -> Self::Frame {
let (slot, props) =
incoming_traceparent(None::<fn(&SpanCtxt) -> bool>, props, TraceFlags::ALL);
let inner = self.inner.open_root(props);
TraceparentCtxtFrame {
inner,
active: true,
slot,
}
}
fn open_push<P: Props>(&self, props: P) -> Self::Frame {
let (slot, props) =
incoming_traceparent(None::<fn(&SpanCtxt) -> bool>, props, TraceFlags::ALL);
let inner = self.inner.open_push(props);
TraceparentCtxtFrame {
inner,
active: slot.is_some(),
slot,
}
}
fn open_disabled<P: Props>(&self, props: P) -> Self::Frame {
let (slot, props) =
incoming_traceparent(None::<fn(&SpanCtxt) -> bool>, props, TraceFlags::EMPTY);
let inner = self.inner.open_disabled(props);
TraceparentCtxtFrame {
inner,
active: slot.is_some(),
slot,
}
}
fn enter(&self, frame: &mut Self::Frame) {
if frame.active {
frame.slot = set_active_traceparent(frame.slot.take());
}
self.inner.enter(&mut frame.inner)
}
fn exit(&self, frame: &mut Self::Frame) {
if frame.active {
frame.slot = set_active_traceparent(frame.slot.take());
}
self.inner.exit(&mut frame.inner)
}
fn close(&self, frame: Self::Frame) {
self.inner.close(frame.inner)
}
}
fn incoming_traceparent(
sampler: Option<impl Fn(&SpanCtxt) -> bool>,
props: impl Props,
trace_flags: TraceFlags,
) -> (Option<ActiveTraceparent>, impl Props) {
let span_id = props.pull::<SpanId, _>(KEY_SPAN_ID);
let Some(span_id) = span_id else {
return (
None,
ExcludeTraceparentProps {
check: false,
inner: props,
},
);
};
let active = get_active_traceparent().filter(|active| active.traceparent.is_valid());
if Some(span_id)
== active
.as_ref()
.and_then(|parent| parent.traceparent.span_id)
{
return (
None,
ExcludeTraceparentProps {
check: false,
inner: props,
},
);
}
let incoming = if let Some(active) = active {
ActiveTraceparent {
traceparent: Traceparent::new(
active.traceparent.trace_id,
Some(span_id),
active.traceparent.trace_flags & trace_flags,
),
tracestate: active.tracestate,
span_parent: active.traceparent.span_id,
}
} else {
let trace_id = props.pull::<TraceId, _>(KEY_TRACE_ID);
let trace_flags = if let Some(sampler) = sampler {
if trace_flags.is_sampled() && sampler(&SpanCtxt::new(trace_id, None, Some(span_id))) {
trace_flags & TraceFlags::SAMPLED
} else {
trace_flags & TraceFlags::EMPTY
}
} else {
trace_flags & TraceFlags::SAMPLED
};
ActiveTraceparent {
traceparent: Traceparent::new(trace_id, Some(span_id), trace_flags),
tracestate: Tracestate::empty(),
span_parent: None,
}
};
(
Some(incoming),
ExcludeTraceparentProps {
check: true,
inner: props,
},
)
}
struct ExcludeTraceparentProps<P> {
check: bool,
inner: P,
}
impl<P: Props> Props for ExcludeTraceparentProps<P> {
fn for_each<'kv, F: FnMut(Str<'kv>, Value<'kv>) -> ControlFlow<()>>(
&'kv self,
mut for_each: F,
) -> ControlFlow<()> {
if !self.check {
return self.inner.for_each(for_each);
}
self.inner.for_each(|key, value| match key.get() {
KEY_TRACE_ID | KEY_SPAN_ID | KEY_SPAN_PARENT => ControlFlow::Continue(()),
_ => for_each(key, value),
})
}
}
pub struct TraceparentFilter<S = fn(&SpanCtxt) -> bool> {
sampler: Option<S>,
}
impl TraceparentFilter {
pub const fn new() -> Self {
TraceparentFilter { sampler: None }
}
}
impl<S> TraceparentFilter<S> {
pub const fn new_with_sampler(sampler: S) -> Self {
TraceparentFilter {
sampler: Some(sampler),
}
}
}
impl<S: Fn(&SpanCtxt) -> bool> Filter for TraceparentFilter<S> {
fn matches<E: ToEvent>(&self, evt: E) -> bool {
let evt = evt.to_event();
if emit::kind::is_span_filter().matches(&evt) {
if let (Some(incoming), _) =
incoming_traceparent(self.sampler.as_ref(), evt.props(), TraceFlags::SAMPLED)
{
return incoming.traceparent.trace_flags().is_sampled();
}
}
true
}
}
pub struct InSampledTraceFilter {
match_events_outside_traces: bool,
}
impl Filter for InSampledTraceFilter {
fn matches<E: ToEvent>(&self, _: E) -> bool {
if let Some(active) = get_active_traceparent() {
active.traceparent.trace_flags().is_sampled()
} else {
self.match_events_outside_traces
}
}
}
impl InSampledTraceFilter {
pub const fn new(match_events_outside_traces: bool) -> Self {
InSampledTraceFilter {
match_events_outside_traces,
}
}
}
pub const fn in_sampled_trace_filter(match_events_outside_traces: bool) -> InSampledTraceFilter {
InSampledTraceFilter::new(match_events_outside_traces)
}
#[cfg(test)]
mod tests {
use super::*;
use emit::{
and::And,
emitter, filter,
platform::{
rand_rng::RandRng, system_clock::SystemClock, thread_local_ctxt::ThreadLocalCtxt,
},
runtime::Runtime,
Empty, Rng,
};
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn traceparent_roundtrip() {
let traceparent = Traceparent::new(None, None, TraceFlags::EMPTY);
assert_eq!(
"00-00000000000000000000000000000000-0000000000000000-00",
traceparent.to_string()
);
assert_eq!(
traceparent,
Traceparent::try_from_str(&traceparent.to_string()).unwrap()
);
let rng = RandRng::new();
for _ in 0..1_000 {
let trace_id = TraceId::random(&rng);
let span_id = SpanId::random(&rng);
let trace_flags = TraceFlags::from_u8(rng.gen_u64().unwrap() as u8);
let traceparent = Traceparent::new(trace_id, span_id, trace_flags);
let formatted = traceparent.to_string();
assert_eq!(
Some(traceparent),
Traceparent::try_from_str(&formatted).ok(),
"{traceparent:?} ({formatted}) did not roundtrip"
);
}
}
#[test]
fn traceparent_parse_invalid() {
for case in [
"",
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-010",
"00 4bf92f3577b34da6a3ce929d0e0e4736 00f067aa0ba902b7 01",
"0x-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"00-4bf92f3577b34da6a3ce929d0e0e473x-00f067aa0ba902b7-01",
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902bx-01",
"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-0x",
] {
assert!(Traceparent::try_from_str(case).is_err());
}
}
#[test]
fn traceparent_set_current_sampled() {
let rng = RandRng::new();
assert_eq!(None, Traceparent::current().trace_id());
assert_eq!(None, Traceparent::current().span_id());
assert_eq!(TraceFlags::SAMPLED, *Traceparent::current().trace_flags());
let traceparent = Traceparent::new(
TraceId::random(&rng),
SpanId::random(&rng),
TraceFlags::SAMPLED,
);
let frame = traceparent.push();
assert_eq!(None, Traceparent::current().trace_id());
assert_eq!(None, Traceparent::current().span_id());
assert_eq!(TraceFlags::SAMPLED, *Traceparent::current().trace_flags());
frame.call(|| {
assert_eq!(traceparent, Traceparent::current());
let emit_ctxt = SpanCtxt::current(TraceparentCtxt::new(Empty));
assert_eq!(traceparent.trace_id(), emit_ctxt.trace_id());
assert_eq!(traceparent.span_id(), emit_ctxt.span_id());
assert!(emit_ctxt.span_parent().is_none());
});
assert_eq!(None, Traceparent::current().trace_id());
assert_eq!(None, Traceparent::current().span_id());
assert_eq!(TraceFlags::SAMPLED, *Traceparent::current().trace_flags());
}
#[test]
fn traceparent_set_current_unsampled() {
let rng = RandRng::new();
let traceparent = Traceparent::new(
TraceId::random(&rng),
SpanId::random(&rng),
TraceFlags::EMPTY,
);
traceparent.push().call(|| {
let emit_ctxt = SpanCtxt::current(TraceparentCtxt::new(Empty));
assert!(emit_ctxt.trace_id().is_none());
assert!(emit_ctxt.span_id().is_none());
assert!(emit_ctxt.span_parent().is_none());
});
assert_eq!(TraceFlags::SAMPLED, *Traceparent::current().trace_flags());
}
#[test]
fn traceparent_ctxt() {
let rng = RandRng::new();
let ctxt = TraceparentCtxt::new(ThreadLocalCtxt::new());
let span_ctxt_1 = SpanCtxt::current(&ctxt).new_child(&rng);
span_ctxt_1.push(&ctxt).call(|| {
let span_ctxt_2 = SpanCtxt::current(&ctxt).new_child(&rng);
span_ctxt_2.push(&ctxt).call(|| {
let traceparent = Traceparent::current();
let span_ctxt_3 = SpanCtxt::current(&ctxt);
assert_eq!(span_ctxt_2, span_ctxt_3);
assert_eq!(
span_ctxt_1.trace_id().unwrap(),
span_ctxt_2.trace_id().unwrap()
);
assert_eq!(
span_ctxt_1.span_id().unwrap(),
span_ctxt_2.span_parent().unwrap()
);
assert!(span_ctxt_2.span_id().is_some());
assert_eq!(
traceparent.trace_id().unwrap(),
span_ctxt_2.trace_id().unwrap()
);
assert_eq!(
traceparent.span_id().unwrap(),
span_ctxt_2.span_id().unwrap()
);
assert!(traceparent.trace_flags().is_sampled());
});
});
}
#[test]
fn traceparent_ctxt_ignores_invalid_parent() {
let rng = RandRng::new();
let ctxt = TraceparentCtxt::new(ThreadLocalCtxt::new());
Traceparent::new(None, None, TraceFlags::EMPTY)
.push()
.call(|| {
let span_ctxt_1 = SpanCtxt::current(&ctxt).new_child(&rng);
span_ctxt_1.push(&ctxt).call(|| {
let span_ctxt_2 = SpanCtxt::current(&ctxt);
assert_eq!(span_ctxt_1, span_ctxt_2);
});
});
}
#[test]
fn traceparent_ctxt_ignores_mismatched_trace_ids() {
let rng = RandRng::new();
let ctxt = TraceparentCtxt::new(ThreadLocalCtxt::new());
let span_ctxt_1 = SpanCtxt::current(&ctxt).new_child(&rng);
span_ctxt_1.push(&ctxt).call(|| {
let span_ctxt_2 = SpanCtxt::new_root(&rng);
span_ctxt_2.push(&ctxt).call(|| {
let traceparent = Traceparent::current();
let span_ctxt_3 = SpanCtxt::current(&ctxt);
assert_ne!(span_ctxt_2.trace_id(), span_ctxt_3.trace_id());
assert_eq!(span_ctxt_2.span_id(), span_ctxt_3.span_id());
assert_eq!(
span_ctxt_1.trace_id().unwrap(),
span_ctxt_3.trace_id().unwrap()
);
assert_eq!(
span_ctxt_1.span_id().unwrap(),
span_ctxt_3.span_parent().unwrap()
);
assert!(span_ctxt_3.span_id().is_some());
assert_eq!(
traceparent.trace_id().unwrap(),
span_ctxt_3.trace_id().unwrap()
);
assert_eq!(
traceparent.span_id().unwrap(),
span_ctxt_3.span_id().unwrap()
);
assert!(traceparent.trace_flags().is_sampled());
});
});
}
#[test]
fn traceparent_ctxt_unsets_on_empty_root() {
let rng = RandRng::new();
let ctxt = TraceparentCtxt::new(ThreadLocalCtxt::new());
let span_ctxt_1 = SpanCtxt::current(&ctxt).new_child(&rng);
span_ctxt_1.push(&ctxt).call(|| {
let traceparent = Traceparent::current();
assert_ne!(Traceparent::empty(), traceparent);
let frame = emit::Frame::root(&ctxt, emit::Empty);
frame.call(|| {
let traceparent = Traceparent::current();
let span_ctxt = SpanCtxt::current(&ctxt);
assert_eq!(Traceparent::empty(), traceparent);
assert!(span_ctxt.trace_id().is_none());
assert!(span_ctxt.span_id().is_none());
assert!(span_ctxt.span_parent().is_none());
})
});
}
#[test]
#[cfg(not(target_os = "wasi"))]
fn traceparent_across_threads() {
let rng = RandRng::new();
let traceparent = Traceparent::new(
TraceId::random(&rng),
SpanId::random(&rng),
TraceFlags::SAMPLED,
);
let frame = traceparent.push();
std::thread::spawn(move || {
frame.call(|| {
let current = Traceparent::current();
assert_eq!(traceparent, current);
})
})
.join()
.unwrap();
}
#[test]
#[cfg(not(target_os = "wasi"))]
fn traceparent_unsampled_across_threads() {
let rng = RandRng::new();
let traceparent = Traceparent::new(
TraceId::random(&rng),
SpanId::random(&rng),
TraceFlags::EMPTY,
);
let frame = traceparent.push();
std::thread::spawn(move || {
frame.call(|| {
let current = Traceparent::current();
assert_eq!(traceparent, current);
})
})
.join()
.unwrap();
}
#[test]
#[cfg(not(target_os = "wasi"))]
fn traceparent_ctxt_across_threads() {
let rng = RandRng::new();
let ctxt = TraceparentCtxt::new(ThreadLocalCtxt::new());
let span_ctxt = SpanCtxt::current(&ctxt).new_child(&rng).push(ctxt.clone());
std::thread::spawn(move || {
span_ctxt.call(|| {
let traceparent = Traceparent::current();
let span_ctxt = SpanCtxt::current(&ctxt);
assert_eq!(span_ctxt.trace_id(), traceparent.trace_id());
assert_eq!(span_ctxt.span_id(), traceparent.span_id());
assert_eq!(TraceFlags::SAMPLED, *traceparent.trace_flags());
})
})
.join()
.unwrap();
}
#[test]
#[cfg(not(target_os = "wasi"))]
fn traceparent_unsampled_ctxt_across_threads() {
let rng = RandRng::new();
let ctxt = TraceparentCtxt::new(ThreadLocalCtxt::new());
let frame = Traceparent::new(
TraceId::random(&rng),
SpanId::random(&rng),
TraceFlags::EMPTY,
)
.push();
frame.call(|| {
let span_ctxt = SpanCtxt::current(&ctxt).new_child(&rng).push(ctxt.clone());
std::thread::spawn(move || {
span_ctxt.call(|| {
let traceparent = Traceparent::current();
let span_ctxt = SpanCtxt::current(&ctxt);
assert_eq!(None, span_ctxt.trace_id());
assert_eq!(None, span_ctxt.span_id());
assert_eq!(TraceFlags::EMPTY, *traceparent.trace_flags());
})
})
.join()
.unwrap();
});
}
#[test]
fn traceparent_span() {
static RT: Runtime<
Empty,
And<filter::FromFn, TraceparentFilter>,
TraceparentCtxt<ThreadLocalCtxt>,
SystemClock,
RandRng,
> = Runtime::build(
Empty,
And::new(
filter::FromFn::new(|evt| evt.mdl() != emit::path!("unsampled")),
TraceparentFilter::new(),
),
TraceparentCtxt::new(ThreadLocalCtxt::shared()),
SystemClock::new(),
RandRng::new(),
);
#[emit::span(rt: RT, mdl: emit::path!("unsampled"), "a")]
fn unsampled() {
unsampled_sampled();
}
#[emit::span(rt: RT, mdl: emit::path!("sampled"), "a")]
fn unsampled_sampled() {
let ctxt = SpanCtxt::current(RT.ctxt());
let traceparent = Traceparent::current();
assert!(ctxt.trace_id().is_none());
assert!(ctxt.span_id().is_none());
assert!(traceparent.trace_id().is_some());
assert!(traceparent.span_id().is_some());
assert!(!traceparent.trace_flags().is_sampled());
}
#[emit::span(rt: RT, mdl: emit::path!("sampled"), "a")]
fn sampled() {
let ctxt = SpanCtxt::current(RT.ctxt());
let traceparent = Traceparent::current();
assert!(ctxt.trace_id().is_some());
assert!(ctxt.span_id().is_some());
assert_eq!(traceparent.trace_id(), ctxt.trace_id());
assert_eq!(traceparent.span_id(), ctxt.span_id());
assert!(traceparent.trace_flags().is_sampled());
}
unsampled();
sampled();
}
#[test]
fn traceparent_span_sampler() {
static EMITTER_SPAN_CALLS: AtomicUsize = AtomicUsize::new(0);
static EMITTER_EVENT_CALLS: AtomicUsize = AtomicUsize::new(0);
static SAMPLER_CALLS: AtomicUsize = AtomicUsize::new(0);
static RT: Runtime<
emitter::FromFn,
TraceparentFilter,
TraceparentCtxt<ThreadLocalCtxt>,
SystemClock,
RandRng,
> = Runtime::build(
emitter::FromFn::new(|evt| {
if evt.props().pull("evt_kind") == Some(emit::Kind::Span) {
EMITTER_SPAN_CALLS.fetch_add(1, Ordering::Relaxed);
} else {
EMITTER_EVENT_CALLS.fetch_add(1, Ordering::Relaxed);
}
}),
TraceparentFilter::new_with_sampler({
move |_| SAMPLER_CALLS.fetch_add(1, Ordering::Relaxed) % 10 == 0
}),
TraceparentCtxt::new(ThreadLocalCtxt::shared()),
SystemClock::new(),
RandRng::new(),
);
#[emit::span(rt: RT, "outer")]
fn outer() {
inner()
}
#[emit::span(rt: RT, "inner")]
fn inner() {
emit::emit!(rt: RT, "event");
}
for _ in 0..30 {
outer();
}
assert_eq!(6, EMITTER_SPAN_CALLS.load(Ordering::Relaxed));
assert_eq!(30, EMITTER_EVENT_CALLS.load(Ordering::Relaxed));
}
#[test]
fn tracestate_get_set() {
assert_eq!("", Tracestate::current().get());
assert_eq!(Tracestate::empty(), Tracestate::current());
let state = Tracestate::new_raw("a=1");
state.push().call(|| {
assert_eq!("a=1", Tracestate::current().get());
assert_eq!(state, Tracestate::current());
});
}
#[test]
fn in_trace_filter_includes_or_excludes_traced_events() {
Traceparent::new(
TraceId::from_u128(1),
SpanId::from_u64(1),
TraceFlags::SAMPLED,
)
.push()
.call(|| {
assert!(in_sampled_trace_filter(true).matches(emit::evt!("event")));
});
Traceparent::new(
TraceId::from_u128(1),
SpanId::from_u64(1),
TraceFlags::EMPTY,
)
.push()
.call(|| {
assert!(!in_sampled_trace_filter(true).matches(emit::evt!("event")));
});
}
#[test]
fn in_trace_filter_includes_or_excludes_untraced_events() {
assert!(in_sampled_trace_filter(true).matches(emit::evt!("event")));
assert!(!in_sampled_trace_filter(false).matches(emit::evt!("event")));
}
}