| 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 | |