use std::{error::Error, fmt, fmt::Write};
use tracing::error;
pub trait ErrorExt {
fn display_chain(&self) -> String;
fn display_chain_with_msg<S: AsRef<str>>(&self, msg: S) -> String;
}
impl<E: Error> ErrorExt for E {
fn display_chain(&self) -> String {
let mut s = format!("Error: {self}");
let mut source = self.source();
while let Some(error) = source {
if let Err(err) = write!(&mut s, "\nCaused by: {error}") {
error!("error formatting failure: {err}");
}
source = error.source();
}
s
}
fn display_chain_with_msg<S: AsRef<str>>(&self, msg: S) -> String {
let mut s = format!("Error: {}\nCaused by: {}", msg.as_ref(), self);
let mut source = self.source();
while let Some(error) = source {
if let Err(err) = write!(&mut s, "\nCaused by: {error}") {
error!("error formatting failure: {err}");
}
source = error.source();
}
s
}
}
#[macro_export]
macro_rules! trace_err_chain {
($err:expr) => {
tracing::error!("{}", $crate::ErrorExt::display_chain(&$err));
};
($err:expr, $($args:tt)*) => {
tracing::error!("{}", $crate::ErrorExt::display_chain_with_msg(&$err, ::std::format!($($args)*)));
};
}
#[cfg(test)]
mod tests {
use tracing_test::traced_test;
use std::{io, path::PathBuf};
#[test]
#[traced_test]
fn test_trace_err_chain() {
trace_err_chain!(io::Error::other("file not found"));
assert!(logs_contain("Error: file not found"));
}
#[test]
#[traced_test]
fn test_trace_err_chain_with_msg() {
trace_err_chain!(io::Error::other("file not found"), "failed to open file");
assert!(logs_contain("Error: failed to open file"));
}
#[test]
#[traced_test]
fn test_trace_err_chain_with_msgfmt() {
trace_err_chain!(
io::Error::other("file not found"),
"failed to open file: {}",
PathBuf::from("test.txt").display()
);
assert!(logs_contain("Error: failed to open file: test.txt"));
}
}
#[derive(Debug)]
pub struct BoxedError(Box<dyn Error + 'static + Send>);
impl fmt::Display for BoxedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Error for BoxedError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.0.source()
}
}
impl BoxedError {
pub fn new(error: impl Error + 'static + Send) -> Self {
BoxedError(Box::new(error))
}
}