diff --git a/examples/examples/map-traced-error.rs b/examples/examples/map-traced-error.rs new file mode 100644 index 0000000000..a6c55861aa --- /dev/null +++ b/examples/examples/map-traced-error.rs @@ -0,0 +1,82 @@ +//! An example on composing errors inside of a `TracedError`, such that the +//! SpanTrace captured is captured when creating the inner error, but still wraps +//! the outer error. +#![deny(rust_2018_idioms)] +#![allow(clippy::try_err)] +use std::error::Error; +use std::fmt; +use tracing_error::{prelude::*, ErrorLayer, TracedError}; +use tracing_subscriber::prelude::*; + +fn main() { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + // The `ErrorLayer` subscriber enables the use of `SpanTrace`. + .with(ErrorLayer::default()) + .init(); + + let e = do_something().unwrap_err(); + print_extracted_spantraces(&e); +} + +// Iterate through the source errors and check if each error is actually the attached `SpanTrace` +// before printing it +fn print_extracted_spantraces(error: &(dyn Error + 'static)) { + let mut error = Some(error); + let mut ind = 0; + + while let Some(err) = error { + if let Some(spantrace) = err.span_trace() { + eprintln!("Span Backtrace:\n{}", spantrace); + } else { + eprintln!("Error {}: {}", ind, err); + } + + error = err.source(); + ind += 1; + } +} + +#[tracing::instrument] +fn do_something() -> Result<(), TracedError> { + do_the_real_stuff().map_err(TracedError::err_into) +} + +#[tracing::instrument] +fn do_the_real_stuff() -> Result<(), TracedError> { + Err(InnerError).map_err(TracedError::from) +} + +#[derive(Debug)] +struct OuterError { + source: InnerError, +} + +impl Error for OuterError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} + +impl fmt::Display for OuterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "outer error message") + } +} + +impl From for OuterError { + fn from(source: InnerError) -> Self { + Self { source } + } +} + +#[derive(Debug)] +struct InnerError; + +impl Error for InnerError {} + +impl fmt::Display for InnerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "inner error message") + } +} diff --git a/tracing-error/src/error.rs b/tracing-error/src/error.rs index 017d42ff10..8d25401a3e 100644 --- a/tracing-error/src/error.rs +++ b/tracing-error/src/error.rs @@ -60,11 +60,12 @@ pub struct TracedError { inner: ErrorImpl, } -impl From for TracedError +impl TracedError where - E: Error + Send + Sync + 'static, + E: std::error::Error + Send + Sync + 'static, { - fn from(error: E) -> Self { + /// Construct a TracedError given both the SpanTrace and the inner Error + pub fn new(error: E, span_trace: SpanTrace) -> Self { // # SAFETY // // This function + the repr(C) on the ErrorImpl make the type erasure throughout the rest @@ -80,14 +81,113 @@ where object_ref: object_ref::, }; - Self { + TracedError { inner: ErrorImpl { vtable, - span_trace: SpanTrace::capture(), + span_trace, error, }, } } + + /// Convert the inner error type of a `TracedError` while preserving the + /// attached `SpanTrace`. + /// + /// # Examples + /// + /// ```rust + /// use tracing_error::TracedError; + /// # #[derive(Debug)] + /// # struct InnerError; + /// # #[derive(Debug)] + /// # struct OuterError(InnerError); + /// # impl std::fmt::Display for InnerError { + /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// # write!(f, "Inner Error") + /// # } + /// # } + /// # impl std::error::Error for InnerError { + /// # } + /// # impl std::fmt::Display for OuterError { + /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// # write!(f, "Outer Error") + /// # } + /// # } + /// # impl std::error::Error for OuterError { + /// # fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// # Some(&self.0) + /// # } + /// # } + /// + /// let err: TracedError = InnerError.into(); + /// let err: TracedError = err.map(|inner| OuterError(inner)); + /// ``` + pub fn map(self, op: O) -> TracedError + where + O: FnOnce(E) -> F, + F: std::error::Error + Send + Sync + 'static, + { + let span_trace = self.inner.span_trace; + let error = self.inner.error; + let error = op(error); + + TracedError::new(error, span_trace) + } + + /// Convert the inner error type of a `TracedError` using the inner error's `Into` + /// implementation, while preserving the attached `SpanTrace`. + /// + /// # Examples + /// + /// ```rust + /// use tracing_error::TracedError; + /// # #[derive(Debug)] + /// # struct InnerError; + /// # #[derive(Debug)] + /// # struct OuterError(InnerError); + /// # impl std::fmt::Display for InnerError { + /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// # write!(f, "Inner Error") + /// # } + /// # } + /// # impl std::error::Error for InnerError { + /// # } + /// # impl std::fmt::Display for OuterError { + /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// # write!(f, "Outer Error") + /// # } + /// # } + /// # impl std::error::Error for OuterError { + /// # fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + /// # Some(&self.0) + /// # } + /// # } + /// + /// impl From for OuterError { + /// fn from(inner: InnerError) -> Self { + /// Self(inner) + /// } + /// } + /// + /// let err: TracedError = InnerError.into(); + /// let err: TracedError = err.err_into(); + /// ``` + pub fn err_into(self) -> TracedError + where + E: Into, + F: std::error::Error + Send + Sync + 'static, + { + self.map(Into::into) + } +} + +impl From for TracedError +where + E: Error + Send + Sync + 'static, +{ + fn from(error: E) -> Self { + TracedError::new(error, SpanTrace::capture()) + } } #[repr(C)]