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}