slog-error-chain
provides std::fmt::Display
, slog::KV
, and slog::Value
adapters to report the full chain of error causes from std::error::Error
s, and
a proc macro to derive slog::KV
and slog::Value
implementations for error
types which will log the full chain of error causes.
This crate was born out of a use of thiserror
to derive std::error::Error
implementations on error enums, although it does not depend on thiserror
and
will work with any Error
s. Error enums often wrap other error sources, such
as:
#[derive(Debug, thiserror::Error)]
enum MyError {
#[error("an I/O error occurred trying to open {}", .path.display())]
OpeningFile {
path: PathBuf,
#[source]
err: io::Error,
},
}
The Display
implementation produced by deriving thiserror::Error
only prints
the topmost error, and does not print any causes. Given the example above, a
MyError::OpeningFile { .. }
error will Display
-format as
# println!("{my_error}")
an I/O error occurred trying to open /some/path
This crate provides InlineErrorChain
, which is an adapter that will print the
full error chain:
# println!("{}", InlineErrorChain::new(&my_error))
an I/O error occurred trying to open /some/path: file not found
InlineErrorChain
also implements slog::Value
and slog::KV
, allowing it to
be logged directly:
// explicit key
info!(
log, "something happened"; "my-key" => InlineErrorChain::new(&err),
);
// key omitted; will log with the key "error"
info!(
log, "something happened"; InlineErrorChain::new(&err),
);
With the derive
feature enabled, error types can #[derive(SlogInlineError)]
to gain slog::Value
and slog::KV
implementations on themselves, allowing
them to be logged directly:
use slog_error_chain::SlogInlineError;
#[derive(Debug, thiserror::Error, SlogInlineError)]
enum MyError {
#[error("an I/O error occurred trying to open {}", .path.display())]
OpeningFile {
path: PathBuf,
#[source]
err: io::Error,
},
}
let err = MyError::OpeningFile { .. };
// explicit key; logs the full chain
info!(log, "something happened"; "my-key" => &err);
// implicit key; logs the full chain with the key "error"
info!(log, "something happened"; &err);
An easy solution to reach for when encountering the "printing an error doesn't
show the underlying cause" problem is to embed the inner error in the outer
error's display string, such as by adding : {err}
to the above example:
#[derive(Debug, thiserror::Error)]
enum MyError {
#[error("an I/O error occurred trying to open {}: {err}", .path.display())]
OpeningFile {
path: PathBuf,
#[source]
err: io::Error,
},
}
Doing so will make the Display
implementation of MyError
look reasonable:
# println!("{my_error}")
an I/O error occurred trying to open /some/path: file not found
but this is incorrect! If you use an error adapter that knows how to walk the
full chain of errors (such as InlineErrorChain
or anyhow::Error
), you will
see "double-speak" along the chain:
# println!("{}", InlineErrorChain::new(&my_error))
an I/O error occurred trying to open /some/path: file not found: file not found
The amount of doubled error text will compound as additional errors are added to the chain, as each layer reprints the remainder of the chain starting from itself.
slog-error-chain
gates additional functionality behind two cargo features:
derive
: Provides the#[derive(SlogInlineError)]
proc macro that can be applied to error types; it provides implementations ofslog::Value
andslog::KV
that delegate toInlineErrorChain
.nested-values
: Provides theArrayErrorChain
type, which is similar toInlineErrorChain
except that it also implementsslog::SerdeValue
, and for loggers that support nested values, the error will be logged as an array of strings (one element per error in the chain).
If both derive
and nested-values
are enabled, the
#[derive(SlogArrayError)]
proc macro is provided. This gives implementations
of slog::Value
, slog::SerdeValue
, and slog::KV
for the error type that
delegates to ArrayErrorChain
. However, implementing slog::SerdeValue
also
requires implementing serde::Serialize
, so this proc macro cannot be used with
error types that already implement serde::Serialize
.
basic
demonstrates raw InlineErrorChain
usage:
% cargo run --example basic
Dec 15 20:34:03.682 INFO logging error with Display impl, err: an I/O error occurred trying to open /some/path
Dec 15 20:34:03.682 INFO logging error with InlineErrorChain, explicit key, my-key: an I/O error occurred trying to open /some/path: custom I/O error
Dec 15 20:34:03.682 INFO logging error with InlineErrorChain, implicit key, error: an I/O error occurred trying to open /some/path: custom I/O error
derive
demonstrates #[derive(SlogInlineError)]
:
% cargo run --example derive --features derive
Dec 15 20:44:45.976 INFO derived slog::Value with explicit key, my-key: outer error: inner error: custom I/O error
Dec 15 20:44:45.976 INFO derived slog::KV using implicit error key, error: outer error: inner error: custom I/O error
nested-values
demonstrates the nested-values
feature (along with #[derive(SlogArrayError)]
:
% cargo run --example nested-values --features derive,nested-values
Dec 15 20:34:25.329 INFO slog-term inline error formatting, explicit key, my-key: outer error: inner error: custom I/O error
Dec 15 20:34:25.329 INFO slog-term inline error formatting, implicit key, error: outer error: inner error: custom I/O error
Dec 15 20:34:25.329 INFO slog-term structured error formatting, explicit key, my-key: outer error: inner error: custom I/O error
Dec 15 20:34:25.329 INFO slog-term structured error formatting, implicit key, error: outer error: inner error: custom I/O error
{"msg":"slog-json inline error formatting, explicit key","level":"INFO","ts":"2023-12-15T20:34:25.329726569Z","my-key":"outer error: inner error: custom I/O error"}
{"msg":"slog-json inline error formatting, implicit key","level":"INFO","ts":"2023-12-15T20:34:25.329768879Z","error":"outer error: inner error: custom I/O error"}
{"msg":"slog-json structured error formatting, explicit key","level":"INFO","ts":"2023-12-15T20:34:25.329805499Z","my-key":["outer error","inner error","custom I/O error"]}
{"msg":"slog-json structured error formatting, implicit key","level":"INFO","ts":"2023-12-15T20:34:25.329853429Z","error":["outer error","inner error","custom I/O error"]}