diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bfd60d9..45c1ceb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] * Add `ErrorRef` wrapper to enable logging error references. + * The `#` error formatter in macros was updated to automatically select `ErrorValue` or `ErrorRef`. ### 2.8.0-beta.1 - 2023-09-09 diff --git a/src/lib.rs b/src/lib.rs index bc7c0569..ee79ec3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, { use $crate::{ErrorRefKind, ErrorValueKind}; let v = $v; let w = $crate::ErrorTagWrapper(v); (&&w).slog_error_kind().wrap(w.0) } )), $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, { use $crate::{ErrorRefKind, ErrorValueKind}; let v = $v; let w = $crate::ErrorTagWrapper(v); (&&w).slog_error_kind().wrap(w.0) } )), $args_ready); $($args)* ) }; (@ $args_ready:expr; $k:expr => $v:expr) => { kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); ) @@ -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, { use $crate::{ErrorRefKind, ErrorValueKind}; let v = $v; let w = $crate::ErrorTagWrapper(v); (&&w).slog_error_kind().wrap(w.0) } )), $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, { use $crate::{ErrorRefKind, ErrorValueKind}; let v = $v; let w = $crate::ErrorTagWrapper(v); (&&w).slog_error_kind().wrap(w.0) } )), $args_ready); $($args)* ) }; (@ $args_ready:expr; $k:expr => $v:expr) => { slog_kv!(@ ($crate::SingleKV::from(($k, $v)), $args_ready); ) @@ -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) @@ -3349,6 +3350,115 @@ where } } +/// Wrapper for auto-deref specialization for error values and references. +/// +/// See +/// and 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); + +#[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!((&&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(self, e: E) -> ErrorValue + 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 ErrorValueKind for ErrorTagWrapper {} + +/// 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<'a, E>(self, e: &'a E) -> ErrorRef<'a, 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 ErrorRefKind for &&ErrorTagWrapper +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 @@ -3388,7 +3498,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> diff --git a/src/tests.rs b/src/tests.rs index ff6a66b1..03f265ff 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -132,6 +132,30 @@ 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); + } + + #[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 = @@ -145,8 +169,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] @@ -164,8 +188,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] @@ -183,8 +207,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] @@ -202,8 +226,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] @@ -224,8 +248,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] @@ -234,7 +258,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); } }