logerr/
lib.rs

1mod error;
2
3/// Helper trait to easily log error types.
4///
5/// The `print_error` function takes a closure which takes a `&str` and fares with it as necessary
6/// to log the error to some usable location. For convenience, logging to stdout, stderr and
7/// `log::error!` is already implemented.
8///
9/// Note that the trait functions pass the error through unmodified, so they can be chained with
10/// the usual handling of [`std::result::Result`] types.
11///
12// # Developer notes
13//
14// We can dump the different impls once [#31844](https://github.com/rust-lang/rust/issues/31844)
15// is stabilized.
16pub trait LoggableError<T>: Sized {
17    /// Gives a formatted error message derived from `self` to the closure `fun` for
18    /// printing/logging as appropriate.
19    ///
20    /// # Examples
21    ///
22    /// ```ignore
23    /// use anyhow;
24    /// use crate::LoggableError;
25    ///
26    /// let my_err: anyhow::Result<&str> = Err(anyhow::anyhow!("Test error"));
27    /// my_err
28    ///     .print_error(|msg| println!("{msg}"))
29    ///     .unwrap();
30    /// ```
31    #[track_caller]
32    fn print_error<F: Fn(&str)>(self, fun: F) -> Self;
33
34    /// Convenienve function, calls `print_error` and logs the result as error.
35    ///
36    /// *Note*: Requires the `log` feature.
37    ///
38    /// This is not a wrapper around `log::error!`, because the `log` crate uses a lot of compile
39    /// time macros from `std` to determine caller locations/module names etc. Since these are
40    /// resolved at compile time in the location they are written, they would always resolve to the
41    /// location in this function where `log::error!` is called, masking the real caller location.
42    /// Hence, we build the log message ourselves. This means that we lose the information about
43    /// the calling module (Because it can only be resolved at compile time), however the callers
44    /// file and line number are preserved.
45    #[cfg(not(feature = "log"))]
46    fn to_log(self) -> Self;
47    #[cfg(feature = "log")]
48    #[track_caller]
49    fn to_log(self) -> Self {
50        let caller = std::panic::Location::caller();
51        self.print_error(|msg| {
52            // Build the log entry manually
53            // NOTE: The log entry has no module path associated with it. This is because `log`
54            // gets the module path from the `std::module_path!()` macro, which is replaced at
55            // compile time in the location it is written!
56            // This is necessary until https://github.com/rust-lang/rust/issues/87417 lands
57            log::logger().log(
58                &log::Record::builder()
59                    .level(log::Level::Error)
60                    .args(format_args!("{}", msg))
61                    .file(Some(caller.file()))
62                    .line(Some(caller.line()))
63                    .module_path(None)
64                    .build(),
65            );
66        })
67    }
68
69    /// Convenienve function, calls `print_error` with the closure `|msg| eprintln!("{}", msg)`.
70    fn to_stderr(self) -> Self {
71        self.print_error(|msg| eprintln!("{}", msg))
72    }
73
74    /// Convenienve function, calls `print_error` with the closure `|msg| println!("{}", msg)`.
75    fn to_stdout(self) -> Self {
76        self.print_error(|msg| println!("{}", msg))
77    }
78}