Even nicer logging: Add ok_or_log() and more (#2284)

Co-authored-by: Floris Bruynooghe <flub@devork.be>
This commit is contained in:
Hocuri
2021-03-13 21:06:37 +01:00
committed by GitHub
parent 4ab90f7069
commit 98fc559536
5 changed files with 90 additions and 47 deletions

View File

@@ -676,7 +676,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
.sql
.set_raw_config_int(context, "backup_time", now as i32)
.await?;
sql::housekeeping(context).await.log(context);
sql::housekeeping(context).await.ok_or_log(context);
context
.sql

View File

@@ -1157,7 +1157,7 @@ async fn perform_job_action(
Action::MoveMsg => job.move_msg(context, connection.inbox()).await,
Action::FetchExistingMsgs => job.fetch_existing_msgs(context, connection.inbox()).await,
Action::Housekeeping => {
sql::housekeeping(context).await.log(context);
sql::housekeeping(context).await.ok_or_log(context);
Status::Finished(Ok(()))
}
};

View File

@@ -1,5 +1,4 @@
//! # Logging
use crate::context::Context;
#[macro_export]
@@ -61,38 +60,94 @@ macro_rules! emit_event {
};
}
pub trait LogExt<T> {
pub trait LogExt<T, E>
where
Self: std::marker::Sized,
{
#[track_caller]
fn log_err_inner(self, context: &Context, msg: Option<&str>) -> Result<T, E>;
/// Emits a warning if the receiver contains an Err value.
///
/// Returns an [`Option<T>`] with the `Ok(_)` value, if any:
/// - You won't get any warnings about unused results but can still use the value if you need it
/// - This prevents the same warning from being printed to the log multiple times
///
/// Thanks to the [track_caller](https://blog.rust-lang.org/2020/08/27/Rust-1.46.0.html#track_caller)
/// feature, the location of the caller is printed to the log, just like with the warn!() macro.
///
/// Unfortunately, the track_caller feature does not work on async functions (as of Rust 1.50).
/// Once it is, you can add `#[track_caller]` to helper functions that use one of the log helpers here
/// so that the location of the caller can be seen in the log. (this won't work with the macros,
/// like warn!(), since the file!() and line!() macros don't work with track_caller)
/// See https://github.com/rust-lang/rust/issues/78840 for progress on this.
#[track_caller]
fn log(self, context: &Context) -> Option<T>;
}
fn log_err(self, context: &Context, msg: &str) -> Result<T, E> {
self.log_err_inner(context, Some(msg))
}
impl<T> LogExt<T> for anyhow::Result<T> {
/// Emits a warning if the receiver contains an Err value and returns an [`Option<T>`].
///
/// Example:
/// ```text
/// if let Err(e) = do_something() {
/// warn!(context, "{:#}", e);
/// }
/// ```
/// is equivalent to:
/// ```text
/// do_something().ok_or_log(context);
/// ```
///
/// For a note on the `track_caller` feature, see the doc comment on `log_err()`.
#[track_caller]
fn log(self, context: &Context) -> Option<T> {
match self {
Err(e) => {
let location = std::panic::Location::caller();
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
let full = format!(
"{file}:{line}: {e:#}",
file = location.file(),
line = location.line(),
e = e
);
// We can't use the warn!() macro here as the file!() and line!() macros
// don't work well with #[track_caller]
emit_event!(context, crate::EventType::Warning(full));
None
}
Ok(v) => Some(v),
}
fn ok_or_log(self, context: &Context) -> Option<T> {
self.log_err_inner(context, None).ok()
}
/// Like `ok_or_log()`, but you can pass an extra message that is prepended in the log.
///
/// Example:
/// ```text
/// if let Err(e) = do_something() {
/// warn!(context, "Something went wrong: {:#}", e);
/// }
/// ```
/// is equivalent to:
/// ```text
/// do_something().ok_or_log_msg(context, "Something went wrong");
/// ```
/// and is also equivalent to:
/// ```text
/// use anyhow::Context as _;
/// do_something().context("Something went wrong").ok_or_log(context);
/// ```
///
/// For a note on the `track_caller` feature, see the doc comment on `log_err()`.
#[track_caller]
fn ok_or_log_msg(self, context: &Context, msg: &'static str) -> Option<T> {
self.log_err_inner(context, Some(msg)).ok()
}
}
impl<T: Default, E: std::fmt::Display> LogExt<T, E> for Result<T, E> {
#[track_caller]
fn log_err_inner(self, context: &Context, msg: Option<&str>) -> Result<T, E> {
if let Err(e) = &self {
let location = std::panic::Location::caller();
let separator = if msg.is_none() { "" } else { ": " };
let msg = msg.unwrap_or_default();
// We are using Anyhow's .context() and to show the inner error, too, we need the {:#}:
let full = format!(
"{file}:{line}: {msg}{separator}{e:#}",
file = location.file(),
line = location.line(),
msg = msg,
separator = separator,
e = e
);
// We can't use the warn!() macro here as the file!() and line!() macros
// don't work with #[track_caller]
emit_event!(context, crate::EventType::Warning(full));
};
self
}
}