-
Notifications
You must be signed in to change notification settings - Fork 754
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
attributes: add err(Debug)
meta to use Debug
impl
#1631
Conversation
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 looks great. I had a few suggestions that we might want to address before merging this.
Also, it would be nice to add to the documentation for #[instrument(err)]
to explain how to use Debug
instead of Display
using the new syntax:
tracing/tracing-attributes/src/lib.rs
Lines 184 to 193 in 9396211
/// If the function returns a `Result<T, E>` and `E` implements `std::fmt::Display`, you can add | |
/// `err` to emit error events when the function returns `Err`: | |
/// | |
/// ``` | |
/// # use tracing_attributes::instrument; | |
/// #[instrument(err)] | |
/// fn my_function(arg: usize) -> Result<(), std::io::Error> { | |
/// Ok(()) | |
/// } | |
/// ``` |
Thanks for working on this!!
tracing-attributes/src/lib.rs
Outdated
@@ -507,29 +506,34 @@ fn gen_block( | |||
)) | |||
})(); | |||
|
|||
let err_block = match args.err_mode { |
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.
nit, take it or leave it: not sure if err_block
is the best name for this; it's not actually a block, just an expression. maybe
let err_block = match args.err_mode { | |
let err_event = match args.err_mode { |
tracing-attributes/src/lib.rs
Outdated
let mk_fut = match err_block { | ||
Some(err_block) => quote_spanned!(block.span()=> |
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.
if you apply my naming suggestion from above, we would also need to change this
let mk_fut = match err_block { | |
Some(err_block) => quote_spanned!(block.span()=> | |
let mk_fut = match err_block { | |
Some(err_block) => quote_spanned!(block.span()=> |
let mk_fut = match err_block { | |
Some(err_block) => quote_spanned!(block.span()=> | |
let mk_fut = match err_event { | |
Some(err_event) => quote_spanned!(block.span()=> |
tracing-attributes/src/lib.rs
Outdated
async move { | ||
match async move { #block }.await { | ||
#[allow(clippy::unit_arg)] | ||
Ok(x) => Ok(x), | ||
Err(e) => { | ||
tracing::error!(error = %e); | ||
#err_block; |
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.
...and we'd change this as well
#err_block; | |
#err_event; |
tracing-attributes/src/lib.rs
Outdated
@@ -566,15 +570,15 @@ fn gen_block( | |||
} | |||
); | |||
|
|||
if err { | |||
if let Some(err_block) = err_block { |
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.
...and we'd change this
if let Some(err_block) = err_block { | |
if let Some(err_event) = err_event { |
tracing-attributes/src/lib.rs
Outdated
return quote_spanned!(block.span()=> | ||
#span | ||
#[allow(clippy::redundant_closure_call)] | ||
match (move || #block)() { | ||
#[allow(clippy::unit_arg)] | ||
Ok(x) => Ok(x), | ||
Err(e) => { | ||
tracing::error!(error = %e); | ||
#err_block; |
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.
#err_block; | |
#err_event; |
tracing-attributes/src/lib.rs
Outdated
} | ||
impl Default for ErrorMode { |
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.
nit: prefer a line of whitespace between definitions & impl blocks:
} | |
impl Default for ErrorMode { | |
} | |
impl Default for ErrorMode { |
tracing-attributes/src/lib.rs
Outdated
let modes: Punctuated<ErrorMode, Token![,]> = content.parse_terminated(ErrorMode::parse)?; | ||
let modes = modes.into_iter().collect::<HashSet<_>>(); | ||
return match modes.len() { | ||
0 => Ok(Self(ErrorMode::Display)), | ||
1 => Ok(Self(modes.into_iter().next().unwrap())), | ||
_ => Err(syn::Error::new( | ||
content.span(), | ||
"at most one of Debug or Display can be given", | ||
)) | ||
} |
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.
I think we can simplify this a bit --- it shouldn't be necessary to use parse_terminated
to parse any number of error modes, and then emit an error if we parsed more than one. I think, instead, we can use the Parse
impl for Option<T: Parse + Token>
in syn
.
I believe that should allow us to replace the ErrorModes
type entirely. We could move the parenthesized!
inside the ErrorMode
parser, and just try to parse an Option<ErrorMode> when we find an
err` token.
This should hopefully be much simpler!
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.
I might have misunderstood but, I couldn't implement Token
trait. It appears to be supposed to parse some punctuation, identifiers, keywords etc. Also it looks like sealed.
I went ahead and utilized Option<Ident>
to parse an ErrorMode
by its name, I moved most of the parsing including the parenthesis into impl Parse for ErrorMode
.
Overall this looks simpler, but let me know if I further can improve it.
tracing-attributes/tests/err.rs
Outdated
let (collector, handle) = collector::mock() | ||
.new_span(span.clone()) | ||
.enter(span.clone()) | ||
.event(event::mock().at_level(Level::ERROR)) |
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.
thanks for adding the test! it might be nice to change this line to also expect that the error
field is recorded with its Debug
implementation (rather than with Display
).
we could do that like this:
.event(event::mock().at_level(Level::ERROR)) | |
.event( | |
event::mock() | |
.at_level(Level::ERROR) | |
.with_field( | |
field::mock("error") | |
// use the actual error value that will be emitted, so | |
// that this test doesn't break if the standard library | |
// changes the `fmt::Debug` output from the error type | |
// in the future. | |
.with_value(&tracing::field::debug(u8::try_from(1234).unwrap_err()) | |
) | |
) |
but, the other tests don't make assertions about the formatted value of the error, so this isn't strictly necessary --- I just thought it might be nice.
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.
Perfect! Honestly I didn't know what I was doing, and just copied one of the tests. This is what I was looking for. 👍 Will apply.
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 looks good to me --- I commented on a couple minor things. However, it does look like CI is failing because rustfmt
needs to be run: https://github.com/tokio-rs/tracing/pull/1631/checks?check_run_id=3899421739 (this is actually my fault, since I wrote the suggestion for the test change in the GitHub suggestions UI and didn't run rustfmt
on it...). Would you mind running rustfmt
on this branch and committing the results?
tracing-attributes/src/lib.rs
Outdated
/// The above example will be emitting error events using the `std::fmt::Display` implementation. | ||
/// If `E` implements `std::fmt::Debug`, you can also make it use that implementation with | ||
/// `err(Debug)`: |
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.
How about changing this to something like:
/// The above example will be emitting error events using the `std::fmt::Display` implementation. | |
/// If `E` implements `std::fmt::Debug`, you can also make it use that implementation with | |
/// `err(Debug)`: | |
/// By default, error values will be recorded using their `std::fmt::Display` implementations. | |
/// If an error implements `std::fmt::Debug`, it can be recorded using its `Debug` implementation | |
/// instead, by writing `err(Debug)`: |
tracing-attributes/src/lib.rs
Outdated
struct ErrorModes(ErrorMode); | ||
|
||
impl Parse for ErrorModes { | ||
|
||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> { | ||
let _ = input.parse::<kw::err>(); | ||
let mode = ErrorMode::parse(input)?; | ||
Ok(Self(mode)) | ||
} | ||
} |
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.
I feel like the name ErrorModes
is not really accurate for this anymore, since it no longer parses a list of error modes, and now simply parses the err
keyword along with the mode. Maybe we should just rename it to
struct ErrorModes(ErrorMode); | |
impl Parse for ErrorModes { | |
fn parse(input: ParseStream<'_>) -> syn::Result<Self> { | |
let _ = input.parse::<kw::err>(); | |
let mode = ErrorMode::parse(input)?; | |
Ok(Self(mode)) | |
} | |
} | |
struct Error(ErrorMode); | |
impl Parse for Error { | |
fn parse(input: ParseStream<'_>) -> syn::Result<Self> { | |
let _ = input.parse::<kw::err>(); | |
let mode = ErrorMode::parse(input)?; | |
Ok(Self(mode)) | |
} | |
} |
or, we could probably just get rid of this entirely, and move the err
keyword into InstrumentArgs
, like it was before --- we would just try to parse an ErrorMode
if we parsed the err
keyword, inside of the Parse
impl for InstrumentArgs
.
IMO, having a separate type for this is not really necessary any longer, but it's up to you...
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.
That's right, I completely missed this. I think removing Error
and doing err
parsing into the InstrumentArgs
Parse
impl. 👍
Ah sorry about that, I didn't check Applied the suggested changes 👍 |
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.
This looks great! Thanks again for working on this --- I'm going to go ahead and merge it when CI finishes!
err_dbg
meta to use Debug
implerr(Debug)
meta to use Debug
impl
## Motivation This PR attempts to solve #1630 by introducing `err(Debug)` meta to `intrument` attribute macro. As `err` meta causes the error (`e`) returned by instrumented function to be passed to `tracing::error!(error = %e)` i.e. makes it use the `Display` implementation of `e`, the newly added `err(Debug)` makes expands to `tracing::error!(error = ?e)` which makes the `error!` macro to use `Debug` implementation for `e`. `err` and `err(Debug)` are mutually exclusive, adding both will create a compilation error. `err(Display)` is also supported to specify `Display` explicitly. As tried to describe, for some types implementing `Error` it might be more suitable to use `Debug` implementation as in the case of `eyre::Result`. This frees us to manually go over the error chain and print them all, so that `instrument` attribute macro would do it for us. ## Solution - Added a custom keyword `err(Debug)` similar to `err`, - Add `err(Debug)` field to `InstrumentArgs`, - Add parsing for `err(Debug)` arg and check for conflicts with `err`, - Generate `tracing::error!(error = ?e)` when `err(Debug)` is `true` and `tracing::error!(error = %e)` when `err(Display)` or `err` is `true`, - Interpolate generated `err_block` into `Err` branches in both async and sync return positions, if `err` or `err(Debug)` is `true`.
## Motivation This PR attempts to solve #1630 by introducing `err(Debug)` meta to `intrument` attribute macro. As `err` meta causes the error (`e`) returned by instrumented function to be passed to `tracing::error!(error = %e)` i.e. makes it use the `Display` implementation of `e`, the newly added `err(Debug)` makes expands to `tracing::error!(error = ?e)` which makes the `error!` macro to use `Debug` implementation for `e`. `err` and `err(Debug)` are mutually exclusive, adding both will create a compilation error. `err(Display)` is also supported to specify `Display` explicitly. As tried to describe, for some types implementing `Error` it might be more suitable to use `Debug` implementation as in the case of `eyre::Result`. This frees us to manually go over the error chain and print them all, so that `instrument` attribute macro would do it for us. ## Solution - Added a custom keyword `err(Debug)` similar to `err`, - Add `err(Debug)` field to `InstrumentArgs`, - Add parsing for `err(Debug)` arg and check for conflicts with `err`, - Generate `tracing::error!(error = ?e)` when `err(Debug)` is `true` and `tracing::error!(error = %e)` when `err(Display)` or `err` is `true`, - Interpolate generated `err_block` into `Err` branches in both async and sync return positions, if `err` or `err(Debug)` is `true`.
# 0.1.19 (February 3, 2022) This release introduces a new `#[instrument(ret)]` argument to emit an event with the return value of an instrumented function. ### Added - `#[instrument(ret)]` to record the return value of a function ([#1716]) - added `err(Debug)` argument to cause `#[instrument(err)]` to record errors with `Debug` rather than `Display ([#1631]) ### Fixed - incorrect code generation for functions returning async blocks ([#1866]) - incorrect diagnostics when using `rust-analyzer` ([#1634]) Thanks to @Swatinem, @hkmatsumoto, @cynecx, and @ciuncan for contributing to this release! [#1716]: #1716 [#1631]: #1631 [#1634]: #1634 [#1866]: #1866
# 0.1.19 (February 3, 2022) This release introduces a new `#[instrument(ret)]` argument to emit an event with the return value of an instrumented function. ### Added - `#[instrument(ret)]` to record the return value of a function ([#1716]) - added `err(Debug)` argument to cause `#[instrument(err)]` to record errors with `Debug` rather than `Display ([#1631]) ### Fixed - incorrect code generation for functions returning async blocks ([#1866]) - incorrect diagnostics when using `rust-analyzer` ([#1634]) Thanks to @Swatinem, @hkmatsumoto, @cynecx, and @ciuncan for contributing to this release! [#1716]: #1716 [#1631]: #1631 [#1634]: #1634 [#1866]: #1866
# 0.1.19 (February 3, 2022) This release introduces a new `#[instrument(ret)]` argument to emit an event with the return value of an instrumented function. ### Added - `#[instrument(ret)]` to record the return value of a function ([tokio-rs#1716]) - added `err(Debug)` argument to cause `#[instrument(err)]` to record errors with `Debug` rather than `Display ([tokio-rs#1631]) ### Fixed - incorrect code generation for functions returning async blocks ([tokio-rs#1866]) - incorrect diagnostics when using `rust-analyzer` ([tokio-rs#1634]) Thanks to @Swatinem, @hkmatsumoto, @cynecx, and @ciuncan for contributing to this release! [tokio-rs#1716]: tokio-rs#1716 [tokio-rs#1631]: tokio-rs#1631 [tokio-rs#1634]: tokio-rs#1634 [tokio-rs#1866]: tokio-rs#1866
Motivation
This PR attempts to solve #1630 by introducing
err_dbg
meta tointrument
attribute macro. Aserr
meta causes the error (e
) returned by instrumented function to be passed totracing::error!(error = %e)
i.e. makes it use theDisplay
implementation ofe
, the newly addederr_dbg
makes expands totracing::error!(error = ?e)
which makes theerror!
macro to useDebug
implementation fore
.err
anderr_dbg
are mutually exclusive, adding both will create a compilation error.As tried to describe, for some types implementing
Error
it might be more suitable to useDebug
implementation as in the case ofeyre::Result
. This frees us to manually go over the error chain and print them all, so thatinstrument
attribute macro would do it for us.Solution
err_db
similar toerr
,err_dbg
field toInstrumentArgs
,err_dbg
arg and check for conflicts witherr
,tracing::error!(error = ?e)
whenerr_dbg
istrue
andtracing::error!(error = %e)
whenerr
istrue
,err_block
intoErr
branches in both async and sync return positions, iferr
orerr_dbg
istrue
.Even though I copied one of the tests, I'm not sure if it helps to check the new behavior. I manually tested it using VSCode/rust-analyzer's "Expand macro recursively" facility. But I am open to better test suggestions.