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 | { |
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 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> { |
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. |
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. |
149 | fn source<'a>(&'a self) -> Option<&'a (dyn Error + 'static)> { |
150 | let erased: &ErrorImpl = 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 | { |
159 | 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 | { |
168 | 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> { |
174 | fn source(&self) -> Option<&(dyn Error + 'static)> { |
175 | self.error().source() |
176 | } |
177 | } |
178 | |
179 | impl 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 | |
186 | impl 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" )))] |
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 | /// ``` |
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" )))] |
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 | /// ``` |
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 | |
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 | /// ``` |
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 | |
279 | fn in_current_span(self) -> Self::Instrumented { |
280 | TracedError::from(self) |
281 | } |
282 | } |
283 | |
284 | impl ExtractSpanTrace for dyn Error + 'static { |
285 | fn span_trace(&self) -> Option<&SpanTrace> { |
286 | self.downcast_ref::<ErrorImpl<Erased>>() |
287 | .map(|inner: &ErrorImpl| &inner.span_trace) |
288 | } |
289 | } |
290 | |