use crate::layer::WithContext;
use std::fmt;
use tracing::{Metadata, Span};
#[derive(Clone)]
pub struct SpanTrace {
span: Span,
}
impl SpanTrace {
pub fn new(span: Span) -> Self {
SpanTrace { span }
}
pub fn capture() -> Self {
SpanTrace::new(Span::current())
}
pub fn with_spans(&self, f: impl FnMut(&'static Metadata<'static>, &str) -> bool) {
self.span.with_subscriber(|(id, s)| {
if let Some(getcx) = s.downcast_ref::<WithContext>() {
getcx.with_context(s, id, f);
}
});
}
pub fn status(&self) -> SpanTraceStatus {
let inner = if self.span.is_none() {
SpanTraceStatusInner::Empty
} else {
let mut status = None;
self.span.with_subscriber(|(_, s)| {
if s.downcast_ref::<WithContext>().is_some() {
status = Some(SpanTraceStatusInner::Captured);
}
});
status.unwrap_or(SpanTraceStatusInner::Unsupported)
};
SpanTraceStatus(inner)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct SpanTraceStatus(SpanTraceStatusInner);
impl SpanTraceStatus {
pub const UNSUPPORTED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Unsupported);
pub const EMPTY: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Empty);
pub const CAPTURED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Captured);
}
#[derive(Debug, PartialEq, Eq)]
enum SpanTraceStatusInner {
Unsupported,
Empty,
Captured,
}
macro_rules! try_bool {
($e:expr, $dest:ident) => {{
let ret = $e.unwrap_or_else(|e| $dest = Err(e));
if $dest.is_err() {
return false;
}
ret
}};
}
impl fmt::Display for SpanTrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut err = Ok(());
let mut span = 0;
self.with_spans(|metadata, fields| {
if span > 0 {
try_bool!(write!(f, "\n",), err);
}
try_bool!(
write!(f, "{:>4}: {}::{}", span, metadata.target(), metadata.name()),
err
);
if !fields.is_empty() {
try_bool!(write!(f, "\n with {}", fields), err);
}
if let Some((file, line)) = metadata
.file()
.and_then(|file| metadata.line().map(|line| (file, line)))
{
try_bool!(write!(f, "\n at {}:{}", file, line), err);
}
span += 1;
true
});
err
}
}
impl fmt::Debug for SpanTrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct DebugSpan<'a> {
metadata: &'a Metadata<'a>,
fields: &'a str,
}
impl fmt::Debug for DebugSpan<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{{ target: {:?}, name: {:?}",
self.metadata.target(),
self.metadata.name()
)?;
if !self.fields.is_empty() {
write!(f, ", fields: {:?}", self.fields)?;
}
if let Some((file, line)) = self
.metadata
.file()
.and_then(|file| self.metadata.line().map(|line| (file, line)))
{
write!(f, ", file: {:?}, line: {:?}", file, line)?;
}
write!(f, " }}")?;
Ok(())
}
}
write!(f, "SpanTrace ")?;
let mut dbg = f.debug_list();
self.with_spans(|metadata, fields| {
dbg.entry(&DebugSpan { metadata, fields });
true
});
dbg.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ErrorLayer;
use tracing::subscriber::with_default;
use tracing::{span, Level};
use tracing_subscriber::{prelude::*, registry::Registry};
#[test]
fn capture_supported() {
let subscriber = Registry::default().with(ErrorLayer::default());
with_default(subscriber, || {
let span = span!(Level::ERROR, "test span");
let _guard = span.enter();
let span_trace = SpanTrace::capture();
dbg!(&span_trace);
assert_eq!(SpanTraceStatus::CAPTURED, span_trace.status())
});
}
#[test]
fn capture_empty() {
let subscriber = Registry::default().with(ErrorLayer::default());
with_default(subscriber, || {
let span_trace = SpanTrace::capture();
dbg!(&span_trace);
assert_eq!(SpanTraceStatus::EMPTY, span_trace.status())
});
}
#[test]
fn capture_unsupported() {
let subscriber = Registry::default();
with_default(subscriber, || {
let span = span!(Level::ERROR, "test span");
let _guard = span.enter();
let span_trace = SpanTrace::capture();
dbg!(&span_trace);
assert_eq!(SpanTraceStatus::UNSUPPORTED, span_trace.status())
});
}
}