Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions examples/examples/map-traced-error.rs
Original file line number Diff line number Diff line change
@@ -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<OuterError>> {
do_the_real_stuff().map_err(TracedError::err_into)
}

#[tracing::instrument]
fn do_the_real_stuff() -> Result<(), TracedError<InnerError>> {
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<InnerError> 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")
}
}
110 changes: 105 additions & 5 deletions tracing-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ pub struct TracedError<E> {
inner: ErrorImpl<E>,
}

impl<E> From<E> for TracedError<E>
impl<E> TracedError<E>
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
Expand All @@ -80,14 +81,113 @@ where
object_ref: object_ref::<E>,
};

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> = InnerError.into();
/// let err: TracedError<OuterError> = err.map(|inner| OuterError(inner));
/// ```
pub fn map<F, O>(self, op: O) -> TracedError<F>
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<InnerError> for OuterError {
/// fn from(inner: InnerError) -> Self {
/// Self(inner)
/// }
/// }
///
/// let err: TracedError<InnerError> = InnerError.into();
/// let err: TracedError<OuterError> = err.err_into();
/// ```
pub fn err_into<F>(self) -> TracedError<F>
where
E: Into<F>,
F: std::error::Error + Send + Sync + 'static,
{
self.map(Into::into)
}
}

impl<E> From<E> for TracedError<E>
where
E: Error + Send + Sync + 'static,
{
fn from(error: E) -> Self {
TracedError::new(error, SpanTrace::capture())
}
}

#[repr(C)]
Expand Down
Loading