eyre-rs / color-eyre

Custom hooks for colorful human oriented error reports via panics and the eyre crate
Other
960 stars 56 forks source link

Add setters for backtrace and spantrace #128

Closed stormshield-kg closed 1 month ago

stormshield-kg commented 1 year ago

This allows us to keep previously captured backtrace and spantrace and store them inside a new color_eyre::Handler.

Usage example when using a custom enum error but keeping the capture of the backtrace and spantrace :

Code ```rust use std::panic::Location; use backtrace::Backtrace; use color_eyre::{eyre, Handler, Report}; use thiserror::Error; use tracing_error::SpanTrace; #[derive(Debug)] pub struct ErrorImpl { /// Original error pub inner: Report, /// Deepest location pub location: &'static Location<'static>, /// Deepest backtrace pub backtrace: Option, /// Deepest spantrace pub span_trace: Option, } impl ErrorImpl { #[track_caller] pub fn new(mut inner: Report) -> Self { let mut backtrace = None; let mut span_trace = None; if let Some(handler) = inner.handler_mut().downcast_ref::() { backtrace = handler.backtrace().cloned(); span_trace = handler.span_trace().cloned(); } Self { inner, location: Location::caller(), backtrace, span_trace, } } pub fn from_parts( location: &'static Location<'static>, backtrace: Option, span_trace: Option, mut inner: Report, ) -> Self { // Merge location, backtrace and spantrace inner.handler_mut().track_caller(location); if let Some(handler) = inner.handler_mut().downcast_mut::() { if let Some(backtrace) = &backtrace { handler.set_backtrace(backtrace.clone()); } if let Some(span_trace) = &span_trace { handler.set_span_trace(span_trace.clone()); } } Self { inner, location, backtrace, span_trace, } } } macro_rules! define_error_wrapper { ($struct_name:ident, $inner_type:ty $(,)?) => { define_error_wrapper!($struct_name, $inner_type, from()); }; ($struct_name:ident, $inner_type:ty, from($($from_type:ty),*)) => { #[derive(Debug)] pub struct $struct_name(Box); impl $struct_name { pub fn location(&self) -> &'static std::panic::Location<'static> { self.0.location } pub fn take_backtrace(&mut self) -> Option { self.0.backtrace.take() } pub fn take_span_trace(&mut self) -> Option { self.0.span_trace.take() } pub fn downcast(self) -> $inner_type { self.0.inner.downcast().expect("expected inner error type") } } impl> From for $struct_name { #[track_caller] fn from(error: T) -> Self { // Use `eyre::Report::from` which has the `#[track_caller]` attribute let inner = eyre::Report::from(error.into()); Self(Box::new(ErrorImpl::new(inner))) } } impl $inner_type { // Define inherent `into()` method with the `#[track_caller]` attribute #[track_caller] pub fn into(self) -> $struct_name { $struct_name::from(self) } } $( // Define `From` impl between two wrapped errors impl From<$from_type> for $struct_name { fn from(mut error: $from_type) -> Self { Self(Box::new(ErrorImpl::from_parts( error.location(), error.take_backtrace(), error.take_span_trace(), eyre::Report::from(<$inner_type>::from(error.downcast())), ))) } } )* impl From<$struct_name> for eyre::Report { // Extract inner `eyre::Report` fn from(error: $struct_name) -> Self { error.0.inner } } }; } #[derive(Debug, Error)] pub enum InnerConfigError { #[error("invalid filename")] InvalidFilename, } define_error_wrapper!(ConfigError, InnerConfigError); #[derive(Debug, Error)] pub enum InnerError { #[error("configuration error")] Config(#[from] InnerConfigError), } define_error_wrapper!(Error, InnerError, from(ConfigError)); // // Fonctions directly returning enum errors // #[tracing::instrument("config1")] fn config1() -> Result<(), InnerConfigError> { Err(InnerConfigError::InvalidFilename) } #[tracing::instrument("func1")] fn func1() -> Result<(), InnerError> { config1()?; Ok(()) } fn main1() -> eyre::Result<()> { func1()?; Ok(()) } // // Fonctions returning error wrapper types // #[tracing::instrument("config2")] fn config2() -> Result<(), ConfigError> { Err(InnerConfigError::InvalidFilename.into()) } #[tracing::instrument("func2")] fn func2() -> Result<(), Error> { config2()?; Ok(()) } fn main2() -> eyre::Result<()> { func2()?; Ok(()) } // // Main // fn main() -> eyre::Result<()> { color_eyre::install()?; use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; let fmt_layer = fmt::layer().with_target(false); let filter_layer = EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new("info")) .unwrap(); tracing_subscriber::registry() .with(filter_layer) .with(fmt_layer) .with(ErrorLayer::default()) .init(); // we only have shallow spantrace and backtrace if let Err(e) = main1() { println!("main1 error:"); println!("{:?}\n\n\n", e); match e.downcast::().unwrap() { InnerError::Config(InnerConfigError::InvalidFilename) => {} } } // we have full spantrace and backtrace if let Err(e) = main2() { println!("main2 error:"); println!("{:?}\n\n\n", e); // we can still match the inner error match e.downcast::().unwrap() { InnerError::Config(InnerConfigError::InvalidFilename) => {} } } Ok(()) } ```