1use std::{error::Error, fmt, fmt::Write};
6use tracing::error;
7
8pub trait ErrorExt {
10 fn display_chain(&self) -> String;
12
13 fn display_chain_with_msg<S: AsRef<str>>(&self, msg: S) -> String;
15}
16
17impl<E: Error> ErrorExt for E {
18 fn display_chain(&self) -> String {
19 let mut s = format!("Error: {self}");
20 let mut source = self.source();
21 while let Some(error) = source {
22 if let Err(err) = write!(&mut s, "\nCaused by: {error}") {
23 error!("error formatting failure: {err}");
24 }
25 source = error.source();
26 }
27 s
28 }
29
30 fn display_chain_with_msg<S: AsRef<str>>(&self, msg: S) -> String {
31 let mut s = format!("Error: {}\nCaused by: {}", msg.as_ref(), self);
32 let mut source = self.source();
33 while let Some(error) = source {
34 if let Err(err) = write!(&mut s, "\nCaused by: {error}") {
35 error!("error formatting failure: {err}");
36 }
37 source = error.source();
38 }
39 s
40 }
41}
42
43#[macro_export]
44macro_rules! trace_err_chain {
45 ($err:expr) => {
46 tracing::error!("{}", $crate::ErrorExt::display_chain(&$err));
47 };
48 ($err:expr, $($args:tt)*) => {
49 tracing::error!("{}", $crate::ErrorExt::display_chain_with_msg(&$err, ::std::format!($($args)*)));
50 };
51}
52
53#[cfg(test)]
54mod tests {
55 use tracing_test::traced_test;
56
57 use std::{io, path::PathBuf};
58
59 #[test]
60 #[traced_test]
61 fn test_trace_err_chain() {
62 trace_err_chain!(io::Error::other("file not found"));
63 assert!(logs_contain("Error: file not found"));
64 }
65
66 #[test]
67 #[traced_test]
68 fn test_trace_err_chain_with_msg() {
69 trace_err_chain!(io::Error::other("file not found"), "failed to open file");
70 assert!(logs_contain("Error: failed to open file"));
71 }
75
76 #[test]
77 #[traced_test]
78 fn test_trace_err_chain_with_msgfmt() {
79 trace_err_chain!(
80 io::Error::other("file not found"),
81 "failed to open file: {}",
82 PathBuf::from("test.txt").display()
83 );
84 assert!(logs_contain("Error: failed to open file: test.txt"));
85 }
89}
90#[derive(Debug)]
91pub struct BoxedError(Box<dyn Error + 'static + Send>);
92
93impl fmt::Display for BoxedError {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 self.0.fmt(f)
96 }
97}
98
99impl Error for BoxedError {
100 fn source(&self) -> Option<&(dyn Error + 'static)> {
101 self.0.source()
102 }
103}
104
105impl BoxedError {
106 pub fn new(error: impl Error + 'static + Send) -> Self {
107 BoxedError(Box::new(error))
108 }
109}