use std::fmt::{Debug, Display, Write as _}; use backtrace::Backtrace; #[macro_export] macro_rules! error { ($lit: literal) => { $crate::error::Error::from($lit) }; ($lit: literal, $($tt: tt)*) => { $crate::error::Error::from(std::format!($lit, $($tt)*)) }; ($err: expr) => { $crate::error::Error::from($err) }; } pub struct Error { inner: Box, backtrace: Backtrace, } impl Debug for Error { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let error = &*self.inner; write!(formatter, "{error}")?; if let Some(cause) = error.source() { write!(formatter, "\n\nCaused by:")?; let multiple = cause.source().is_some(); for (n, error) in anyhow::Chain::new(cause).enumerate() { writeln!(formatter)?; let mut indented = Indented { inner: formatter, number: if multiple { Some(n) } else { None }, started: false, }; write!(indented, "{error}")?; } } write!( formatter, "\n\nStack backtrace:\n{:?}", std::fmt::from_fn(|backtrace_formatter| fmt_backtrace( &self.backtrace, backtrace_formatter )) )?; Ok(()) } } impl Display for Error { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let error = &*self.inner; write!(formatter, "{error}")?; if formatter.alternate() { let chain = anyhow::Chain::new(error); for cause in chain.skip(1) { write!(formatter, ": {}", cause)?; } } Ok(()) } } impl From for Error where Box: From, { fn from(err: Err) -> Self { Self { inner: err.into(), backtrace: Backtrace::new(), } } } pub type ErrorHandler = fn(Error, Metadata); /// Error metadata. #[derive(Debug)] pub struct Metadata { pub source_name: &'static str, pub source_kind: SourceKind, } /// Error source kind. #[derive(Debug)] #[non_exhaustive] pub enum SourceKind { System, Observer, } impl Display for SourceKind { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SourceKind::System => formatter.write_str("system"), SourceKind::Observer => formatter.write_str("observer"), } } } pub fn err_handler_panic(err: Error, err_metadata: Metadata) { panic!( "Error occurred in {} '{}': {err:?}", err_metadata.source_kind, err_metadata.source_name ); } pub fn err_handler_log_error(err: Error, err_metadata: Metadata) { tracing::error!( "Error occurred in {} '{}': {err:#}", err_metadata.source_kind, err_metadata.source_name ); } fn fmt_backtrace( backtrace: &Backtrace, fmt: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { let style = if fmt.alternate() { backtrace::PrintFmt::Full } else { backtrace::PrintFmt::Short }; // When printing paths we try to strip the cwd if it exists, otherwise // we just print the path as-is. Note that we also only do this for the // short format, because if it's full we presumably want to print // everything. let cwd = std::env::current_dir(); let mut print_path = move |fmt: &mut std::fmt::Formatter<'_>, path: backtrace::BytesOrWideString<'_>| { let path = path.into_path_buf(); if style != backtrace::PrintFmt::Full { if let Ok(cwd) = &cwd { if let Ok(suffix) = path.strip_prefix(cwd) { return std::fmt::Display::fmt(&suffix.display(), fmt); } } } std::fmt::Display::fmt(&path.display(), fmt) }; let mut f = backtrace::BacktraceFmt::new(fmt, style, &mut print_path); f.add_context()?; for frame in backtrace.frames() { if frame.symbols().iter().all(|symbol| { symbol.name().is_some_and(|symbol_name| { let symbol_name = symbol_name.to_string(); symbol_name .contains(">::from") }) }) { continue; } f.frame().backtrace_frame(frame)?; } f.finish()?; Ok(()) } struct Indented<'a, D> { inner: &'a mut D, number: Option, started: bool, } impl std::fmt::Write for Indented<'_, T> where T: std::fmt::Write, { fn write_str(&mut self, s: &str) -> std::fmt::Result { for (i, line) in s.split('\n').enumerate() { if !self.started { self.started = true; match self.number { Some(number) => write!(self.inner, "{: >5}: ", number)?, None => self.inner.write_str(" ")?, } } else if i > 0 { self.inner.write_char('\n')?; if self.number.is_some() { self.inner.write_str(" ")?; } else { self.inner.write_str(" ")?; } } self.inner.write_str(line)?; } Ok(()) } }