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