🛈 Note: This is pre-release documentation for the upcoming tracing 0.2.0 ecosystem.

For the release documentation, please see docs.rs, instead.

tracing_error/
error.rs

1use crate::SpanTrace;
2use std::error::Error;
3use std::fmt::{self, Debug, Display};
4
5struct Erased;
6
7/// A wrapper type for `Error`s that bundles a `SpanTrace` with an inner `Error`
8/// type.
9///
10/// This type is a good match for the error-kind pattern where you have an error
11/// type with an inner enum of error variants and you would like to capture a
12/// span trace that can be extracted during printing without formatting the span
13/// trace as part of your display impl.
14///
15/// An example of implementing an error type for a library using `TracedError`
16/// might look like this
17///
18/// ```rust,compile_fail
19/// #[derive(Debug, thiserror::Error)]
20/// enum Kind {
21///     // ...
22/// }
23///
24/// #[derive(Debug)]
25/// pub struct Error {
26///     source: TracedError<Kind>,
27///     backtrace: Backtrace,
28/// }
29///
30/// impl std::error::Error for Error {
31///     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32///         self.source.source()
33///     }
34///
35///     fn backtrace(&self) -> Option<&Backtrace> {
36///         Some(&self.backtrace)
37///     }
38/// }
39///
40/// impl fmt::Display for Error {
41///     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
42///         fmt::Display::fmt(&self.source, fmt)
43///     }
44/// }
45///
46/// impl<E> From<E> for Error
47/// where
48///     Kind: From<E>,
49/// {
50///     fn from(source: E) -> Self {
51///         Self {
52///             source: Kind::from(source).into(),
53///             backtrace: Backtrace::capture(),
54///         }
55///     }
56/// }
57/// ```
58#[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))]
59pub struct TracedError<E> {
60    inner: ErrorImpl<E>,
61}
62
63impl<E> TracedError<E>
64where
65    E: std::error::Error + Send + Sync + 'static,
66{
67    /// Construct a TracedError given both the SpanTrace and the inner Error
68    pub fn new(error: E, span_trace: SpanTrace) -> Self {
69        // # SAFETY
70        //
71        // This function + the repr(C) on the ErrorImpl make the type erasure throughout the rest
72        // of this struct's methods safe. This saves a function pointer that is parameterized on the Error type
73        // being stored inside the ErrorImpl. This lets the object_ref function safely cast a type
74        // erased `ErrorImpl` back to its original type, which is needed in order to forward our
75        // error/display/debug impls to the internal error type from the type erased error type.
76        //
77        // The repr(C) is necessary to ensure that the struct is laid out in the order we
78        // specified it, so that we can safely access the vtable and spantrace fields through a type
79        // erased pointer to the original object.
80        let vtable = &ErrorVTable {
81            object_ref: object_ref::<E>,
82        };
83
84        TracedError {
85            inner: ErrorImpl {
86                vtable,
87                span_trace,
88                error,
89            },
90        }
91    }
92
93    /// Convert the inner error type of a `TracedError` while preserving the
94    /// attached `SpanTrace`.
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use tracing_error::TracedError;
100    /// # #[derive(Debug)]
101    /// # struct InnerError;
102    /// # #[derive(Debug)]
103    /// # struct OuterError(InnerError);
104    /// # impl std::fmt::Display for InnerError {
105    /// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106    /// #         write!(f, "Inner Error")
107    /// #     }
108    /// # }
109    /// # impl std::error::Error for InnerError {
110    /// # }
111    /// # impl std::fmt::Display for OuterError {
112    /// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113    /// #         write!(f, "Outer Error")
114    /// #     }
115    /// # }
116    /// # impl std::error::Error for OuterError {
117    /// #     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118    /// #         Some(&self.0)
119    /// #     }
120    /// # }
121    ///
122    /// let err: TracedError<InnerError> = InnerError.into();
123    /// let err: TracedError<OuterError> = err.map(|inner| OuterError(inner));
124    /// ```
125    pub fn map<F, O>(self, op: O) -> TracedError<F>
126    where
127        O: FnOnce(E) -> F,
128        F: std::error::Error + Send + Sync + 'static,
129    {
130        let span_trace = self.inner.span_trace;
131        let error = self.inner.error;
132        let error = op(error);
133
134        TracedError::new(error, span_trace)
135    }
136
137    /// Convert the inner error type of a `TracedError` using the inner error's `Into`
138    /// implementation, while preserving the attached `SpanTrace`.
139    ///
140    /// # Examples
141    ///
142    /// ```rust
143    /// use tracing_error::TracedError;
144    /// # #[derive(Debug)]
145    /// # struct InnerError;
146    /// # #[derive(Debug)]
147    /// # struct OuterError(InnerError);
148    /// # impl std::fmt::Display for InnerError {
149    /// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150    /// #         write!(f, "Inner Error")
151    /// #     }
152    /// # }
153    /// # impl std::error::Error for InnerError {
154    /// # }
155    /// # impl std::fmt::Display for OuterError {
156    /// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157    /// #         write!(f, "Outer Error")
158    /// #     }
159    /// # }
160    /// # impl std::error::Error for OuterError {
161    /// #     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
162    /// #         Some(&self.0)
163    /// #     }
164    /// # }
165    ///
166    /// impl From<InnerError> for OuterError {
167    ///     fn from(inner: InnerError) -> Self {
168    ///         Self(inner)
169    ///     }
170    /// }
171    ///
172    /// let err: TracedError<InnerError> = InnerError.into();
173    /// let err: TracedError<OuterError> = err.err_into();
174    /// ```
175    pub fn err_into<F>(self) -> TracedError<F>
176    where
177        E: Into<F>,
178        F: std::error::Error + Send + Sync + 'static,
179    {
180        self.map(Into::into)
181    }
182}
183
184impl<E> From<E> for TracedError<E>
185where
186    E: Error + Send + Sync + 'static,
187{
188    fn from(error: E) -> Self {
189        TracedError::new(error, SpanTrace::capture())
190    }
191}
192
193#[repr(C)]
194struct ErrorImpl<E> {
195    vtable: &'static ErrorVTable,
196    span_trace: SpanTrace,
197    // NOTE: Don't use directly. Use only through vtable. Erased type may have
198    // different alignment.
199    error: E,
200}
201
202impl ErrorImpl<Erased> {
203    pub(crate) fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
204        // # SAFETY
205        //
206        // this function is used to cast a type-erased pointer to a pointer to error's
207        // original type. the `ErrorImpl::error` method, which calls this function, requires that
208        // the type this function casts to be the original erased type of the error; failure to
209        // uphold this is UB. since the `From` impl is parameterized over the original error type,
210        // the function pointer we construct here will also retain the original type. therefore,
211        // when this is consumed by the `error` method, it will be safe to call.
212        unsafe { (self.vtable.object_ref)(self) }
213    }
214}
215
216struct ErrorVTable {
217    object_ref: unsafe fn(&ErrorImpl<Erased>) -> &(dyn Error + Send + Sync + 'static),
218}
219
220// # SAFETY
221//
222// This function must be parameterized on the type E of the original error that is being stored
223// inside of the `ErrorImpl`. When it is parameterized by the correct type, it safely
224// casts the erased `ErrorImpl` pointer type back to the original pointer type.
225unsafe fn object_ref<E>(e: &ErrorImpl<Erased>) -> &(dyn Error + Send + Sync + 'static)
226where
227    E: Error + Send + Sync + 'static,
228{
229    // Attach E's native Error vtable onto a pointer to e.error.
230    &(*(e as *const ErrorImpl<Erased> as *const ErrorImpl<E>)).error
231}
232
233impl<E> Error for TracedError<E>
234where
235    E: std::error::Error + 'static,
236{
237    // # SAFETY
238    //
239    // This function is safe so long as all functions on `ErrorImpl<Erased>` uphold the invariant
240    // that the wrapped error is only ever accessed by the `error` method. This method uses the
241    // function in the vtable to safely convert the pointer type back to the original type, and
242    // then returns the reference to the erased error.
243    //
244    // This function is necessary for the `downcast_ref` in `ExtractSpanTrace` to work, because it
245    // needs a concrete type to downcast to and we cannot downcast to ErrorImpls parameterized on
246    // errors defined in other crates. By erasing the type here we can always cast back to the
247    // Erased version of the ErrorImpl pointer, and still access the internal error type safely
248    // through the vtable.
249    fn source<'a>(&'a self) -> Option<&'a (dyn Error + 'static)> {
250        let erased = unsafe { &*(&self.inner as *const ErrorImpl<E> as *const ErrorImpl<Erased>) };
251        Some(erased)
252    }
253}
254
255impl<E> Debug for TracedError<E>
256where
257    E: std::error::Error,
258{
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        Debug::fmt(&self.inner.error, f)
261    }
262}
263
264impl<E> Display for TracedError<E>
265where
266    E: std::error::Error,
267{
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        Display::fmt(&self.inner.error, f)
270    }
271}
272
273impl Error for ErrorImpl<Erased> {
274    fn source(&self) -> Option<&(dyn Error + 'static)> {
275        self.error().source()
276    }
277}
278
279impl Debug for ErrorImpl<Erased> {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        f.pad("span backtrace:\n")?;
282        Debug::fmt(&self.span_trace, f)
283    }
284}
285
286impl Display for ErrorImpl<Erased> {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        f.pad("span backtrace:\n")?;
289        Display::fmt(&self.span_trace, f)
290    }
291}
292
293/// Extension trait for instrumenting errors with `SpanTrace`s
294#[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))]
295pub trait InstrumentError {
296    /// The type of the wrapped error after instrumentation
297    type Instrumented;
298
299    /// Instrument an Error by bundling it with a SpanTrace
300    ///
301    /// # Examples
302    ///
303    /// ```rust
304    /// use tracing_error::{TracedError, InstrumentError};
305    ///
306    /// fn wrap_error<E>(e: E) -> TracedError<E>
307    /// where
308    ///     E: std::error::Error + Send + Sync + 'static
309    /// {
310    ///     e.in_current_span()
311    /// }
312    /// ```
313    fn in_current_span(self) -> Self::Instrumented;
314}
315
316/// Extension trait for instrumenting errors in `Result`s with `SpanTrace`s
317#[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))]
318pub trait InstrumentResult<T> {
319    /// The type of the wrapped error after instrumentation
320    type Instrumented;
321
322    /// Instrument an Error by bundling it with a SpanTrace
323    ///
324    /// # Examples
325    ///
326    /// ```rust
327    /// # use std::{io, fs};
328    /// use tracing_error::{TracedError, InstrumentResult};
329    ///
330    /// # fn fallible_fn() -> io::Result<()> { fs::read_dir("......").map(drop) };
331    ///
332    /// fn do_thing() -> Result<(), TracedError<io::Error>> {
333    ///     fallible_fn().in_current_span()
334    /// }
335    /// ```
336    fn in_current_span(self) -> Result<T, Self::Instrumented>;
337}
338
339impl<T, E> InstrumentResult<T> for Result<T, E>
340where
341    E: InstrumentError,
342{
343    type Instrumented = <E as InstrumentError>::Instrumented;
344
345    fn in_current_span(self) -> Result<T, Self::Instrumented> {
346        self.map_err(E::in_current_span)
347    }
348}
349
350/// A trait for extracting SpanTraces created by `in_current_span()` from `dyn
351/// Error` trait objects
352#[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))]
353pub trait ExtractSpanTrace {
354    /// Attempts to downcast to a `TracedError` and return a reference to its
355    /// SpanTrace
356    ///
357    /// # Examples
358    ///
359    /// ```rust
360    /// use tracing_error::ExtractSpanTrace;
361    /// use std::error::Error;
362    ///
363    /// fn print_span_trace(e: &(dyn Error + 'static)) {
364    ///     let span_trace = e.span_trace();
365    ///     if let Some(span_trace) = span_trace {
366    ///         println!("{}", span_trace);
367    ///     }
368    /// }
369    /// ```
370    fn span_trace(&self) -> Option<&SpanTrace>;
371}
372
373impl<E> InstrumentError for E
374where
375    TracedError<E>: From<E>,
376{
377    type Instrumented = TracedError<E>;
378
379    fn in_current_span(self) -> Self::Instrumented {
380        TracedError::from(self)
381    }
382}
383
384impl ExtractSpanTrace for dyn Error + 'static {
385    fn span_trace(&self) -> Option<&SpanTrace> {
386        self.downcast_ref::<ErrorImpl<Erased>>()
387            .map(|inner| &inner.span_trace)
388    }
389}