-
Notifications
You must be signed in to change notification settings - Fork 745
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
subscriber::fmt: Print all error sources #1460
subscriber::fmt: Print all error sources #1460
Conversation
According to the format suggested in tokio-rs#1347 (comment) Fixes tokio-rs#1347
examples/examples/fmt/yak_shave.rs
Outdated
return Err(YakError::MissingYak { | ||
source: MissingYakError::OutOfSpace { | ||
source: OutOfSpaceError::OutOfCash, | ||
}, | ||
} | ||
.into()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From<String>: Box<dyn Error>
doesn't support specifying an source.
examples/examples/fmt/yak_shave.rs
Outdated
#[derive(Debug)] | ||
enum OutOfSpaceError { | ||
OutOfCash, | ||
} | ||
|
||
impl Error for OutOfSpaceError {} | ||
|
||
impl Display for OutOfSpaceError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
OutOfSpaceError::OutOfCash => f.write_str("out of cash"), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
enum MissingYakError { | ||
OutOfSpace { source: OutOfSpaceError }, | ||
} | ||
|
||
impl Error for MissingYakError { | ||
fn source(&self) -> Option<&(dyn Error + 'static)> { | ||
match self { | ||
MissingYakError::OutOfSpace { source } => Some(source), | ||
} | ||
} | ||
} | ||
|
||
impl Display for MissingYakError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
MissingYakError::OutOfSpace { .. } => f.write_str("out of space"), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
enum YakError { | ||
MissingYak { source: MissingYakError }, | ||
} | ||
|
||
impl Error for YakError { | ||
fn source(&self) -> Option<&(dyn Error + 'static)> { | ||
match self { | ||
YakError::MissingYak { source } => Some(source), | ||
} | ||
} | ||
} | ||
|
||
impl Display for YakError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
YakError::MissingYak { .. } => f.write_str("missing yak"), | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these verbose impls kind of get in the way of the meat of the example. Maybe it'd be worth bringing in snafu or thiserror here?
Then again, that could imply that there is some special magic for whatever library is shown...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm...this example was really intended to be a simple demo of the instrumentation-side API (e.g. using the span, #[instrument], and event macros). What if we added a new example demonstrating the use of
tracing` with more complex nested error types? Then, the various error impls wouldn't be as much of a distraction from an example that's really not primarily about errors.
If we wanted to demonstrate that multiple error libraries are supported without special-casing, we could also have an error-specific example depend on several error libraries and use all of them in the example --- building an error chain including errors from eyre
, thiserror
, and snafu
, or something, and then recording that error chain with tracing. Mixing and matching several error libraries is unlikely to occur much in real code, but it would demonstrate that tracing
works with any library that implements std::error::Error
. Also, it might be nice to have a smoke test that the errors generated by multiple libs are, in fact, formatted nicely...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm...this example was really intended to be a simple demo of the instrumentation-side API (e.g. using the span, #[instrument], and event macros). What if we added a new example demonstrating the use of tracing` with more complex nested error types? Then, the various error impls wouldn't be as much of a distraction from an example that's really not primarily about errors.
Makes sense. My main worry there would be about the cartesian explosion of example cases.. :P
Mixing and matching several error libraries is unlikely to occur much in real code
Maybe not in the same project, but it seems fairly likely that a source
stack would cross library borders.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, would it be OK to:
- Move the errors to the bottom of the file instead (so they don't take the primary focus when reading)
- Migrate them to error crates (snafu/thiserror)
While keeping them in the same example otherwise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 2377ad0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall, this is definitely the right approach. i noticed a couple things about the formatting that i think could be improved.
i also had some minor nitpicks, including how we could simplify the fmt::Display
impl for ErrorSourceList
, and a slight preference for moving the complex nested error example into a separate example, but those aren't blockers. however, we should fix the formatting issues.
examples/examples/fmt/yak_shave.rs
Outdated
#[derive(Debug)] | ||
enum OutOfSpaceError { | ||
OutOfCash, | ||
} | ||
|
||
impl Error for OutOfSpaceError {} | ||
|
||
impl Display for OutOfSpaceError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
OutOfSpaceError::OutOfCash => f.write_str("out of cash"), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
enum MissingYakError { | ||
OutOfSpace { source: OutOfSpaceError }, | ||
} | ||
|
||
impl Error for MissingYakError { | ||
fn source(&self) -> Option<&(dyn Error + 'static)> { | ||
match self { | ||
MissingYakError::OutOfSpace { source } => Some(source), | ||
} | ||
} | ||
} | ||
|
||
impl Display for MissingYakError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
MissingYakError::OutOfSpace { .. } => f.write_str("out of space"), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
enum YakError { | ||
MissingYak { source: MissingYakError }, | ||
} | ||
|
||
impl Error for YakError { | ||
fn source(&self) -> Option<&(dyn Error + 'static)> { | ||
match self { | ||
YakError::MissingYak { source } => Some(source), | ||
} | ||
} | ||
} | ||
|
||
impl Display for YakError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
YakError::MissingYak { .. } => f.write_str("missing yak"), | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm...this example was really intended to be a simple demo of the instrumentation-side API (e.g. using the span, #[instrument], and event macros). What if we added a new example demonstrating the use of
tracing` with more complex nested error types? Then, the various error impls wouldn't be as much of a distraction from an example that's really not primarily about errors.
If we wanted to demonstrate that multiple error libraries are supported without special-casing, we could also have an error-specific example depend on several error libraries and use all of them in the example --- building an error chain including errors from eyre
, thiserror
, and snafu
, or something, and then recording that error chain with tracing. Mixing and matching several error libraries is unlikely to occur much in real code, but it would demonstrate that tracing
works with any library that implements std::error::Error
. Also, it might be nice to have a smoke test that the errors generated by multiple libs are, in fact, formatted nicely...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall, this is definitely the right approach. i noticed a couple things about the formatting that i think could be improved.
i also had some minor nitpicks, including how we could simplify the fmt::Display
impl for ErrorSourceList
, and a slight preference for moving the complex nested error example into a separate example, but those aren't blockers. however, we should fix the formatting issues.
struct ErrorSourceList<'a>(&'a (dyn std::error::Error + 'static)); | ||
|
||
impl<'a> Display for ErrorSourceList<'a> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another thought i had, but again, take it or leave it: i wonder if a nicer way to factor this would be to have this type just implement Iterator
, analogous to the Error::chain
unstable API. then, we could have the default formatter use f.debug_list().entries(ErrorSourceList(&error)).finish()
, but the Pretty
formatter could do something arbitrarily fancy with the iterator, like delimiting the errors with indentation...and eventually, we could also use the iterator in the JSON formatter to serialize the error chain as a JSON list.
what do you think? i'd be fine with merging this as-is, and making that change later if and when it's necessary, but it seems like it might be a somewhat nicer way to factor this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then, we could have the default formatter
The Display
of ErrorSourceList
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 looks good to me — sorry i missed that this one was ready for another review!
Fixed the compiler warnings in 59578e4, but that should probably be cherry-picked (or split into a separate PR) to make the rest of the PR easier to forwardport to 0.2.x (since it's a treewide change that is more related to the new rustc release than this PR specifically). |
@hawkw Sorry to pester you, but what would be the next step here? Anything more I should do to move this along? |
I'd really like to get this merged today. Would you mind moving the change that fixes warnings to a separate PR, so that we can merge that and then merge this change? |
Sure: #1495 |
Thanks! |
Forward-port of tokio-rs#1460 to the 0.2.x (master) branch.
Forward-port of #1460 to the 0.2.x (master) branch. ## Motivation Fixes #1347 ## Solution Change the format from `err=message, err: source1` to `err=message err.sources=[source1, source2, source3, ...]`, as suggested in #1347 (comment) (still leaving out the sources if there are none). ## Caveats Haven't changed the JSON formatter, since I'm not really sure about how to do that. The current format is `{.., "fields": {.., "err": "message"}}`, disregarding the sources entirely. We could change that to `{.., "fields": {.., "err": "message", "err.sources": ["source1", "source2", "source3", ..]}}`, which would keep backwards compatibility but looks pretty ugly. Another option would be `{.., "fields": {.., "err": {"message": "message", "sources": ["source1", "source2", "source3", ..]}}}` which leaves room for future expansion. Then again, that begs the question about why the first error is special, so maybe it ought to be `{.., "fields": {.., "err": {"message": "message", "source": {"message": "source1", "source": ..}}}}`. But that style of linked list is pretty annoying to parse, so maybe it ought to be flattened to `{.., "fields": {.., "err": [{"message": "message"}, {"message": "source1"}, ..]}}`? Co-authored-by: Eliza Weisman <eliza@buoyant.io>
# 0.2.20 (August 17, 2021) ### Fixed - **fmt**: Fixed `fmt` printing only the first `source` for errors with a chain of sources ([#1460]) - **fmt**: Fixed missing space between level and event in the `Pretty` formatter ([#1498]) - **json**: Fixed `Json` formatter not honoring `without_time` and `with_level` configurations ([#1463]) ### Added - **registry**: Improved panic message when cloning a span whose ID doesn't exist, to aid in debugging issues with multiple subscribers ([#1483]) - **registry**: Improved documentation on span ID generation ([#1453]) Thanks to new contributors @joshtriplett and @lerouxgd, and returning contributor @teozkr, for contributing to this release! [#1460]: #1460 [#1483]: #1483 [#1463]: #1463 [#1453]: #1453
# 0.2.20 (August 17, 2021) ### Fixed - **fmt**: Fixed `fmt` printing only the first `source` for errors with a chain of sources ([#1460]) - **fmt**: Fixed missing space between level and event in the `Pretty` formatter ([#1498]) - **json**: Fixed `Json` formatter not honoring `without_time` and `with_level` configurations ([#1463]) ### Added - **registry**: Improved panic message when cloning a span whose ID doesn't exist, to aid in debugging issues with multiple subscribers ([#1483]) - **registry**: Improved documentation on span ID generation ([#1453]) Thanks to new contributors @joshtriplett and @lerouxgd, and returning contributor @teozkr, for contributing to this release! [#1460]: #1460 [#1483]: #1483 [#1463]: #1463 [#1453]: #1453
# 0.2.20 (August 17, 2021) ### Fixed - **fmt**: Fixed `fmt` printing only the first `source` for errors with a chain of sources ([#1460]) - **fmt**: Fixed missing space between level and event in the `Pretty` formatter ([#1498]) - **json**: Fixed `Json` formatter not honoring `without_time` and `with_level` configurations ([#1463]) ### Added - **registry**: Improved panic message when cloning a span whose ID doesn't exist, to aid in debugging issues with multiple subscribers ([#1483]) - **registry**: Improved documentation on span ID generation ([#1453]) Thanks to new contributors @joshtriplett and @lerouxrgd, and returning contributor @teozkr, for contributing to this release! [#1460]: #1460 [#1483]: #1483 [#1463]: #1463 [#1453]: #1453 [#1498]: #1498
# 0.2.20 (August 17, 2021) ### Fixed - **fmt**: Fixed `fmt` printing only the first `source` for errors with a chain of sources ([tokio-rs#1460]) - **fmt**: Fixed missing space between level and event in the `Pretty` formatter ([tokio-rs#1498]) - **json**: Fixed `Json` formatter not honoring `without_time` and `with_level` configurations ([tokio-rs#1463]) ### Added - **registry**: Improved panic message when cloning a span whose ID doesn't exist, to aid in debugging issues with multiple subscribers ([tokio-rs#1483]) - **registry**: Improved documentation on span ID generation ([tokio-rs#1453]) Thanks to new contributors @joshtriplett and @lerouxrgd, and returning contributor @teozkr, for contributing to this release! [tokio-rs#1460]: tokio-rs#1460 [tokio-rs#1483]: tokio-rs#1483 [tokio-rs#1463]: tokio-rs#1463 [tokio-rs#1453]: tokio-rs#1453 [tokio-rs#1498]: tokio-rs#1498
Motivation
Fixes #1347
Solution
Change the format from
err=message, err: source1
toerr=message err.sources=[source1, source2, source3, ...]
, as suggested in #1347 (comment) (still leaving out the sources if there are none).Caveats
Haven't changed the JSON formatter, since I'm not really sure about how to do that. The current format is
{.., "fields": {.., "err": "message"}}
, disregarding the sources entirely.We could change that to
{.., "fields": {.., "err": "message", "err.sources": ["source1", "source2", "source3", ..]}}
, which would keep backwards compatibility but looks pretty ugly.Another option would be
{.., "fields": {.., "err": {"message": "message", "sources": ["source1", "source2", "source3", ..]}}}
which leaves room for future expansion.Then again, that begs the question about why the first error is special, so maybe it ought to be
{.., "fields": {.., "err": {"message": "message", "source": {"message": "source1", "source": ..}}}}
.But that style of linked list is pretty annoying to parse, so maybe it ought to be flattened to
{.., "fields": {.., "err": [{"message": "message"}, {"message": "source1"}, ..]}}
?