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 | /// [`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)] |
64 | pub struct SpanTrace { |
65 | span: Span, |
66 | } |
67 | |
68 | // === impl SpanTrace === |
69 | |
70 | impl 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_subscriber(|(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 subscriber 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_subscriber(|(_, 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)] |
153 | pub struct SpanTraceStatus(SpanTraceStatusInner); |
154 | |
155 | impl SpanTraceStatus { |
156 | /// Formatting a SpanTrace is not supported, likely because there is no |
157 | /// ErrorLayer or the ErrorLayer 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)] |
171 | enum SpanTraceStatusInner { |
172 | Unsupported, |
173 | Empty, |
174 | Captured, |
175 | } |
176 | |
177 | macro_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 | |
189 | impl 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 | |
223 | impl 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)] |
268 | mod tests { |
269 | use super::*; |
270 | use crate::ErrorLayer; |
271 | use tracing::subscriber::with_default; |
272 | use tracing::{span, Level}; |
273 | use tracing_subscriber::{prelude::*, registry::Registry}; |
274 | |
275 | #[test ] |
276 | fn capture_supported() { |
277 | let subscriber = Registry::default().with(ErrorLayer::default()); |
278 | |
279 | with_default(subscriber, || { |
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 subscriber = Registry::default().with(ErrorLayer::default()); |
294 | |
295 | with_default(subscriber, || { |
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 subscriber = Registry::default(); |
307 | |
308 | with_default(subscriber, || { |
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 | } |
320 | |