• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::SpanTrace;
2 use std::error::Error;
3 use std::fmt::{self, Debug, Display};
4 
5 struct 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")))]
59 pub struct TracedError<E> {
60     inner: ErrorImpl<E>,
61 }
62 
63 impl<E> From<E> for TracedError<E>
64 where
65     E: Error + Send + Sync + 'static,
66 {
from(error: E) -> Self67     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 layed out in the order we
77         // specified it so that we can safely access the vtable and spantrace fields thru 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)]
94 struct 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 
102 impl ErrorImpl<Erased> {
error(&self) -> &(dyn Error + Send + Sync + 'static)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 
116 struct 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.
object_ref<E>(e: &ErrorImpl<Erased>) -> &(dyn Error + Send + Sync + 'static) where E: Error + Send + Sync + 'static,125 unsafe fn object_ref<E>(e: &ErrorImpl<Erased>) -> &(dyn Error + Send + Sync + 'static)
126 where
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 
133 impl<E> Error for TracedError<E>
134 where
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.
source<'a>(&'a self) -> Option<&'a (dyn Error + 'static)>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 
155 impl<E> Debug for TracedError<E>
156 where
157     E: std::error::Error,
158 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result159     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160         Debug::fmt(&self.inner.error, f)
161     }
162 }
163 
164 impl<E> Display for TracedError<E>
165 where
166     E: std::error::Error,
167 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result168     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169         Display::fmt(&self.inner.error, f)
170     }
171 }
172 
173 impl Error for ErrorImpl<Erased> {
source(&self) -> Option<&(dyn Error + 'static)>174     fn source(&self) -> Option<&(dyn Error + 'static)> {
175         self.error().source()
176     }
177 }
178 
179 impl Debug for ErrorImpl<Erased> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result180     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 
186 impl Display for ErrorImpl<Erased> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result187     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")))]
195 pub 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     /// ```
in_current_span(self) -> Self::Instrumented213     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")))]
218 pub 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     /// ```
in_current_span(self) -> Result<T, Self::Instrumented>236     fn in_current_span(self) -> Result<T, Self::Instrumented>;
237 }
238 
239 impl<T, E> InstrumentResult<T> for Result<T, E>
240 where
241     E: InstrumentError,
242 {
243     type Instrumented = <E as InstrumentError>::Instrumented;
244 
in_current_span(self) -> Result<T, Self::Instrumented>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")))]
253 pub 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     /// ```
span_trace(&self) -> Option<&SpanTrace>270     fn span_trace(&self) -> Option<&SpanTrace>;
271 }
272 
273 impl<E> InstrumentError for E
274 where
275     TracedError<E>: From<E>,
276 {
277     type Instrumented = TracedError<E>;
278 
in_current_span(self) -> Self::Instrumented279     fn in_current_span(self) -> Self::Instrumented {
280         TracedError::from(self)
281     }
282 }
283 
284 impl ExtractSpanTrace for dyn Error + 'static {
span_trace(&self) -> Option<&SpanTrace>285     fn span_trace(&self) -> Option<&SpanTrace> {
286         self.downcast_ref::<ErrorImpl<Erased>>()
287             .map(|inner| &inner.span_trace)
288     }
289 }
290