1use crate::layer::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/// [`tracing`]: https://docs.rs/tracing
57/// [`Backtrace`]: https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
58/// [span]: https://docs.rs/tracing/latest/tracing/span/index.html
59/// [parents]: https://docs.rs/tracing/latest/tracing/span/index.html#span-relationships
60/// [fields]: https://docs.rs/tracing/latest/tracing/field/index.html
61/// [futures]: https://doc.rust-lang.org/std/future/trait.Future.html
62/// [`tracing-futures`]: https://docs.rs/tracing-futures/
63/// [`with_spans`]: #method.with_spans
64#[derive(Clone)]
65pub struct SpanTrace {
66 span: Span,
67}
68
69// === impl SpanTrace ===
70
71impl SpanTrace {
72 /// Create a new span trace with the given span as the innermost span.
73 pub fn new(span: Span) -> Self {
74 SpanTrace { span }
75 }
76
77 /// Capture the current span trace.
78 ///
79 /// # Examples
80 /// ```rust
81 /// use tracing_error::SpanTrace;
82 ///
83 /// pub struct MyError {
84 /// span_trace: SpanTrace,
85 /// // ...
86 /// }
87 ///
88 /// # fn some_error_condition() -> bool { true }
89 ///
90 /// #[tracing::instrument]
91 /// pub fn my_function(arg: &str) -> Result<(), MyError> {
92 /// if some_error_condition() {
93 /// return Err(MyError {
94 /// span_trace: SpanTrace::capture(),
95 /// // ...
96 /// });
97 /// }
98 ///
99 /// // ...
100 /// # Ok(())
101 /// }
102 /// ```
103 pub fn capture() -> Self {
104 SpanTrace::new(Span::current())
105 }
106
107 /// Apply a function to all captured spans in the trace until it returns
108 /// `false`.
109 ///
110 /// This will call the provided function with a reference to the
111 /// [`Metadata`] and a formatted representation of the [fields] of each span
112 /// captured in the trace, starting with the span that was current when the
113 /// trace was captured. The function may return `true` or `false` to
114 /// indicate whether to continue iterating over spans; if it returns
115 /// `false`, no additional spans will be visited.
116 ///
117 /// [fields]: https://docs.rs/tracing/latest/tracing/field/index.html
118 /// [`Metadata`]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html
119 pub fn with_spans(&self, f: impl FnMut(&'static Metadata<'static>, &str) -> bool) {
120 self.span.with_subscriber(|(id, s)| {
121 if let Some(getcx) = s.downcast_ref::<WithContext>() {
122 getcx.with_context(s, id, f);
123 }
124 });
125 }
126
127 /// Returns the status of this `SpanTrace`.
128 ///
129 /// The status indicates one of the following:
130 /// * the current subscriber does not support capturing `SpanTrace`s
131 /// * there was no current span, so a trace was not captured
132 /// * a span trace was successfully captured
133 pub fn status(&self) -> SpanTraceStatus {
134 let inner = if self.span.is_none() {
135 SpanTraceStatusInner::Empty
136 } else {
137 let mut status = None;
138 self.span.with_subscriber(|(_, s)| {
139 if s.downcast_ref::<WithContext>().is_some() {
140 status = Some(SpanTraceStatusInner::Captured);
141 }
142 });
143
144 status.unwrap_or(SpanTraceStatusInner::Unsupported)
145 };
146
147 SpanTraceStatus(inner)
148 }
149}
150
151/// The current status of a SpanTrace, indicating whether it was captured or
152/// whether it is empty for some other reason.
153#[derive(Debug, PartialEq, Eq)]
154pub struct SpanTraceStatus(SpanTraceStatusInner);
155
156impl SpanTraceStatus {
157 /// Formatting a SpanTrace is not supported, likely because there is no
158 /// ErrorLayer or the ErrorLayer is from a different version of
159 /// tracing_error
160 pub const UNSUPPORTED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Unsupported);
161
162 /// The SpanTrace is empty, likely because it was captured outside of any
163 /// `span`s
164 pub const EMPTY: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Empty);
165
166 /// A span trace has been captured and the `SpanTrace` should print
167 /// reasonable information when rendered.
168 pub const CAPTURED: SpanTraceStatus = SpanTraceStatus(SpanTraceStatusInner::Captured);
169}
170
171#[derive(Debug, PartialEq, Eq)]
172enum SpanTraceStatusInner {
173 Unsupported,
174 Empty,
175 Captured,
176}
177
178macro_rules! try_bool {
179 ($e:expr, $dest:ident) => {{
180 let ret = $e.unwrap_or_else(|e| $dest = Err(e));
181
182 if $dest.is_err() {
183 return false;
184 }
185
186 ret
187 }};
188}
189
190impl fmt::Display for SpanTrace {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 let mut err = Ok(());
193 let mut span = 0;
194
195 self.with_spans(|metadata, fields| {
196 if span > 0 {
197 try_bool!(write!(f, "\n",), err);
198 }
199
200 try_bool!(
201 write!(f, "{:>4}: {}::{}", span, metadata.target(), metadata.name()),
202 err
203 );
204
205 if !fields.is_empty() {
206 try_bool!(write!(f, "\n with {}", fields), err);
207 }
208
209 if let Some((file, line)) = metadata
210 .file()
211 .and_then(|file| metadata.line().map(|line| (file, line)))
212 {
213 try_bool!(write!(f, "\n at {}:{}", file, line), err);
214 }
215
216 span += 1;
217 true
218 });
219
220 err
221 }
222}
223
224impl fmt::Debug for SpanTrace {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 struct DebugSpan<'a> {
227 metadata: &'a Metadata<'a>,
228 fields: &'a str,
229 }
230
231 impl<'a> fmt::Debug for DebugSpan<'a> {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 write!(
234 f,
235 "{{ target: {:?}, name: {:?}",
236 self.metadata.target(),
237 self.metadata.name()
238 )?;
239
240 if !self.fields.is_empty() {
241 write!(f, ", fields: {:?}", self.fields)?;
242 }
243
244 if let Some((file, line)) = self
245 .metadata
246 .file()
247 .and_then(|file| self.metadata.line().map(|line| (file, line)))
248 {
249 write!(f, ", file: {:?}, line: {:?}", file, line)?;
250 }
251
252 write!(f, " }}")?;
253
254 Ok(())
255 }
256 }
257
258 write!(f, "SpanTrace ")?;
259 let mut dbg = f.debug_list();
260 self.with_spans(|metadata, fields| {
261 dbg.entry(&DebugSpan { metadata, fields });
262 true
263 });
264 dbg.finish()
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::ErrorLayer;
272 use tracing::subscriber::with_default;
273 use tracing::{span, Level};
274 use tracing_subscriber::{prelude::*, registry::Registry};
275
276 #[test]
277 fn capture_supported() {
278 let subscriber = Registry::default().with(ErrorLayer::default());
279
280 with_default(subscriber, || {
281 let span = span!(Level::ERROR, "test span");
282 let _guard = span.enter();
283
284 let span_trace = SpanTrace::capture();
285
286 dbg!(&span_trace);
287
288 assert_eq!(SpanTraceStatus::CAPTURED, span_trace.status())
289 });
290 }
291
292 #[test]
293 fn capture_empty() {
294 let subscriber = Registry::default().with(ErrorLayer::default());
295
296 with_default(subscriber, || {
297 let span_trace = SpanTrace::capture();
298
299 dbg!(&span_trace);
300
301 assert_eq!(SpanTraceStatus::EMPTY, span_trace.status())
302 });
303 }
304
305 #[test]
306 fn capture_unsupported() {
307 let subscriber = Registry::default();
308
309 with_default(subscriber, || {
310 let span = span!(Level::ERROR, "test span");
311 let _guard = span.enter();
312
313 let span_trace = SpanTrace::capture();
314
315 dbg!(&span_trace);
316
317 assert_eq!(SpanTraceStatus::UNSUPPORTED, span_trace.status())
318 });
319 }
320}
321