| 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 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)] |
| 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 | |