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> From<E> for TracedError<E>
64where
65    E: Error + Send + Sync + 'static,
66{
67    fn from(error: E) -> Self {
68        // # SAFETY
69        //
70        // This function + the repr(C) on the ErrorImpl make the type erasure throughout the rest
71        // of this struct's methods safe. This saves a function pointer that is parameterized on the Error type
72        // being stored inside the ErrorImpl. This lets the object_ref function safely cast a type
73        // erased `ErrorImpl` back to its original type, which is needed in order to forward our
74        // error/display/debug impls to the internal error type from the type erased error type.
75        //
76        // The repr(C) is necessary to ensure that the struct is laid out in the order we
77        // specified it, so that we can safely access the vtable and spantrace fields through a type
78        // erased pointer to the original object.
79        let vtable = &ErrorVTable {
80            object_ref: object_ref::<E>,
81        };
82
83        Self {
84            inner: ErrorImpl {
85                vtable,
86                span_trace: SpanTrace::capture(),
87                error,
88            },
89        }
90    }
91}
92
93#[repr(C)]
94struct ErrorImpl<E> {
95    vtable: &'static ErrorVTable,
96    span_trace: SpanTrace,
97    // NOTE: Don't use directly. Use only through vtable. Erased type may have
98    // different alignment.
99    error: E,
100}
101
102impl ErrorImpl<Erased> {
103    pub(crate) fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
104        // # SAFETY
105        //
106        // this function is used to cast a type-erased pointer to a pointer to error's
107        // original type. the `ErrorImpl::error` method, which calls this function, requires that
108        // the type this function casts to be the original erased type of the error; failure to
109        // uphold this is UB. since the `From` impl is parameterized over the original error type,
110        // the function pointer we construct here will also retain the original type. therefore,
111        // when this is consumed by the `error` method, it will be safe to call.
112        unsafe { (self.vtable.object_ref)(self) }
113    }
114}
115
116struct ErrorVTable {
117    object_ref: unsafe fn(&ErrorImpl<Erased>) -> &(dyn Error + Send + Sync + 'static),
118}
119
120// # SAFETY
121//
122// This function must be parameterized on the type E of the original error that is being stored
123// inside of the `ErrorImpl`. When it is parameterized by the correct type, it safely
124// casts the erased `ErrorImpl` pointer type back to the original pointer type.
125unsafe fn object_ref<E>(e: &ErrorImpl<Erased>) -> &(dyn Error + Send + Sync + 'static)
126where
127    E: Error + Send + Sync + 'static,
128{
129    // Attach E's native Error vtable onto a pointer to e.error.
130    &(*(e as *const ErrorImpl<Erased> as *const ErrorImpl<E>)).error
131}
132
133impl<E> Error for TracedError<E>
134where
135    E: std::error::Error + 'static,
136{
137    // # SAFETY
138    //
139    // This function is safe so long as all functions on `ErrorImpl<Erased>` uphold the invariant
140    // that the wrapped error is only ever accessed by the `error` method. This method uses the
141    // function in the vtable to safely convert the pointer type back to the original type, and
142    // then returns the reference to the erased error.
143    //
144    // This function is necessary for the `downcast_ref` in `ExtractSpanTrace` to work, because it
145    // needs a concrete type to downcast to and we cannot downcast to ErrorImpls parameterized on
146    // errors defined in other crates. By erasing the type here we can always cast back to the
147    // Erased version of the ErrorImpl pointer, and still access the internal error type safely
148    // through the vtable.
149    fn source<'a>(&'a self) -> Option<&'a (dyn Error + 'static)> {
150        let erased = unsafe { &*(&self.inner as *const ErrorImpl<E> as *const ErrorImpl<Erased>) };
151        Some(erased)
152    }
153}
154
155impl<E> Debug for TracedError<E>
156where
157    E: std::error::Error,
158{
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        Debug::fmt(&self.inner.error, f)
161    }
162}
163
164impl<E> Display for TracedError<E>
165where
166    E: std::error::Error,
167{
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        Display::fmt(&self.inner.error, f)
170    }
171}
172
173impl Error for ErrorImpl<Erased> {
174    fn source(&self) -> Option<&(dyn Error + 'static)> {
175        self.error().source()
176    }
177}
178
179impl Debug for ErrorImpl<Erased> {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        f.pad("span backtrace:\n")?;
182        Debug::fmt(&self.span_trace, f)
183    }
184}
185
186impl Display for ErrorImpl<Erased> {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.pad("span backtrace:\n")?;
189        Display::fmt(&self.span_trace, f)
190    }
191}
192
193/// Extension trait for instrumenting errors with `SpanTrace`s
194#[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))]
195pub trait InstrumentError {
196    /// The type of the wrapped error after instrumentation
197    type Instrumented;
198
199    /// Instrument an Error by bundling it with a SpanTrace
200    ///
201    /// # Examples
202    ///
203    /// ```rust
204    /// use tracing_error::{TracedError, InstrumentError};
205    ///
206    /// fn wrap_error<E>(e: E) -> TracedError<E>
207    /// where
208    ///     E: std::error::Error + Send + Sync + 'static
209    /// {
210    ///     e.in_current_span()
211    /// }
212    /// ```
213    fn in_current_span(self) -> Self::Instrumented;
214}
215
216/// Extension trait for instrumenting errors in `Result`s with `SpanTrace`s
217#[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))]
218pub trait InstrumentResult<T> {
219    /// The type of the wrapped error after instrumentation
220    type Instrumented;
221
222    /// Instrument an Error by bundling it with a SpanTrace
223    ///
224    /// # Examples
225    ///
226    /// ```rust
227    /// # use std::{io, fs};
228    /// use tracing_error::{TracedError, InstrumentResult};
229    ///
230    /// # fn fallible_fn() -> io::Result<()> { fs::read_dir("......").map(drop) };
231    ///
232    /// fn do_thing() -> Result<(), TracedError<io::Error>> {
233    ///     fallible_fn().in_current_span()
234    /// }
235    /// ```
236    fn in_current_span(self) -> Result<T, Self::Instrumented>;
237}
238
239impl<T, E> InstrumentResult<T> for Result<T, E>
240where
241    E: InstrumentError,
242{
243    type Instrumented = <E as InstrumentError>::Instrumented;
244
245    fn in_current_span(self) -> Result<T, Self::Instrumented> {
246        self.map_err(E::in_current_span)
247    }
248}
249
250/// A trait for extracting SpanTraces created by `in_current_span()` from `dyn
251/// Error` trait objects
252#[cfg_attr(docsrs, doc(cfg(feature = "traced-error")))]
253pub trait ExtractSpanTrace {
254    /// Attempts to downcast to a `TracedError` and return a reference to its
255    /// SpanTrace
256    ///
257    /// # Examples
258    ///
259    /// ```rust
260    /// use tracing_error::ExtractSpanTrace;
261    /// use std::error::Error;
262    ///
263    /// fn print_span_trace(e: &(dyn Error + 'static)) {
264    ///     let span_trace = e.span_trace();
265    ///     if let Some(span_trace) = span_trace {
266    ///         println!("{}", span_trace);
267    ///     }
268    /// }
269    /// ```
270    fn span_trace(&self) -> Option<&SpanTrace>;
271}
272
273impl<E> InstrumentError for E
274where
275    TracedError<E>: From<E>,
276{
277    type Instrumented = TracedError<E>;
278
279    fn in_current_span(self) -> Self::Instrumented {
280        TracedError::from(self)
281    }
282}
283
284impl ExtractSpanTrace for dyn Error + 'static {
285    fn span_trace(&self) -> Option<&SpanTrace> {
286        self.downcast_ref::<ErrorImpl<Erased>>()
287            .map(|inner| &inner.span_trace)
288    }
289}