Skip to content

Commit

Permalink
Add ErrorRef wrapper (#327)
Browse files Browse the repository at this point in the history
This commit adds the `ErrorRef` wrapper type. It enables to log error by reference, without moving ownership. It means that it is the non-owning counterpart of `ErrorValue`.

Logging error references is an important use case for transparent logging middlewares. With this wrapper type, you can log the error and still pass it up the call stack.

Such a wrapper type could already easily be implemented in user applications, but having it as part of Slog should help ergonomics. This also enables further improvements by integrating it with the Slog macros.

Closes #288
  • Loading branch information
demurgos authored Jan 2, 2024
1 parent 945dc8b commit a50e813
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

* Add `ErrorRef` wrapper to enable logging error references.

### 2.8.0-beta.1 - 2023-09-09

* **BIG:** Updated to Rust 2018
Expand Down
30 changes: 30 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3357,6 +3357,8 @@ where
///
/// This struct is only available in `std` because the `Error` trait is not available
/// without `std`.
///
/// Use [`ErrorRef`] if you have an error reference.
#[cfg(feature = "std")]
pub struct ErrorValue<E: std::error::Error>(pub E);

Expand All @@ -3375,6 +3377,34 @@ where
}
}

/// A wrapper struct for serializing errors references.
///
/// This struct can be used to wrap types that don't implement `slog::Value` but
/// do implement `std::error::Error` so that they can be logged.
/// This is usually not used directly but using `#error` in the macros.
///
/// This struct is only available in `std` because the `Error` trait is not available
/// without `std`.
///
/// Use [`ErrorValue`] if you want to move ownership of the error value.
#[cfg(feature = "std")]
pub struct ErrorRef<'a, E: std::error::Error>(pub &'a E);

#[cfg(feature = "std")]
impl<'a, E> Value for ErrorRef<'a, E>
where
E: 'static + std::error::Error,
{
fn serialize(
&self,
_record: &Record<'_>,
key: Key,
serializer: &mut dyn Serializer,
) -> Result {
serializer.emit_error(key, self.0)
}
}

#[cfg(feature = "nested-values")]
impl<T> Value for Serde<T>
where
Expand Down
56 changes: 56 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ mod no_imports {
mod std_only {
use super::super::*;

/// Error checking drain
///
/// This asserts that the message and formatted KV are the same.
#[derive(Clone)]
struct CheckError;

Expand Down Expand Up @@ -56,6 +59,7 @@ mod std_only {

let mut serializer = ErrorSerializer(String::new());
values.serialize(record, &mut serializer).unwrap();
record.kv().serialize(record, &mut serializer).unwrap();
assert_eq!(serializer.0, format!("{}", record.msg()));
Ok(())
}
Expand Down Expand Up @@ -136,6 +140,15 @@ mod std_only {
slog_info!(logger, "foo");
}

#[test]
fn error_ref_fmt_no_source() {
let error = TestError::new("foo");
let error = &error;
let logger = Logger::root(CheckError, o!());
info!(logger, "foo"; "error" => ErrorRef(error));
slog_info!(logger, "foo"; "error" => ErrorRef(error));
}

#[test]
fn error_fmt_no_source_not_last() {
let logger = Logger::root(
Expand All @@ -146,6 +159,15 @@ mod std_only {
slog_info!(logger, "not-error: not-error; foo");
}

#[test]
fn error_ref_fmt_no_source_not_last() {
let error = TestError::new("foo");
let error = &error;
let logger = Logger::root(CheckError, o!());
info!(logger, "not-error: not-error; foo"; "error" => ErrorRef(error), "not-error" => "not-error");
slog_info!(logger, "not-error: not-error; foo"; "error" => ErrorRef(error), "not-error" => "not-error");
}

#[test]
fn error_fmt_no_source_last() {
let logger = Logger::root(
Expand All @@ -155,13 +177,33 @@ mod std_only {
info!(logger, "foonot-error: not-error; ");
slog_info!(logger, "foonot-error: not-error; ");
}

#[test]
fn error_ref_fmt_no_source_last() {
let error = TestError::new("foo");
let error = &error;
let logger = Logger::root(CheckError, o!());
info!(logger, "foonot-error: not-error; "; "not-error" => "not-error", "error" => ErrorRef(error));
slog_info!(logger, "foonot-error: not-error; "; "not-error" => "not-error", "error" => ErrorRef(error));
}

#[test]
fn error_fmt_single_source() {
let logger = Logger::root(
CheckError,
o!("error" => #TestError("foo", Some(TestError::new("bar")))),
);
info!(logger, "foo: bar");
slog_info!(logger, "foo: bar");
}

#[test]
fn error_ref_fmt_single_source() {
let error = TestError("foo", Some(TestError::new("bar")));
let error = &error;
let logger = Logger::root(CheckError, o!());
info!(logger, "foo: bar"; "error" => ErrorRef(error));
slog_info!(logger, "foo: bar"; "error" => ErrorRef(error));
}

#[test]
Expand All @@ -171,6 +213,19 @@ mod std_only {
o!("error" => #TestError("foo", Some(TestError("bar", Some(TestError::new("baz")))))),
);
info!(logger, "foo: bar: baz");
slog_info!(logger, "foo: bar: baz");
}

#[test]
fn error_ref_fmt_two_sources() {
let error = TestError(
"foo",
Some(TestError("bar", Some(TestError::new("baz")))),
);
let error = &error;
let logger = Logger::root(CheckError, o!());
info!(logger, "foo: bar: baz"; "error" => ErrorRef(error));
slog_info!(logger, "foo: bar: baz"; "error" => ErrorRef(error));
}

#[test]
Expand All @@ -179,6 +234,7 @@ mod std_only {
info!(logger, "not found"; "error" => std::io::Error::from(std::io::ErrorKind::NotFound));
// compiles?
info!(logger, "not found"; "error" => #std::io::Error::from(std::io::ErrorKind::NotFound));
info!(logger, "not found"; "error" => ErrorRef(&std::io::Error::from(std::io::ErrorKind::NotFound)));
}
}

Expand Down

0 comments on commit a50e813

Please sign in to comment.