Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ErrorRef in # error formatter #328

Merged
merged 6 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

* Add `ErrorRef` wrapper to enable logging error references.
* Add `ErrorRef` wrapper to enable logging error references (PR #327)
* The `#` error formatter in macros was updated to automatically select `ErrorValue` or `ErrorRef` (PR #328)

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

Expand Down
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@ release_max_level_trace = []
erased-serde = { version = "0.3", optional = true }
serde = { version = "1", optional = true }
serde_derive = { version = "1", optional = true }

[dev-dependencies]
# NOTE: This is just a macro (not a runtime dependency)
#
# It is used to conditionally enable use of newer rust language
# features depending on the compiler features.
# Currently, this is not needed because our MSRV is sufficient
# for all the features we use.
# However, in the future we may need to re-add it.
# rustversion = "1"
#
# For the time being, this is only needed for tests.
rustversion = "1"

[[example]]
name = "singlethread"
Expand Down
139 changes: 133 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,10 +405,10 @@ macro_rules! kv(
kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#?}", $v))), $args_ready); $($args)* )
};
(@ $args_ready:expr; $k:expr => #$v:expr) => {
kv!(@ ($crate::SingleKV::from(($k, $crate::ErrorValue($v))), $args_ready); )
kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@wrap_error $v) )), $args_ready); )
};
(@ $args_ready:expr; $k:expr => #$v:expr, $($args:tt)* ) => {
kv!(@ ($crate::SingleKV::from(($k, $crate::ErrorValue($v))), $args_ready); $($args)* )
kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@wrap_error $v))), $args_ready); $($args)* )
};
(@ $args_ready:expr; $k:expr => $v:expr) => {
kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); )
Expand Down Expand Up @@ -462,10 +462,10 @@ macro_rules! slog_kv(
slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@format_args "{:#?}", $v))), $args_ready); $($args)* )
};
(@ $args_ready:expr; $k:expr => #$v:expr) => {
slog_kv!(@ ($crate::SingleKV::from(($k, $crate::ErrorValue($v))), $args_ready); )
slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@wrap_error $v) )), $args_ready); )
};
(@ $args_ready:expr; $k:expr => #$v:expr, $($args:tt)* ) => {
slog_kv!(@ ($crate::SingleKV::from(($k, $crate::ErrorValue($v))), $args_ready); $($args)* )
slog_kv!(@ ($crate::SingleKV::from(($k, __slog_builtin!(@wrap_error $v) )), $args_ready); $($args)* )
};
(@ $args_ready:expr; $k:expr => $v:expr) => {
slog_kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); )
Expand Down Expand Up @@ -730,7 +730,8 @@ macro_rules! slog_record(
/// Similarly to use `std::fmt::Debug` value can be prefixed with `?`,
/// or pretty-printed with `#?`.
///
/// Errors can be prefixed with `#` as a shorthand for wrapping with `ErrorValue`.
/// Errors can be prefixed with `#` as a shorthand. Error values will be wrapped
/// with [`ErrorValue`]. Error references will be wrapped in [`ErrorRef`].
///
/// Full list of supported formats can always be inspected by looking at
/// [`kv` macro](macro.log.html)
Expand Down Expand Up @@ -1067,6 +1068,16 @@ macro_rules! __slog_builtin {
(@line) => ( line!() );
(@column) => ( column!() );
(@module_path) => ( module_path!() );
(@wrap_error $v:expr) => ({
// this magical sequence of code is used to wrap either with
// slog::ErrorValue or slog::ErrorRef as appropriate
// See PR #328 for details
#[allow(unused_imports)]
use $crate::{ErrorRefKind, ErrorValueKind};
let v = $v;
let w = $crate::ErrorTagWrapper(v);
(&&w).slog_error_kind().wrap(w.0)
});
}

// }}}
Expand Down Expand Up @@ -3349,6 +3360,122 @@ where
}
}

/// Wrapper for auto-deref specialization for error values and references.
///
/// See <https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html>
/// and <https://github.com/dtolnay/case-studies/tree/master/autoref-specialization> for
/// details about the technique.
///
/// This type allows to detect if it received an error reference or value.
/// To test the kind of `e`, you have to call it as `(&&ErrorTagWrapper(e)).slog_error_kind()`.
/// It will return [`ErrorValueTag`] for values and [`ErrorRefTag`] for references.
///
/// This is an internal implementation detail of the `kv!` macro and should not
/// be used directly.
#[cfg(feature = "std")]
#[doc(hidden)]
pub struct ErrorTagWrapper<E>(E);

#[cfg(feature = "std")]
#[test]
fn test_error_tag_wrapper() {
#[derive(Debug, Clone, Copy)]
struct MyError(&'static str);
impl core::fmt::Display for MyError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.0)
}
}
impl std::error::Error for MyError {}
let e = MyError("everything is on fire");
assert_eq!(
{
#[allow(clippy::needless_borrow)]
// The "needless" borrow is part of the point
(&&ErrorTagWrapper(e)).slog_error_kind()
},
ErrorValueTag
);
let e = &e;
assert_eq!((&&ErrorTagWrapper(e)).slog_error_kind(), ErrorRefTag);
}

/// Unit struct indicating that the content of `ErrorTagWrapper` was a value.
///
/// This is an internal implementation detail of the `kv!` macro and should not
/// be used directly.
#[cfg(feature = "std")]
#[doc(hidden)]
#[derive(Debug, PartialEq, Eq)]
pub struct ErrorValueTag;
#[cfg(feature = "std")]
impl ErrorValueTag {
/// Create a [`Value`] wrapper for an owned error value.
pub fn wrap<E>(self, e: E) -> ErrorValue<E>
where
E: std::error::Error,
{
ErrorValue(e)
}
}

/// Auxiliary trait for auto-deref dispatch of owned error values
///
/// This is an internal implementation detail of the `kv!` macro and should not
/// be used directly.
#[cfg(feature = "std")]
#[doc(hidden)]
pub trait ErrorValueKind {
#[inline]
fn slog_error_kind(&self) -> ErrorValueTag {
ErrorValueTag
}
}
#[cfg(feature = "std")]
impl<E: std::error::Error> ErrorValueKind for ErrorTagWrapper<E> {}

/// Unit struct indicating that the content of `ErrorTagWrapper` was a reference.
///
/// This is an internal implementation detail of the `kv!` macro and should not
/// be used directly.
#[cfg(feature = "std")]
#[doc(hidden)]
#[derive(Debug, PartialEq, Eq)]
pub struct ErrorRefTag;
#[cfg(feature = "std")]
impl ErrorRefTag {
/// Create a [`Value`] wrapper for an error reference.
pub fn wrap<E>(self, e: &E) -> ErrorRef<'_, E>
where
E: ?Sized + 'static + std::error::Error,
{
ErrorRef(e)
}
}

/// Auxiliary trait for auto-deref dispatch of error references
///
/// This is an internal implementation detail of the `kv!` macro and should not
/// be used directly.
#[cfg(feature = "std")]
#[doc(hidden)]
pub trait ErrorRefKind {
#[inline]
fn slog_error_kind(self) -> ErrorRefTag
where
Self: Sized,
{
ErrorRefTag
}
}
#[cfg(feature = "std")]
impl<ERef> ErrorRefKind for &&ErrorTagWrapper<ERef>
where
ERef: core::ops::Deref,
ERef::Target: std::error::Error,
{
}

/// A wrapper struct for serializing errors
///
/// This struct can be used to wrap types that don't implement `slog::Value` but
Expand Down Expand Up @@ -3388,7 +3515,7 @@ where
///
/// 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);
pub struct ErrorRef<'a, E: ?Sized + std::error::Error>(pub &'a E);

#[cfg(feature = "std")]
impl<'a, E> Value for ErrorRef<'a, E>
Expand Down
49 changes: 38 additions & 11 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,32 @@ mod std_only {
info!(log, "(d2, d1, c, b2, b1, a)");
}

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

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

// requires `impl Error for Arc<T>` - since 1.52
#[rustversion::since(1.52)]
#[test]
fn error_arc_ref() {
let error = TestError::new("foo");
let error = Arc::new(error);
let logger = Logger::root(CheckError, o!());
info!(logger, "foo"; "error" => #&error);
}

#[test]
fn error_fmt_no_source() {
let logger =
Expand All @@ -145,8 +171,8 @@ mod std_only {
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));
info!(logger, "foo"; "error" => #error);
slog_info!(logger, "foo"; "error" => #error);
}

#[test]
Expand All @@ -164,8 +190,8 @@ mod std_only {
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");
info!(logger, "not-error: not-error; foo"; "error" => #error, "not-error" => "not-error");
slog_info!(logger, "not-error: not-error; foo"; "error" => #error, "not-error" => "not-error");
}

#[test]
Expand All @@ -183,8 +209,8 @@ mod std_only {
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));
info!(logger, "foonot-error: not-error; "; "not-error" => "not-error", "error" => #error);
slog_info!(logger, "foonot-error: not-error; "; "not-error" => "not-error", "error" => #error);
}

#[test]
Expand All @@ -202,8 +228,8 @@ mod std_only {
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));
info!(logger, "foo: bar"; "error" => #error);
slog_info!(logger, "foo: bar"; "error" => #error);
}

#[test]
Expand All @@ -224,8 +250,8 @@ mod std_only {
);
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));
info!(logger, "foo: bar: baz"; "error" => #error);
slog_info!(logger, "foo: bar: baz"; "error" => #error);
}

#[test]
Expand All @@ -234,7 +260,8 @@ 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)));
let error = std::io::Error::from(std::io::ErrorKind::NotFound);
info!(logger, "not found"; "error" => #&error);
}
}

Expand Down
Loading