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

subscriber::fmt: Print all error sources #1460

Merged
merged 10 commits into from
Aug 6, 2021

Conversation

nightkr
Copy link
Contributor

@nightkr nightkr commented Jul 9, 2021

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"}, ..]}}?

@nightkr nightkr requested review from hawkw and a team as code owners July 9, 2021 03:20
Comment on lines 74 to 79
return Err(YakError::MissingYak {
source: MissingYakError::OutOfSpace {
source: OutOfSpaceError::OutOfCash,
},
}
.into());
Copy link
Contributor Author

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.

Comment on lines 4 to 59
#[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"),
}
}
}
Copy link
Contributor Author

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...

Copy link
Member

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...

Copy link
Contributor Author

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.

Copy link
Contributor Author

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:

  1. Move the errors to the bottom of the file instead (so they don't take the primary focus when reading)
  2. Migrate them to error crates (snafu/thiserror)

While keeping them in the same example otherwise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 2377ad0

Copy link
Member

@hawkw hawkw left a 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.

Comment on lines 4 to 59
#[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"),
}
}
}
Copy link
Member

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...

tracing-subscriber/src/fmt/format/mod.rs Outdated Show resolved Hide resolved
tracing-subscriber/src/fmt/format/mod.rs Outdated Show resolved Hide resolved
tracing-subscriber/src/fmt/format/mod.rs Show resolved Hide resolved
Copy link
Member

@hawkw hawkw left a 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.

Comment on lines +813 to +815
struct ErrorSourceList<'a>(&'a (dyn std::error::Error + 'static));

impl<'a> Display for ErrorSourceList<'a> {
Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Member

@hawkw hawkw left a 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!

@nightkr
Copy link
Contributor Author

nightkr commented Aug 6, 2021

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).

@nightkr
Copy link
Contributor Author

nightkr commented Aug 6, 2021

@hawkw Sorry to pester you, but what would be the next step here? Anything more I should do to move this along?

@hawkw
Copy link
Member

hawkw commented Aug 6, 2021

@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?

nightkr added a commit to nightkr/tracing that referenced this pull request Aug 6, 2021
@nightkr
Copy link
Contributor Author

nightkr commented Aug 6, 2021

Sure: #1495

hawkw pushed a commit that referenced this pull request Aug 6, 2021
@hawkw hawkw merged commit aeae4fa into tokio-rs:v0.1.x Aug 6, 2021
@nightkr
Copy link
Contributor Author

nightkr commented Aug 6, 2021

Thanks!

@nightkr nightkr deleted the feature/subscriber-fmt-error-sources branch August 6, 2021 17:14
nightkr added a commit to nightkr/tracing that referenced this pull request Aug 6, 2021
Forward-port of tokio-rs#1460 to the 0.2.x (master) branch.
hawkw added a commit that referenced this pull request Aug 7, 2021
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>
hawkw added a commit that referenced this pull request Aug 17, 2021
# 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
hawkw added a commit that referenced this pull request Aug 17, 2021
# 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
hawkw added a commit that referenced this pull request Aug 17, 2021
# 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
kaffarell pushed a commit to kaffarell/tracing that referenced this pull request May 22, 2024
kaffarell pushed a commit to kaffarell/tracing that referenced this pull request May 22, 2024
# 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants