🛈 Note: This is pre-release documentation for the upcoming tracing 0.2.0 ecosystem.

For the release documentation, please see docs.rs, instead.

tracing_error/
backtrace.rs

1use crate::subscriber::WithContext;
2use std::fmt;
3use tracing::{Metadata, Span};
4
5/// A captured trace of [`tracing`] spans.
6///
7/// This type can be thought of as a relative of
8/// [`std::backtrace::Backtrace`][`Backtrace`].
9/// However, rather than capturing the current call stack when it is
10/// constructed, a `SpanTrace` instead captures the current [span] and its
11/// [parents].
12///
13/// In many cases, span traces may be as useful as stack backtraces useful in
14/// pinpointing where an error occurred and why, if not moreso:
15///
16/// * A span trace captures only the user-defined, human-readable `tracing`
17///   spans, rather than _every_ frame in the call stack, often cutting out a
18///   lot of noise.
19/// * Span traces include the [fields] recorded by each span in the trace, as
20///   well as their names and source code location, so different invocations of
21///   a function can be distinguished,
22/// * In asynchronous code, backtraces for errors that occur in [futures] often
23///   consist not of the stack frames that _spawned_ a future, but the stack
24///   frames of the executor that is responsible for running that future. This
25///   means that if an `async fn` calls another `async fn` which generates an
26///   error, the calling async function will not appear in the stack trace (and
27///   often, the callee won't either!). On the other hand, when the
28///   [`tracing-futures`] crate is used to instrument async code, the span trace
29///   will represent the logical application context a future was running in,
30///   rather than the stack trace of the executor that was polling a future when
31///   an error occurred.
32///
33/// Finally, unlike stack [`Backtrace`]s, capturing a `SpanTrace` is fairly
34/// lightweight, and the resulting struct is not large. The `SpanTrace` struct
35/// is formatted lazily; instead, it simply stores a copy of the current span,
36/// and allows visiting the spans in that span's trace tree by calling the
37/// [`with_spans` method][`with_spans`].
38///
39/// # Formatting
40///
41/// The `SpanTrace` type implements `fmt::Display`, formatting the span trace
42/// similarly to how Rust formats panics. For example:
43///
44/// ```text
45///    0: custom_error::do_another_thing
46///         with answer=42 will_succeed=false
47///           at examples/examples/custom_error.rs:42
48///    1: custom_error::do_something
49///         with foo="hello world"
50///           at examples/examples/custom_error.rs:37
51/// ```
52///
53/// Additionally, if custom formatting is desired, the [`with_spans`] method can
54/// be used to visit each span in the trace, formatting them in order.
55///
56/// [`Backtrace`]: std::backtrace::Backtrace
57/// [span]: mod@tracing::span
58/// [parents]: mod@tracing::span#span-relationships
59/// [fields]: tracing::field
60/// [futures]: std::future::Future
61/// [`tracing-futures`]: https://docs.rs/tracing-futures/
62/// [`with_spans`]: SpanTrace::with_spans()
63#[derive(Clone)]
64pub struct SpanTrace {
65    span: Span,
66}
67
68// === impl SpanTrace ===
69
70impl SpanTrace {
71    /// Create a new span trace with the given span as the innermost span.
72    pub fn new(span: Span) -> Self {
73        SpanTrace { span }
74    }
75
76    /// Capture the current span trace.
77    ///
78    /// # Examples
79    /// ```rust
80    /// use tracing_error::SpanTrace;
81    ///
82    /// pub struct MyError {
83    ///     span_trace: SpanTrace,
84    ///     // ...
85    /// }
86    ///
87    /// # fn some_error_condition() -> bool { true }
88    ///
89    /// #[tracing::instrument]
90    /// pub fn my_function(arg: &str) -> Result<(), MyError> {
91    ///     if some_error_condition() {
92    ///         return Err(MyError {
93    ///             span_trace: SpanTrace::capture(),
94    ///             // ...
95    ///         });
96    ///     }
97    ///
98    ///     // ...
99    /// #   Ok(())
100    /// }
101    /// ```
102    pub fn capture() -> Self {
103        SpanTrace::new(Span::current())
104    }
105
106    /// Apply a function to all captured spans in the trace until it returns
107    /// `false`.
108    ///
109    /// This will call the provided function with a reference to the
110    /// [`Metadata`] and a formatted representation of the [fields] of each span
111    /// captured in the trace, starting with the span that was current when the
112    /// trace was captured. The function may return `true` or `false` to
113    /// indicate whether to continue iterating over spans; if it returns
114    /// `false`, no additional spans will be visited.
115    ///
116    /// [fields]: tracing::field
117    /// [`Metadata`]: tracing::Metadata
118    pub fn with_spans(&self, f: impl FnMut(&'static Metadata<'static>, &str) -> bool) {
119        self.span.with_collector(|(id, s)| {
120            if let Some(getcx) = s.downcast_ref::<WithContext>() {
121                getcx.with_context(s, id, f);
122            }
123        });
124    }
125
126    /// Returns the status of this `SpanTrace`.
127    ///
128    /// The status indicates one of the following:
129    /// * the current collector does not support capturing `SpanTrace`s
130    /// * there was no current span, so a trace was not captured
131    /// * a span trace was successfully captured
132    pub fn status(&self) -> SpanTraceStatus {
133        let inner = if self.span.is_none() {
134            SpanTraceStatusInner::Empty
135        } else {
136            let mut status = None;
137            self.span.with_collector(|(_, s)| {
138                if s.downcast_ref::<WithContext>().is_some() {
139                    status = Some(SpanTraceStatusInner::Captured);
140                }
141            });
142
143            status.unwrap_or(SpanTraceStatusInner::Unsupported)
144        };
145
146        SpanTraceStatus(inner)
147    }
148}
149
150/// The current status of a SpanTrace, indicating whether it was captured or
151/// whether it is empty for some other reason.
152#[derive(Debug, PartialEq, Eq)]
153pub struct SpanTraceStatus(SpanTraceStatusInner);
154
155impl SpanTraceStatus {
156    /// Formatting a SpanTrace is not supported, likely because there is no
157    /// ErrorSubscriber or the ErrorSubscriber is from a different version of
158    /// tracing_error
159    pub const UNSUPPORTED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Unsupported);
160
161    /// The SpanTrace is empty, likely because it was captured outside of any
162    /// `span`s
163    pub const EMPTY: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Empty);
164
165    /// A span trace has been captured and the `SpanTrace` should print
166    /// reasonable information when rendered.
167    pub const CAPTURED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Captured);
168}
169
170#[derive(Debug, PartialEq, Eq)]
171enum SpanTraceStatusInner {
172    Unsupported,
173    Empty,
174    Captured,
175}
176
177macro_rules! try_bool {
178    ($e:expr, $dest:ident) => {{
179        let ret = $e.unwrap_or_else(|e| $dest = Err(e));
180
181        if $dest.is_err() {
182            return false;
183        }
184
185        ret
186    }};
187}
188
189impl fmt::Display for SpanTrace {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        let mut err = Ok(());
192        let mut span = 0;
193
194        self.with_spans(|metadata, fields| {
195            if span > 0 {
196                try_bool!(write!(f, "\n",), err);
197            }
198
199            try_bool!(
200                write!(f, "{:>4}: {}::{}", span, metadata.target(), metadata.name()),
201                err
202            );
203
204            if !fields.is_empty() {
205                try_bool!(write!(f, "\n           with {}", fields), err);
206            }
207
208            if let Some((file, line)) = metadata
209                .file()
210                .and_then(|file| metadata.line().map(|line| (file, line)))
211            {
212                try_bool!(write!(f, "\n             at {}:{}", file, line), err);
213            }
214
215            span += 1;
216            true
217        });
218
219        err
220    }
221}
222
223impl fmt::Debug for SpanTrace {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        struct DebugSpan<'a> {
226            metadata: &'a Metadata<'a>,
227            fields: &'a str,
228        }
229
230        impl fmt::Debug for DebugSpan<'_> {
231            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232                write!(
233                    f,
234                    "{{ target: {:?}, name: {:?}",
235                    self.metadata.target(),
236                    self.metadata.name()
237                )?;
238
239                if !self.fields.is_empty() {
240                    write!(f, ", fields: {:?}", self.fields)?;
241                }
242
243                if let Some((file, line)) = self
244                    .metadata
245                    .file()
246                    .and_then(|file| self.metadata.line().map(|line| (file, line)))
247                {
248                    write!(f, ", file: {:?}, line: {:?}", file, line)?;
249                }
250
251                write!(f, " }}")?;
252
253                Ok(())
254            }
255        }
256
257        write!(f, "SpanTrace ")?;
258        let mut dbg = f.debug_list();
259        self.with_spans(|metadata, fields| {
260            dbg.entry(&DebugSpan { metadata, fields });
261            true
262        });
263        dbg.finish()
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270    use crate::ErrorSubscriber;
271    use tracing::collect::with_default;
272    use tracing::{span, Level};
273    use tracing_subscriber::{prelude::*, registry::Registry};
274
275    #[test]
276    fn capture_supported() {
277        let collector = Registry::default().with(ErrorSubscriber::default());
278
279        with_default(collector, || {
280            let span = span!(Level::ERROR, "test span");
281            let _guard = span.enter();
282
283            let span_trace = SpanTrace::capture();
284
285            dbg!(&span_trace);
286
287            assert_eq!(SpanTraceStatus::CAPTURED, span_trace.status())
288        });
289    }
290
291    #[test]
292    fn capture_empty() {
293        let collector = Registry::default().with(ErrorSubscriber::default());
294
295        with_default(collector, || {
296            let span_trace = SpanTrace::capture();
297
298            dbg!(&span_trace);
299
300            assert_eq!(SpanTraceStatus::EMPTY, span_trace.status())
301        });
302    }
303
304    #[test]
305    fn capture_unsupported() {
306        let collector = Registry::default();
307
308        with_default(collector, || {
309            let span = span!(Level::ERROR, "test span");
310            let _guard = span.enter();
311
312            let span_trace = SpanTrace::capture();
313
314            dbg!(&span_trace);
315
316            assert_eq!(SpanTraceStatus::UNSUPPORTED, span_trace.status())
317        });
318    }
319}