use emit_core::{
and::And,
clock::Clock,
ctxt::Ctxt,
empty::Empty,
event::{Event, ToEvent},
extent::{Extent, ToExtent},
filter::Filter,
path::Path,
props::{ErasedProps, Props},
rng::Rng,
str::{Str, ToStr},
template::{self, Template},
timestamp::Timestamp,
value::{FromValue, ToValue, Value},
well_known::{
KEY_EVT_KIND, KEY_SPAN_ID, KEY_SPAN_NAME, KEY_SPAN_PARENT, KEY_TRACE_ID, SPAN_KIND_CLIENT,
SPAN_KIND_CONSUMER, SPAN_KIND_INTERNAL, SPAN_KIND_PRODUCER, SPAN_KIND_SERVER,
},
};
use crate::{kind::Kind, Frame, Timer};
use core::{
fmt, mem,
num::{NonZeroU128, NonZeroU64},
ops::ControlFlow,
str::{self, FromStr},
};
pub use self::completion::Completion;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct TraceId(NonZeroU128);
impl fmt::Debug for TraceId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(str::from_utf8(&self.to_hex()).unwrap(), f)
}
}
impl fmt::Display for TraceId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(str::from_utf8(&self.to_hex()).unwrap())
}
}
impl FromStr for TraceId {
type Err = ParseIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_hex_slice(s.as_bytes())
}
}
impl ToValue for TraceId {
fn to_value(&self) -> Value<'_> {
Value::capture_display(self)
}
}
impl<'v> FromValue<'v> for TraceId {
fn from_value(value: Value<'v>) -> Option<Self> {
value
.downcast_ref::<TraceId>()
.copied()
.or_else(|| u128::from_value(value.by_ref()).and_then(TraceId::from_u128))
.or_else(|| TraceId::try_from_hex(value).ok())
}
}
#[cfg(feature = "sval")]
impl sval::Value for TraceId {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
sval::stream_display(stream, self)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for TraceId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl TraceId {
pub fn random<R: Rng>(rng: R) -> Option<Self> {
Some(TraceId::new(NonZeroU128::new(rng.gen_u128()?)?))
}
pub const fn new(v: NonZeroU128) -> Self {
TraceId(v)
}
pub fn from_u128(v: u128) -> Option<Self> {
Some(TraceId(NonZeroU128::new(v)?))
}
pub const fn to_u128(&self) -> u128 {
self.0.get()
}
pub fn from_bytes(v: [u8; 16]) -> Option<Self> {
Self::from_u128(u128::from_be_bytes(v))
}
pub fn to_bytes(&self) -> [u8; 16] {
self.0.get().to_be_bytes()
}
pub fn to_hex(&self) -> [u8; 32] {
let mut dst = [0; 32];
let src: [u8; 16] = self.0.get().to_be_bytes();
for i in 0..src.len() {
let b = src[i];
dst[i * 2] = HEX_ENCODE_TABLE[(b >> 4) as usize];
dst[i * 2 + 1] = HEX_ENCODE_TABLE[(b & 0x0f) as usize];
}
dst
}
pub fn try_from_hex_slice(hex: &[u8]) -> Result<Self, ParseIdError> {
let hex: &[u8; 32] = hex.try_into().map_err(|_| ParseIdError {})?;
let mut dst = [0; 16];
let mut i = 0;
while i < 16 {
let h1 = HEX_DECODE_TABLE[hex[i * 2] as usize];
let h2 = HEX_DECODE_TABLE[hex[i * 2 + 1] as usize];
if h1 | h2 == 0xff {
return Err(ParseIdError {});
}
dst[i] = SHL4_TABLE[h1 as usize] | h2;
i += 1;
}
Ok(TraceId::new(
NonZeroU128::new(u128::from_be_bytes(dst)).ok_or_else(|| ParseIdError {})?,
))
}
pub fn try_from_hex(hex: impl fmt::Display) -> Result<Self, ParseIdError> {
let mut buf = Buffer::<32>::new();
Self::try_from_hex_slice(buf.buffer(hex)?)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpanId(NonZeroU64);
impl fmt::Debug for SpanId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(str::from_utf8(&self.to_hex()).unwrap(), f)
}
}
impl fmt::Display for SpanId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(str::from_utf8(&self.to_hex()).unwrap())
}
}
impl FromStr for SpanId {
type Err = ParseIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_hex_slice(s.as_bytes())
}
}
impl ToValue for SpanId {
fn to_value(&self) -> Value<'_> {
Value::capture_display(self)
}
}
impl<'v> FromValue<'v> for SpanId {
fn from_value(value: Value<'v>) -> Option<Self> {
value
.downcast_ref::<SpanId>()
.copied()
.or_else(|| u64::from_value(value.by_ref()).and_then(SpanId::from_u64))
.or_else(|| SpanId::try_from_hex(value).ok())
}
}
#[cfg(feature = "sval")]
impl sval::Value for SpanId {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
sval::stream_display(stream, self)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SpanId {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl SpanId {
pub fn random<R: Rng>(rng: R) -> Option<Self> {
Some(SpanId::new(NonZeroU64::new(rng.gen_u64()?)?))
}
pub const fn new(v: NonZeroU64) -> Self {
SpanId(v)
}
pub fn from_u64(v: u64) -> Option<Self> {
Some(SpanId(NonZeroU64::new(v)?))
}
pub const fn to_u64(&self) -> u64 {
self.0.get()
}
pub fn from_bytes(v: [u8; 8]) -> Option<Self> {
Self::from_u64(u64::from_be_bytes(v))
}
pub fn to_bytes(&self) -> [u8; 8] {
self.0.get().to_be_bytes()
}
pub fn to_hex(&self) -> [u8; 16] {
let mut dst = [0; 16];
let src: [u8; 8] = self.0.get().to_be_bytes();
for i in 0..src.len() {
let b = src[i];
dst[i * 2] = HEX_ENCODE_TABLE[(b >> 4) as usize];
dst[i * 2 + 1] = HEX_ENCODE_TABLE[(b & 0x0f) as usize];
}
dst
}
pub fn try_from_hex_slice(hex: &[u8]) -> Result<Self, ParseIdError> {
let hex: &[u8; 16] = hex.try_into().map_err(|_| ParseIdError {})?;
let mut dst = [0; 8];
let mut i = 0;
while i < 8 {
let h1 = HEX_DECODE_TABLE[hex[i * 2] as usize];
let h2 = HEX_DECODE_TABLE[hex[i * 2 + 1] as usize];
if h1 | h2 == 0xff {
return Err(ParseIdError {});
}
dst[i] = SHL4_TABLE[h1 as usize] | h2;
i += 1;
}
Ok(SpanId::new(
NonZeroU64::new(u64::from_be_bytes(dst)).ok_or_else(|| ParseIdError {})?,
))
}
pub fn try_from_hex(hex: impl fmt::Display) -> Result<Self, ParseIdError> {
let mut buf = Buffer::<16>::new();
Self::try_from_hex_slice(buf.buffer(hex)?)
}
}
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',
];
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
}
};
const SHL4_TABLE: &[u8; 256] = &{
let mut buf = [0; 256];
let mut i: u8 = 0;
loop {
buf[i as usize] = i.wrapping_shl(4);
if i == 255 {
break buf;
}
i += 1;
}
};
#[derive(Debug)]
pub struct ParseIdError {}
impl fmt::Display for ParseIdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the input was not a valid id")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseIdError {}
struct Buffer<const N: usize> {
value: [u8; N],
idx: usize,
}
impl<const N: usize> Buffer<N> {
fn new() -> Self {
Buffer {
value: [0; N],
idx: 0,
}
}
fn buffer(&mut self, value: impl fmt::Display) -> Result<&[u8], ParseIdError> {
use fmt::Write as _;
self.idx = 0;
write!(self, "{}", value).map_err(|_| ParseIdError {})?;
Ok(&self.value[..self.idx])
}
}
impl<const N: usize> fmt::Write for Buffer<N> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let s = s.as_bytes();
let next_idx = self.idx + s.len();
if next_idx <= self.value.len() {
self.value[self.idx..next_idx].copy_from_slice(s);
self.idx = next_idx;
Ok(())
} else {
Err(fmt::Error)
}
}
}
pub struct Span<'a, P> {
mdl: Path<'a>,
name: Str<'a>,
extent: Option<Extent>,
tpl: Option<Template<'a>>,
props: P,
}
impl<'a, P: Props> Span<'a, P> {
pub fn new(
mdl: impl Into<Path<'a>>,
name: impl Into<Str<'a>>,
extent: impl ToExtent,
props: P,
) -> Self {
Span {
mdl: mdl.into(),
extent: extent.to_extent(),
name: name.into(),
tpl: None,
props,
}
}
pub fn mdl(&self) -> &Path<'a> {
&self.mdl
}
pub fn with_mdl(mut self, mdl: impl Into<Path<'a>>) -> Self {
self.mdl = mdl.into();
self
}
pub fn name(&self) -> &Str<'a> {
&self.name
}
pub fn with_name(mut self, name: impl Into<Str<'a>>) -> Self {
self.name = name.into();
self
}
pub fn extent(&self) -> Option<&Extent> {
self.extent.as_ref()
}
pub fn with_extent(mut self, extent: impl ToExtent) -> Self {
self.extent = extent.to_extent();
self
}
pub fn ts(&self) -> Option<&Timestamp> {
self.extent.as_ref().map(|extent| extent.as_point())
}
pub fn ts_start(&self) -> Option<&Timestamp> {
self.extent
.as_ref()
.and_then(|extent| extent.as_range())
.map(|span| &span.start)
}
pub fn props(&self) -> &P {
&self.props
}
pub fn props_mut(&mut self) -> &mut P {
&mut self.props
}
pub fn with_props<U>(self, props: U) -> Span<'a, U> {
Span {
mdl: self.mdl,
extent: self.extent,
name: self.name,
tpl: self.tpl,
props,
}
}
pub fn map_props<U>(self, map: impl FnOnce(P) -> U) -> Span<'a, U> {
Span {
mdl: self.mdl,
extent: self.extent,
name: self.name,
tpl: self.tpl,
props: map(self.props),
}
}
pub fn tpl(&self) -> &Template<'a> {
self.tpl.as_ref().unwrap_or(&END_TEMPLATE)
}
pub fn with_tpl(mut self, tpl: impl Into<Template<'a>>) -> Self {
self.tpl = Some(tpl.into());
self
}
pub fn erase<'b>(&'b self) -> Span<'b, &'b dyn ErasedProps> {
Span {
mdl: self.mdl.by_ref(),
extent: self.extent.clone(),
name: self.name.by_ref(),
tpl: self.tpl.as_ref().map(|tpl| tpl.by_ref()),
props: &self.props,
}
}
}
impl<'a, P: Props> fmt::Debug for Span<'a, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.to_event(), f)
}
}
const START_TEMPLATE_PARTS: &'static [template::Part<'static>] = &[
template::Part::hole("span_name"),
template::Part::text(" started"),
];
static START_TEMPLATE: Template<'static> = Template::new(&START_TEMPLATE_PARTS);
const END_TEMPLATE_PARTS: &'static [template::Part<'static>] = &[
template::Part::hole("span_name"),
template::Part::text(" completed"),
];
static END_TEMPLATE: Template<'static> = Template::new(&END_TEMPLATE_PARTS);
impl<'a, P: Props> ToEvent for Span<'a, P> {
type Props<'b>
= &'b Self
where
Self: 'b;
fn to_event<'b>(&'b self) -> Event<'b, Self::Props<'b>> {
Event::new(
self.mdl.by_ref(),
self.tpl().by_ref(),
self.extent.clone(),
&self,
)
}
}
impl<'a, P: Props> ToExtent for Span<'a, P> {
fn to_extent(&self) -> Option<Extent> {
self.extent().cloned()
}
}
impl<'a, P: Props> Props for Span<'a, P> {
fn for_each<'kv, F: FnMut(Str<'kv>, Value<'kv>) -> ControlFlow<()>>(
&'kv self,
mut for_each: F,
) -> ControlFlow<()> {
for_each(KEY_EVT_KIND.to_str(), Kind::Span.to_value())?;
for_each(KEY_SPAN_NAME.to_str(), self.name.to_value())?;
self.props.for_each(&mut for_each)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpanCtxt {
trace_id: Option<TraceId>,
span_parent: Option<SpanId>,
span_id: Option<SpanId>,
}
impl SpanCtxt {
pub const fn new(
trace_id: Option<TraceId>,
span_parent: Option<SpanId>,
span_id: Option<SpanId>,
) -> Self {
SpanCtxt {
trace_id,
span_parent,
span_id,
}
}
pub const fn empty() -> Self {
Self {
trace_id: None,
span_parent: None,
span_id: None,
}
}
pub fn new_root(rng: impl Rng) -> Self {
let trace_id = TraceId::random(&rng);
let span_parent = None;
let span_id = SpanId::random(&rng);
SpanCtxt::new(trace_id, span_parent, span_id)
}
pub fn current(ctxt: impl Ctxt) -> Self {
ctxt.with_current(|current| {
SpanCtxt::new(
current.pull::<TraceId, _>(KEY_TRACE_ID),
current.pull::<SpanId, _>(KEY_SPAN_PARENT),
current.pull::<SpanId, _>(KEY_SPAN_ID),
)
})
}
pub fn new_child(&self, rng: impl Rng) -> Self {
let trace_id = self.trace_id.or_else(|| TraceId::random(&rng));
let span_parent = self.span_id;
let span_id = SpanId::random(&rng);
SpanCtxt::new(trace_id, span_parent, span_id)
}
pub fn trace_id(&self) -> Option<&TraceId> {
self.trace_id.as_ref()
}
pub fn span_parent(&self) -> Option<&SpanId> {
self.span_parent.as_ref()
}
pub fn span_id(&self) -> Option<&SpanId> {
self.span_id.as_ref()
}
pub fn push<T: Ctxt>(&self, ctxt: T) -> Frame<T> {
Frame::push(ctxt, self)
}
}
impl Props for SpanCtxt {
fn for_each<'kv, F: FnMut(Str<'kv>, Value<'kv>) -> ControlFlow<()>>(
&'kv self,
mut for_each: F,
) -> ControlFlow<()> {
if let Some(ref trace_id) = self.trace_id {
for_each(KEY_TRACE_ID.to_str(), trace_id.to_value())?;
}
if let Some(ref span_id) = self.span_id {
for_each(KEY_SPAN_ID.to_str(), span_id.to_value())?;
}
if let Some(ref span_parent) = self.span_parent {
for_each(KEY_SPAN_PARENT.to_str(), span_parent.to_value())?;
}
ControlFlow::Continue(())
}
}
#[derive(Debug)]
pub struct ParseKindError {}
impl fmt::Display for ParseKindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the input was not a valid kind")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseKindError {}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum SpanKind {
Internal,
Server,
Client,
Producer,
Consumer,
}
impl SpanKind {
pub fn try_from_str(s: &str) -> Result<Self, ParseKindError> {
s.parse()
}
pub fn as_str(&self) -> &'static str {
match self {
SpanKind::Internal => SPAN_KIND_INTERNAL,
SpanKind::Server => SPAN_KIND_SERVER,
SpanKind::Client => SPAN_KIND_CLIENT,
SpanKind::Producer => SPAN_KIND_PRODUCER,
SpanKind::Consumer => SPAN_KIND_CONSUMER,
}
}
}
impl FromStr for SpanKind {
type Err = ParseKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s.eq_ignore_ascii_case(SPAN_KIND_INTERNAL) {
return Ok(SpanKind::Internal);
}
if s.eq_ignore_ascii_case(SPAN_KIND_SERVER) {
return Ok(SpanKind::Server);
}
if s.eq_ignore_ascii_case(SPAN_KIND_CLIENT) {
return Ok(SpanKind::Client);
}
if s.eq_ignore_ascii_case(SPAN_KIND_PRODUCER) {
return Ok(SpanKind::Producer);
}
if s.eq_ignore_ascii_case(SPAN_KIND_CONSUMER) {
return Ok(SpanKind::Consumer);
}
Err(ParseKindError {})
}
}
impl fmt::Debug for SpanKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self)
}
}
impl fmt::Display for SpanKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(feature = "sval")]
impl sval::Value for SpanKind {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.value(self.as_str())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SpanKind {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_str().serialize(serializer)
}
}
impl ToValue for SpanKind {
fn to_value(&self) -> Value<'_> {
Value::capture_display(self)
}
}
impl<'v> FromValue<'v> for SpanKind {
fn from_value(value: Value<'v>) -> Option<Self> {
value
.downcast_ref::<SpanKind>()
.copied()
.or_else(|| value.parse())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpanLink {
trace_id: TraceId,
span_id: SpanId,
}
impl SpanLink {
pub const fn new(trace_id: TraceId, span_id: SpanId) -> Self {
SpanLink { trace_id, span_id }
}
pub fn try_from_str(s: &str) -> Result<Self, ParseLinkError> {
Self::try_from_slice(s.as_bytes())
}
fn try_from_slice(s: &[u8]) -> Result<Self, ParseLinkError> {
if s.len() != 49 {
return Err(ParseLinkError {});
}
if s[32] != b'-' {
return Err(ParseLinkError {});
}
let trace_id = TraceId::try_from_hex_slice(&s[0..32]).map_err(|_| ParseLinkError {})?;
let span_id = SpanId::try_from_hex_slice(&s[33..49]).map_err(|_| ParseLinkError {})?;
Ok(SpanLink::new(trace_id, span_id))
}
pub fn parse(s: impl fmt::Display) -> Result<Self, ParseLinkError> {
let mut buf = Buffer::<49>::new();
Self::try_from_slice(buf.buffer(s).map_err(|_| ParseLinkError {})?)
}
pub fn trace_id(&self) -> &TraceId {
&self.trace_id
}
pub fn span_id(&self) -> &SpanId {
&self.span_id
}
}
impl FromStr for SpanLink {
type Err = ParseLinkError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
impl fmt::Debug for SpanLink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self)
}
}
impl fmt::Display for SpanLink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buf = [0; 49];
buf[0..32].copy_from_slice(&self.trace_id.to_hex());
buf[32] = b'-';
buf[33..49].copy_from_slice(&self.span_id.to_hex());
f.write_str(str::from_utf8(&buf).unwrap())
}
}
#[cfg(feature = "sval")]
impl sval::Value for SpanLink {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
sval::stream_display(stream, self)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SpanLink {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl ToValue for SpanLink {
fn to_value(&self) -> Value<'_> {
Value::capture_display(self)
}
}
impl<'v> FromValue<'v> for SpanLink {
fn from_value(value: Value<'v>) -> Option<Self> {
value
.downcast_ref::<SpanLink>()
.copied()
.or_else(|| SpanLink::parse(value).ok())
}
}
#[derive(Debug)]
pub struct ParseLinkError {}
impl fmt::Display for ParseLinkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the input was not a valid span link")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseLinkError {}
pub struct SpanGuard<'a, T: Clock, P: Props, F: Completion> {
state: SpanGuardState<T>,
data: Option<SpanGuardData<'a, P>>,
completion: Option<F>,
}
struct SpanGuardData<'a, P: Props> {
mdl: Path<'a>,
name: Str<'a>,
ctxt: SpanCtxt,
props: P,
}
enum SpanGuardState<T: Clock> {
Initial(T),
Started(Timer<T>),
Completed,
}
impl<T: Clock> SpanGuardState<T> {
fn take(&mut self) -> Self {
mem::replace(self, SpanGuardState::Completed)
}
}
impl<'a, T: Clock, P: Props, F: Completion> Drop for SpanGuard<'a, T, P, F> {
fn drop(&mut self) {
self.complete_default();
}
}
impl<'a, T: Clock, P: Props, F: Completion> SpanGuard<'a, T, P, F> {
pub fn new<C: Ctxt>(
filter: impl Filter,
ctxt: C,
clock: T,
rng: impl Rng,
completion: F,
ctxt_props: impl Props,
span_mdl: impl Into<Path<'a>>,
span_name: impl Into<Str<'a>>,
span_props: P,
) -> (Self, Frame<C>) {
let span_mdl = span_mdl.into();
let span_name = span_name.into();
let span_ctxt = SpanCtxt::current(&ctxt).new_child(rng);
let is_enabled = ctxt.with_current(|current_ctxt_props| {
filter.matches(
Span::new(
span_mdl.by_ref(),
span_name.by_ref(),
Empty,
(&span_props)
.and_props(&ctxt_props)
.and_props(span_ctxt)
.and_props(current_ctxt_props),
)
.to_event()
.with_tpl(START_TEMPLATE.by_ref()),
)
});
let guard = SpanGuard {
state: SpanGuardState::Initial(clock),
data: Some(SpanGuardData {
mdl: span_mdl,
ctxt: span_ctxt,
name: span_name,
props: span_props,
}),
completion: if is_enabled { Some(completion) } else { None },
};
let frame = guard.push_ctxt(ctxt, ctxt_props);
(guard, frame)
}
fn push_ctxt<C: Ctxt>(&self, ctxt: C, ctxt_props: impl Props) -> Frame<C> {
let span_ctxt = self.data.as_ref().expect("span is already complete").ctxt;
if self.is_enabled() {
Frame::push(ctxt, ctxt_props.and_props(span_ctxt))
} else {
Frame::disabled(ctxt, ctxt_props.and_props(span_ctxt))
}
}
pub fn start(&mut self) {
let state = mem::replace(&mut self.state, SpanGuardState::Completed);
let SpanGuardState::Initial(clock) = state else {
self.state = state;
return;
};
self.state = SpanGuardState::Started(Timer::start(clock));
}
pub fn is_enabled(&self) -> bool {
self.completion.is_some()
}
#[must_use = "this method returns a new `SpanGuard` that will be immediately dropped unless used"]
pub fn with_completion<U: Completion>(mut self, completion: U) -> SpanGuard<'a, T, P, U> {
self.completion.take();
SpanGuard {
state: self.state.take(),
data: self.data.take(),
completion: Some(completion),
}
}
#[must_use = "this method returns a new `SpanGuard` that will be immediately dropped unless used"]
pub fn with_mdl(mut self, mdl: impl Into<Path<'a>>) -> Self {
if let Some(ref mut data) = self.data {
data.mdl = mdl.into();
}
self
}
#[must_use = "this method returns a new `SpanGuard` that will be immediately dropped unless used"]
pub fn with_name(mut self, name: impl Into<Str<'a>>) -> Self {
if let Some(ref mut data) = self.data {
data.name = name.into();
}
self
}
#[must_use = "this method returns a new `SpanGuard` that will be immediately dropped unless used"]
pub fn with_props<U: Props>(self, props: U) -> SpanGuard<'a, T, U, F> {
self.map_props(|_| props)
}
#[must_use = "this method returns a new `SpanGuard` that will be immediately dropped unless used"]
pub fn map_props<U: Props>(mut self, map: impl FnOnce(P) -> U) -> SpanGuard<'a, T, U, F> {
let data = self.data.take().map(|data| SpanGuardData {
mdl: data.mdl,
name: data.name,
ctxt: data.ctxt,
props: map(data.props),
});
SpanGuard {
state: self.state.take(),
data,
completion: self.completion.take(),
}
}
#[must_use = "this method returns a new `SpanGuard` that will be immediately dropped unless used"]
pub fn push_prop<K: ToStr, V: ToValue>(
self,
key: K,
value: V,
) -> SpanGuard<'a, T, And<(K, V), P>, F> {
self.push_props((key, value))
}
#[must_use = "this method returns a new `SpanGuard` that will be immediately dropped unless used"]
pub fn push_props<U: Props>(self, props: U) -> SpanGuard<'a, T, And<U, P>, F> {
self.map_props(|current| props.and_props(current))
}
pub fn props_mut(&mut self) -> Option<&mut P> {
self.data.as_mut().map(|data| &mut data.props)
}
pub fn complete(mut self) -> bool {
self.complete_default()
}
fn complete_default(&mut self) -> bool {
if let (SpanGuardState::Started(timer), Some(data), Some(completion)) =
(self.state.take(), self.data.take(), self.completion.take())
{
completion.complete(Span::new(data.mdl, data.name, timer, data.props));
true
} else {
false
}
}
pub fn complete_with(mut self, completion: impl Completion) -> bool {
if let (SpanGuardState::Started(timer), Some(data), Some(_)) =
(self.state.take(), self.data.take(), self.completion.take())
{
completion.complete(Span::new(data.mdl, data.name, timer, data.props));
true
} else {
false
}
}
}
pub mod completion {
use emit_core::{
ctxt::Ctxt,
emitter::Emitter,
empty::Empty,
event::ToEvent,
props::{ErasedProps, Props},
template::Template,
value::{ToValue, Value},
well_known::{KEY_ERR, KEY_LVL},
};
use crate::{level::Level, span::Span};
use core::fmt;
pub trait Completion {
fn complete<P: Props>(&self, span: Span<P>);
}
impl<'a, C: Completion + ?Sized> Completion for &'a C {
fn complete<P: Props>(&self, span: Span<P>) {
(**self).complete(span)
}
}
impl Completion for Empty {
fn complete<P: Props>(&self, _: Span<P>) {}
}
pub struct Default<'a, E, C, L = Level> {
emitter: E,
ctxt: C,
tpl: Option<Template<'a>>,
lvl: Option<L>,
panic_lvl: Option<L>,
}
impl<'a, E: Emitter, C: Ctxt, L: ToValue> Completion for Default<'a, E, C, L> {
fn complete<P: Props>(&self, span: Span<P>) {
struct PanicError;
impl fmt::Debug for PanicError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for PanicError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "panicked")
}
}
#[cfg(feature = "std")]
impl std::error::Error for PanicError {}
impl ToValue for PanicError {
fn to_value(&self) -> Value<'_> {
#[cfg(feature = "std")]
{
Value::capture_error(self)
}
#[cfg(not(feature = "std"))]
{
Value::capture_display(self)
}
}
}
fn is_panicking() -> bool {
#[cfg(feature = "std")]
{
std::thread::panicking()
}
#[cfg(not(feature = "std"))]
{
false
}
}
let completion_props = if is_panicking() {
[
self.panic_lvl
.as_ref()
.map(|lvl| Value::from_any(lvl))
.or_else(|| Some(Value::from_any(&Level::Error)))
.map(|lvl| (KEY_LVL, lvl)),
Some((KEY_ERR, Value::from_any(&PanicError))),
]
} else {
[
self.lvl
.as_ref()
.map(|lvl| Value::from_any(lvl))
.map(|lvl| (KEY_LVL, lvl)),
None,
]
};
let tpl = self.tpl.as_ref().unwrap_or_else(|| span.tpl()).by_ref();
let evt = span
.to_event()
.with_tpl(tpl)
.map_props(|span_props| completion_props.and_props(span_props));
emit_core::emit(&self.emitter, Empty, &self.ctxt, Empty, evt);
}
}
impl<'a, E, C, L> Default<'a, E, C, L> {
pub const fn new(emitter: E, ctxt: C) -> Self {
Default {
emitter,
ctxt,
tpl: None,
lvl: None,
panic_lvl: None,
}
}
pub fn with_lvl(self, lvl: L) -> Self {
Default {
emitter: self.emitter,
ctxt: self.ctxt,
tpl: self.tpl,
lvl: Some(lvl),
panic_lvl: self.panic_lvl,
}
}
pub fn with_panic_lvl(self, lvl: L) -> Self {
Default {
emitter: self.emitter,
ctxt: self.ctxt,
tpl: self.tpl,
lvl: self.lvl,
panic_lvl: Some(lvl),
}
}
pub fn with_tpl<'b>(self, tpl: impl Into<Template<'b>>) -> Default<'b, E, C, L> {
Default {
emitter: self.emitter,
ctxt: self.ctxt,
tpl: Some(tpl.into()),
lvl: self.lvl,
panic_lvl: self.panic_lvl,
}
}
}
pub const fn default<'a, E: Emitter, C: Ctxt>(emitter: E, ctxt: C) -> Default<'a, E, C> {
Default::new(emitter, ctxt)
}
pub struct FromEmitter<E>(E);
impl<E: Emitter> Completion for FromEmitter<E> {
fn complete<P: Props>(&self, span: Span<P>) {
self.0.emit(span)
}
}
impl<E> FromEmitter<E> {
pub const fn new(emitter: E) -> Self {
FromEmitter(emitter)
}
}
pub const fn from_emitter<E: Emitter>(emitter: E) -> FromEmitter<E> {
FromEmitter(emitter)
}
pub struct FromFn<F = fn(Span<&dyn ErasedProps>)>(F);
pub const fn from_fn<F: Fn(Span<&dyn ErasedProps>)>(f: F) -> FromFn<F> {
FromFn(f)
}
impl<F> FromFn<F> {
pub const fn new(completion: F) -> FromFn<F> {
FromFn(completion)
}
}
impl<F: Fn(Span<&dyn ErasedProps>)> Completion for FromFn<F> {
fn complete<P: Props>(&self, span: Span<P>) {
(self.0)(span.erase())
}
}
mod internal {
use super::*;
pub trait DispatchCompletion {
fn dispatch_complete(&self, span: Span<&dyn ErasedProps>);
}
pub trait SealedCompletion {
fn erase_completion(&self) -> crate::internal::Erased<&dyn DispatchCompletion>;
}
}
pub trait ErasedCompletion: internal::SealedCompletion {}
impl<T: Completion> ErasedCompletion for T {}
impl<T: Completion> internal::SealedCompletion for T {
fn erase_completion(&self) -> crate::internal::Erased<&dyn internal::DispatchCompletion> {
crate::internal::Erased(self)
}
}
impl<T: Completion> internal::DispatchCompletion for T {
fn dispatch_complete(&self, span: Span<&dyn ErasedProps>) {
self.complete(span)
}
}
impl<'a> Completion for dyn ErasedCompletion + 'a {
fn complete<P: Props>(&self, span: Span<P>) {
self.erase_completion().0.dispatch_complete(span.erase())
}
}
impl<'a> Completion for dyn ErasedCompletion + Send + Sync + 'a {
fn complete<P: Props>(&self, span: Span<P>) {
(self as &(dyn ErasedCompletion + 'a)).complete(span)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use emit_core::{emitter, path::Path};
#[test]
fn from_fn_completion() {
let called = Cell::new(false);
let completion = from_fn(|span| {
assert_eq!("test", span.name());
called.set(true);
});
completion.complete(Span::new(Path::new_raw("test"), "test", Empty, Empty));
assert!(called.get());
}
#[test]
fn erased_completion() {
let called = Cell::new(false);
let completion = from_fn(|span| {
assert_eq!("test", span.name());
called.set(true);
});
let completion = &completion as &dyn ErasedCompletion;
completion.complete(Span::new(Path::new_raw("test"), "test", Empty, Empty));
assert!(called.get());
}
#[test]
fn default_completion() {
let called = Cell::new(false);
let completion = default(
emitter::from_fn(|_| {
called.set(true);
}),
Empty,
);
completion.complete(Span::new(Path::new_raw("test"), "test", Empty, Empty));
assert!(called.get());
}
#[test]
fn default_completion_uses_lvl() {
let called = Cell::new(false);
let completion = default(
emitter::from_fn(|evt| {
assert_eq!(Level::Info, evt.props().pull("lvl").unwrap());
called.set(true);
}),
Empty,
)
.with_lvl(Level::Info);
completion.complete(Span::new(Path::new_raw("test"), "test", Empty, Empty));
assert!(called.get());
}
#[test]
fn default_completion_uses_tpl() {
let called = Cell::new(false);
let completion = default(
emitter::from_fn(|evt| {
assert_eq!("test template", evt.msg().to_string());
called.set(true);
}),
Empty,
)
.with_lvl(Level::Info)
.with_tpl(Template::literal("test template"));
completion.complete(Span::new(Path::new_raw("test"), "test", Empty, Empty));
assert!(called.get());
}
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
struct Guard<T: Completion>(T);
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
impl<T: Completion> Drop for Guard<T> {
fn drop(&mut self) {
self.0
.complete(Span::new(Path::new_raw("test"), "test", Empty, Empty));
}
}
#[test]
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
fn default_completion_detects_panics() {
let called = Cell::new(false);
let completion = default(
emitter::from_fn(|evt| {
assert_eq!(Level::Error, evt.props().pull("lvl").unwrap());
assert!(evt.props().get("err").is_some());
called.set(true);
}),
Empty,
);
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
let _guard = Guard(completion);
panic!("explicit panic")
}));
assert!(called.get());
}
#[test]
#[cfg(feature = "std")]
#[cfg(not(target_arch = "wasm32"))]
fn default_completion_uses_panic_lvl() {
let called = Cell::new(false);
let completion = default(
emitter::from_fn(|evt| {
assert_eq!(Level::Warn, evt.props().pull("lvl").unwrap());
called.set(true);
}),
Empty,
)
.with_panic_lvl(Level::Warn);
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
let _guard = Guard(completion);
panic!("explicit panic")
}));
assert!(called.get());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
use emit_core::filter;
use std::time::Duration;
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
use std::cell::Cell;
use crate::Timestamp;
#[test]
fn span_id_parse() {
for (case, expected) in [
(
"0123456789abcdef",
Ok(SpanId::from_u64(0x0123456789abcdef).unwrap()),
),
(
"0000000000000001",
Ok(SpanId::from_u64(0x0000000000000001).unwrap()),
),
("0000000000000000", Err(ParseIdError {})),
("0x00000000000001", Err(ParseIdError {})),
("0x0000000000000001", Err(ParseIdError {})),
("1", Err(ParseIdError {})),
("", Err::<SpanId, ParseIdError>(ParseIdError {})),
] {
match expected {
Ok(expected) => {
assert_eq!(expected, SpanId::try_from_hex(case).unwrap());
assert_eq!(expected, SpanId::try_from_hex(case).unwrap());
}
Err(e) => assert_eq!(
e.to_string(),
SpanId::try_from_hex(case).unwrap_err().to_string()
),
}
}
}
#[test]
fn trace_id_parse() {
for (case, expected) in [
(
"0123456789abcdef0123456789abcdef",
Ok(TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap()),
),
(
"00000000000000000000000000000001",
Ok(TraceId::from_u128(0x00000000000000000000000000000001).unwrap()),
),
("00000000000000000000000000000000", Err(ParseIdError {})),
("0x000000000000000000000000000001", Err(ParseIdError {})),
("0x00000000000000000000000000000001", Err(ParseIdError {})),
("1", Err(ParseIdError {})),
("", Err::<TraceId, ParseIdError>(ParseIdError {})),
] {
match expected {
Ok(expected) => assert_eq!(expected, TraceId::try_from_hex(case).unwrap()),
Err(e) => assert_eq!(
e.to_string(),
TraceId::try_from_hex(case).unwrap_err().to_string()
),
}
}
}
#[test]
fn span_id_fmt() {
for (case, expected) in [
(SpanId::from_u64(1).unwrap(), "0000000000000001"),
(
SpanId::from_u64(0x0123456789abcdef).unwrap(),
"0123456789abcdef",
),
] {
assert_eq!(expected, case.to_string());
assert_eq!(expected, str::from_utf8(&case.to_hex()).unwrap());
}
}
#[test]
fn trace_id_fmt() {
for (case, expected) in [
(
TraceId::from_u128(1).unwrap(),
"00000000000000000000000000000001",
),
(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
"0123456789abcdef0123456789abcdef",
),
] {
assert_eq!(expected, case.to_string());
assert_eq!(expected, str::from_utf8(&case.to_hex()).unwrap());
}
}
#[test]
fn span_id_roundtrip() {
let id = SpanId::new(NonZeroU64::new(u64::MAX / 2).unwrap());
let fmt = id.to_string();
let parsed: SpanId = fmt.parse().unwrap();
assert_eq!(id, parsed, "{}", fmt);
}
#[test]
fn trace_id_roundtrip() {
let id = TraceId::new(NonZeroU128::new(u128::MAX / 2).unwrap());
let fmt = id.to_string();
let parsed: TraceId = fmt.parse().unwrap();
assert_eq!(id, parsed, "{}", fmt);
}
#[test]
fn span_id_random_empty() {
assert!(SpanId::random(Empty).is_none());
}
#[test]
#[cfg(feature = "rand")]
fn span_id_random_rand() {
assert!(SpanId::random(crate::platform::DefaultRng::new()).is_some());
}
#[test]
fn trace_id_random_empty() {
assert!(TraceId::random(Empty).is_none());
}
#[test]
#[cfg(feature = "rand")]
fn trace_id_random_rand() {
assert!(TraceId::random(crate::platform::DefaultRng::new()).is_some());
}
#[test]
fn span_id_to_from_value() {
let id = SpanId::from_u64(u64::MAX / 2).unwrap();
assert_eq!(id, SpanId::from_value(id.to_value()).unwrap());
}
#[test]
fn span_id_from_value_string() {
assert_eq!(
SpanId::from_u64(0x0123456789abcdef).unwrap(),
Value::from("0123456789abcdef").cast().unwrap()
);
}
#[test]
fn span_id_from_value_u64() {
assert_eq!(
SpanId::from_u64(0x0123456789abcdef).unwrap(),
Value::from(0x0123456789abcdefu64).cast().unwrap()
);
}
#[test]
fn trace_id_to_from_value() {
let id = TraceId::from_u128(u128::MAX / 2).unwrap();
assert_eq!(id, TraceId::from_value(id.to_value()).unwrap());
}
#[test]
fn trace_id_from_value_string() {
assert_eq!(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
Value::from("0123456789abcdef0123456789abcdef")
.cast()
.unwrap()
);
}
#[test]
fn trace_id_from_value_u128() {
assert_eq!(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
Value::from(0x0123456789abcdef0123456789abcdefu128)
.cast()
.unwrap()
);
}
#[cfg(feature = "sval")]
#[test]
fn span_id_stream() {
sval_test::assert_tokens(
&SpanId::from_u64(0x0123456789abcdef).unwrap(),
&[
sval_test::Token::TextBegin(None),
sval_test::Token::TextFragmentComputed("0123456789abcdef".to_owned()),
sval_test::Token::TextEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn span_id_serialize() {
serde_test::assert_ser_tokens(
&SpanId::from_u64(0x0123456789abcdef).unwrap(),
&[serde_test::Token::Str("0123456789abcdef")],
);
}
#[cfg(feature = "sval")]
#[test]
fn trace_id_stream() {
sval_test::assert_tokens(
&TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
&[
sval_test::Token::TextBegin(None),
sval_test::Token::TextFragmentComputed(
"0123456789abcdef0123456789abcdef".to_owned(),
),
sval_test::Token::TextEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn trace_id_serialize() {
serde_test::assert_ser_tokens(
&TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
&[serde_test::Token::Str("0123456789abcdef0123456789abcdef")],
);
}
#[test]
fn span_link_parse() {
for (case, expected) in [
(
"0123456789abcdef0123456789abcdef-0123456789abcdef",
Ok::<SpanLink, ParseLinkError>(SpanLink::new(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
SpanId::from_u64(0x0123456789abcdef).unwrap(),
)),
),
(
"00000000000000000000000000000001-0000000000000001",
Ok::<SpanLink, ParseLinkError>(SpanLink::new(
TraceId::from_u128(0x1).unwrap(),
SpanId::from_u64(0x1).unwrap(),
)),
),
(
"00000000000000000000000000000000-0000000000000000",
Err(ParseLinkError {}),
),
(
"0123456789abcdef0123456789abcde-0123456789abcdef",
Err(ParseLinkError {}),
),
(
"0123456789abcdef0123456789abcdef-0123456789abcde",
Err(ParseLinkError {}),
),
(
"0123456789abcdef0123456789abcdef 0123456789abcdef",
Err(ParseLinkError {}),
),
(
"0123456789abcdef0123456789abcdef0123456789abcdef",
Err(ParseLinkError {}),
),
] {
match expected {
Ok(expected) => assert_eq!(expected, SpanLink::try_from_str(case).unwrap()),
Err(e) => assert_eq!(
e.to_string(),
SpanLink::try_from_str(case).unwrap_err().to_string()
),
}
}
}
#[test]
fn span_link_fmt() {
let link = SpanLink::new(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
SpanId::from_u64(0x0123456789abcdef).unwrap(),
);
assert_eq!(
"0123456789abcdef0123456789abcdef-0123456789abcdef",
link.to_string()
);
}
#[test]
fn span_link_roundtrip() {
let link = SpanLink::new(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
SpanId::from_u64(0x0123456789abcdef).unwrap(),
);
assert_eq!(link, SpanLink::parse(link).unwrap());
}
#[test]
fn span_link_to_from_value() {
let link = SpanLink::new(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
SpanId::from_u64(0x0123456789abcdef).unwrap(),
);
assert_eq!(link, SpanLink::from_value(link.to_value()).unwrap());
}
#[test]
fn span_link_from_value_string() {
assert_eq!(
SpanLink::new(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
SpanId::from_u64(0x0123456789abcdef).unwrap(),
),
Value::from("0123456789abcdef0123456789abcdef-0123456789abcdef")
.cast()
.unwrap()
);
}
#[cfg(feature = "sval")]
#[test]
fn span_link_stream() {
sval_test::assert_tokens(
&SpanLink::new(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
SpanId::from_u64(0x0123456789abcdef).unwrap(),
),
&[
sval_test::Token::TextBegin(None),
sval_test::Token::TextFragmentComputed(
"0123456789abcdef0123456789abcdef-0123456789abcdef".to_owned(),
),
sval_test::Token::TextEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn span_link_serialize() {
serde_test::assert_ser_tokens(
&SpanLink::new(
TraceId::from_u128(0x0123456789abcdef0123456789abcdef).unwrap(),
SpanId::from_u64(0x0123456789abcdef).unwrap(),
),
&[serde_test::Token::Str(
"0123456789abcdef0123456789abcdef-0123456789abcdef",
)],
);
}
#[test]
fn span_kind_parse() {
for (case, expected) in [
("internal", Some(SpanKind::Internal)),
(" internal ", Some(SpanKind::Internal)),
("server", Some(SpanKind::Server)),
("client", Some(SpanKind::Client)),
("producer", Some(SpanKind::Producer)),
("consumer", Some(SpanKind::Consumer)),
("", None),
("int", None),
("internalx", None),
] {
assert_eq!(expected, SpanKind::try_from_str(case).ok());
}
}
#[test]
fn span_kind_roundtrip() {
for case in [
SpanKind::Internal,
SpanKind::Server,
SpanKind::Client,
SpanKind::Producer,
SpanKind::Consumer,
] {
assert_eq!(case, SpanKind::try_from_str(&case.to_string()).unwrap());
}
}
#[cfg(feature = "sval")]
#[test]
fn span_kind_stream() {
sval_test::assert_tokens(
&SpanKind::Internal,
&[
sval_test::Token::TextBegin(Some(8)),
sval_test::Token::TextFragment("internal"),
sval_test::Token::TextEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn span_kind_serialize() {
serde_test::assert_ser_tokens(&SpanKind::Internal, &[serde_test::Token::Str("internal")]);
}
#[test]
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
fn span_ctxt_new() {
let rng = crate::platform::DefaultRng::new();
let ctxt = crate::platform::DefaultCtxt::new();
let root = SpanCtxt::current(&ctxt);
assert_eq!(SpanCtxt::empty(), root);
let root = SpanCtxt::new_root(&rng);
assert!(root.span_id.is_some());
assert!(root.trace_id.is_some());
assert!(root.span_parent.is_none());
let mut frame = ctxt.open_push(root);
ctxt.enter(&mut frame);
let current = SpanCtxt::current(&ctxt);
assert_eq!(root, current);
let root = current;
let child = SpanCtxt::new_child(&root, &rng);
assert_eq!(root.trace_id, child.trace_id);
assert_ne!(root.span_id, child.span_id);
assert!(child.span_id.is_some());
assert_eq!(root.span_id, child.span_parent);
ctxt.exit(&mut frame);
ctxt.close(frame);
}
#[test]
fn span_new() {
let span = Span::new(
Path::new_raw("test"),
"my span",
Timestamp::from_unix(Duration::from_secs(1)),
("span_prop", true),
);
assert_eq!("test", span.mdl());
assert_eq!(
Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
span.extent().unwrap().as_point()
);
assert_eq!("my span", span.name());
assert_eq!(true, span.props().pull::<bool, _>("span_prop").unwrap());
}
#[test]
fn span_to_event() {
let span = Span::new(
Path::new_raw("test"),
"my span",
Timestamp::from_unix(Duration::from_secs(1)),
("span_prop", true),
);
let evt = span.to_event();
assert_eq!("test", evt.mdl());
assert_eq!(
Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
evt.extent().unwrap().as_point()
);
assert_eq!("my span completed", evt.msg().to_string());
assert_eq!(
"my span",
evt.props().pull::<Str, _>(KEY_SPAN_NAME).unwrap()
);
assert_eq!(true, evt.props().pull::<bool, _>("span_prop").unwrap());
assert_eq!(
Kind::Span,
evt.props().pull::<Kind, _>(KEY_EVT_KIND).unwrap()
);
}
#[test]
fn span_to_event_uses_tpl() {
assert_eq!(
"test",
Span::new(
Path::new_raw("test"),
"my span",
Timestamp::from_unix(Duration::from_secs(1)),
("span_prop", true),
)
.with_tpl(Template::literal("test"))
.to_event()
.msg()
.to_string(),
);
}
#[test]
fn span_to_extent() {
for (case, expected) in [
(
Some(Timestamp::from_unix(Duration::from_secs(1)).unwrap()),
Some(Extent::point(
Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
)),
),
(None, None),
] {
let span = Span::new(Path::new_raw("test"), "my span", case, ("span_prop", true));
let extent = span.to_extent();
assert_eq!(
expected.map(|extent| extent.as_range().cloned()),
extent.map(|extent| extent.as_range().cloned())
);
}
}
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
struct MyClock(Cell<u64>);
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
impl Clock for MyClock {
fn now(&self) -> Option<crate::Timestamp> {
let ts = crate::Timestamp::from_unix(Duration::from_secs(self.0.get()));
self.0.set(self.0.get() + 1);
ts
}
}
#[test]
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
fn active_span_new() {
let clock = MyClock(Cell::new(0));
let rng = crate::platform::DefaultRng::new();
let ctxt = crate::platform::DefaultCtxt::new();
let complete_called = Cell::new(false);
let (mut guard, frame) = SpanGuard::new(
filter::from_fn(|evt| {
assert_eq!(2, evt.props().pull::<usize, _>("ctxt_prop").unwrap());
assert!(evt.props().get("trace_id").is_some());
assert!(evt.props().get("span_id").is_some());
true
}),
&ctxt,
&clock,
&rng,
completion::from_fn(|evt| {
assert_eq!(
Timestamp::from_unix(Duration::from_secs(0)).unwrap(),
evt.extent().unwrap().as_range().unwrap().start
);
assert_eq!(
Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
evt.extent().unwrap().as_range().unwrap().end
);
assert_eq!("test", evt.mdl());
assert_eq!("span", evt.name());
assert_eq!(1, evt.props().pull::<usize, _>("event_prop").unwrap());
ctxt.with_current(|props| {
assert_eq!(2, props.pull::<usize, _>("ctxt_prop").unwrap());
});
let current_ctxt = SpanCtxt::current(&ctxt);
assert_ne!(current_ctxt, SpanCtxt::empty());
complete_called.set(true);
}),
("ctxt_prop", 2),
Path::new_raw("test"),
"span",
("event_prop", 1),
);
assert!(guard.is_enabled());
frame.call(move || {
guard.start();
drop(guard);
});
assert!(complete_called.get());
}
#[test]
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
fn active_span_unstarted_complete() {
let clock = MyClock(Cell::new(0));
let rng = crate::platform::DefaultRng::new();
let ctxt = crate::platform::DefaultCtxt::new();
let complete_called = Cell::new(false);
let (guard, frame) = SpanGuard::new(
filter::from_fn(|_| true),
&ctxt,
&clock,
&rng,
completion::from_fn(|_| {}),
Empty,
Path::new_raw("test"),
"span",
Empty,
);
assert!(guard.is_enabled());
frame.call(move || {
drop(guard);
});
assert!(!complete_called.get());
}
#[test]
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
fn active_span_new_disabled() {
let rng = crate::platform::DefaultRng::new();
let clock = crate::platform::DefaultClock::new();
let ctxt = crate::platform::DefaultCtxt::new();
let complete_called = Cell::new(false);
let (mut guard, frame) = SpanGuard::new(
filter::from_fn(|_| false),
&ctxt,
&clock,
&rng,
completion::from_fn(|_| {
complete_called.set(true);
}),
Empty,
Path::new_raw("test"),
"span",
Empty,
);
assert!(!guard.is_enabled());
frame.call(move || {
guard.start();
drop(guard);
});
assert!(!complete_called.get());
}
#[test]
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
fn active_span_custom_complete() {
let ctxt = crate::platform::DefaultCtxt::new();
let clock = crate::platform::DefaultClock::new();
let rng = crate::platform::DefaultRng::new();
let custom_complete_called = Cell::new(false);
let default_complete_called = Cell::new(false);
let (mut guard, _) = SpanGuard::new(
filter::from_fn(|_| true),
&ctxt,
&clock,
&rng,
completion::from_fn(|_| {
default_complete_called.set(true);
}),
Empty,
Path::new_raw("test"),
"span",
Empty,
);
assert!(guard.is_enabled());
guard.start();
guard.complete_with(completion::from_fn(|_| {
custom_complete_called.set(true);
}));
assert!(!default_complete_called.get());
assert!(custom_complete_called.get());
}
#[test]
#[cfg(all(feature = "std", feature = "rand", not(miri)))]
fn active_span_with_props() {
let clock = MyClock(Cell::new(0));
let rng = crate::platform::DefaultRng::new();
let ctxt = crate::platform::DefaultCtxt::new();
let complete_called = Cell::new(false);
let (mut guard, frame) = SpanGuard::new(
filter::from_fn(|_| true),
&ctxt,
&clock,
&rng,
completion::from_fn(|evt| {
assert_eq!(2, evt.props().pull::<usize, _>("event_prop").unwrap());
complete_called.set(true);
}),
Empty,
Path::new_raw("test"),
"span",
("event_prop", 1),
);
frame.call(move || {
guard.start();
let guard = guard.with_props(("event_prop", 2));
drop(guard);
});
assert!(complete_called.get());
}
}