tempo_eyre/
lib.rs

1//! Hooks to format eyre reports with their source chain attached.
2//!
3//! The intended use of this error hook is within tracing events, specifically
4//! those generated by the `#[tracing::instrument(err)]` proc macro. This is
5//! because error events emitted this way are printed using their
6//! `std::fmt::Display` formatting, while `#[instrument(err(Debug))]` would
7//! print the full source chain but without respecting the formatting desired
8//! by the tracing subscriber.
9//!
10//! Because errors without their source chain are nigh useless, this crate
11//! provides the `tempo_eyre::install()` hook to install an error handler that
12//! formats errors in a list style like `[error 0, error 1, error 2]`.
13//!
14//! # Example
15//!
16//! ```rust
17//! # use eyre::{eyre, WrapErr as _};
18//! tempo_eyre::install();
19//!
20//! let err = eyre!("bottom error")
21//!     .wrap_err("middle error")
22//!     .wrap_err("top error");
23//! println!("full source chain: {err}");
24//! ```
25//! This would print:
26//! ```text
27//! full source chain: [top error, middle error, bottom error]
28//! ```
29
30/// Installs the hook as the global error report hook.
31///
32/// **NOTE**: It must be called before any `eyre::Report`s are constructed
33/// to prevent the default handler from being installed.
34///
35/// # Errors
36///
37/// Calling this function after another handler has been installed will cause
38/// an error.
39pub fn install() -> eyre::Result<()> {
40    eyre::set_hook(Box::new(|_| Box::new(ErrorHandler)))?;
41    Ok(())
42}
43
44struct ErrorHandler;
45
46impl eyre::EyreHandler for ErrorHandler {
47    /// Copied directly from [`eyre::DefaultHandler`] because we can't construct
48    /// and hence delegate to it.
49    fn debug(
50        &self,
51        error: &(dyn std::error::Error + 'static),
52        f: &mut core::fmt::Formatter<'_>,
53    ) -> core::fmt::Result {
54        use core::fmt::Write as _;
55
56        if f.alternate() {
57            return core::fmt::Debug::fmt(error, f);
58        }
59
60        write!(f, "{error}")?;
61
62        if let Some(cause) = error.source() {
63            write!(f, "\n\nCaused by:")?;
64            let multiple = cause.source().is_some();
65            for (n, error) in eyre::Chain::new(cause).enumerate() {
66                writeln!(f)?;
67                if multiple {
68                    write!(indenter::indented(f).ind(n), "{error}")?;
69                } else {
70                    write!(indenter::indented(f), "{error}")?;
71                }
72            }
73        }
74        Result::Ok(())
75    }
76
77    fn display(
78        &self,
79        error: &(dyn std::error::Error + 'static),
80        f: &mut core::fmt::Formatter<'_>,
81    ) -> core::fmt::Result {
82        let mut list = f.debug_list();
83        let mut curr = Some(error);
84        while let Some(curr_err) = curr {
85            list.entry(&format_args!("{curr_err}"));
86            curr = curr_err.source();
87        }
88        list.finish()?;
89        Ok(())
90    }
91}