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 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)]
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: &ErrorImpl = 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: &ErrorImpl| &inner.span_trace)
288 }
289}
290