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}