1 | use crate::layer::WithContext; |
2 | use std::fmt; |
3 | use 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)] |
65 | pub struct SpanTrace { |
66 | span: Span, |
67 | } |
68 | |
69 | // === impl SpanTrace === |
70 | |
71 | impl 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)] |
154 | pub struct SpanTraceStatus(SpanTraceStatusInner); |
155 | |
156 | impl 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)] |
172 | enum SpanTraceStatusInner { |
173 | Unsupported, |
174 | Empty, |
175 | Captured, |
176 | } |
177 | |
178 | macro_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 | |
190 | impl 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 | |
224 | impl 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)] |
269 | mod 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 | |