From 3d4f4dc5c78523026b17e4e5386de9b7a9d3d3a2 Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Thu, 5 Nov 2020 16:42:20 -0800 Subject: [PATCH 1/2] Add support for converting errors wrapped by TracedError (#1055) ## Motivation Right now TracedError is pretty much incompatible with enum heavy error handling implementations. The way its currently designed you'd have to make sure that each leaf error independently is wrapped in a `TracedError` so that all sources end up capturing a SpanTrace. ## Solution To resolve this I've added a `map` method for transforming the inner type of a TracedError while preserving the existing SpanTrace. This is still a bit cumbersome, where before you'd be able to write `result.map_err(|source| MyError { source, some_context })` now you'll have to write `result.map_err(|source| source.map(|source| MyError { source, some_context }))`. To fix this we should probably eventually add an extension trait on `Result` but I want to experiment with that half of the change in `zebra` before committing to an API, since it can easily be done out of tree. Co-authored-by: Eliza Weisman --- examples/examples/map-traced-error.rs | 82 +++++++++++++++++++ tracing-error/src/error.rs | 110 ++++++++++++++++++++++++-- 2 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 examples/examples/map-traced-error.rs diff --git a/examples/examples/map-traced-error.rs b/examples/examples/map-traced-error.rs new file mode 100644 index 0000000000..25bf779355 --- /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::*, ErrorSubscriber, TracedError}; +use tracing_subscriber::prelude::*; + +fn main() { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::subscriber()) + // The `ErrorSubscriber` subscriber enables the use of `SpanTrace`. + .with(ErrorSubscriber::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)] From ddd8a6191e648d7394d0b1c51248049049837f08 Mon Sep 17 00:00:00 2001 From: kalzoo <22137047+kalzoo@users.noreply.github.com> Date: Sun, 26 Feb 2023 11:51:02 -0800 Subject: [PATCH 2/2] tracing-error: revert ErrorSubscriber -> ErrorLayer in example --- examples/examples/map-traced-error.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/examples/map-traced-error.rs b/examples/examples/map-traced-error.rs index 25bf779355..a6c55861aa 100644 --- a/examples/examples/map-traced-error.rs +++ b/examples/examples/map-traced-error.rs @@ -5,14 +5,14 @@ #![allow(clippy::try_err)] use std::error::Error; use std::fmt; -use tracing_error::{prelude::*, ErrorSubscriber, TracedError}; +use tracing_error::{prelude::*, ErrorLayer, TracedError}; use tracing_subscriber::prelude::*; fn main() { tracing_subscriber::registry() - .with(tracing_subscriber::fmt::subscriber()) - // The `ErrorSubscriber` subscriber enables the use of `SpanTrace`. - .with(ErrorSubscriber::default()) + .with(tracing_subscriber::fmt::layer()) + // The `ErrorLayer` subscriber enables the use of `SpanTrace`. + .with(ErrorLayer::default()) .init(); let e = do_something().unwrap_err();