-
Notifications
You must be signed in to change notification settings - Fork 726
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 support for custom levels for ret
and err
(#2330)
#2335
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.
Thanks for working on this! This branch looks like a good start. I commented on some code style suggestions, but some of them might require a larger refactoring of this code, so if you'd prefer not do make all of those changes as part of this PR, that's fine. I would like to see the test changes I requested, though.
Thanks!
tracing-attributes/src/attr.rs
Outdated
@@ -6,60 +6,30 @@ use quote::{quote, quote_spanned, ToTokens}; | |||
use syn::ext::IdentExt as _; | |||
use syn::parse::{Parse, ParseStream}; | |||
|
|||
#[derive(Clone, Default, Debug)] | |||
pub(crate) struct DisplayArgs { |
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 is kind of a nitpick, but I feel like DisplayArgs
isn't really the clearest name for this --- it sounds like it's specifically referring to fmt::Display
, but the struct is also used when the event is recorded using Debug
. How about calling it EventArgs
, instead, since it configures an event emitted for a return value?
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.
Yeah, I didn't have a great idea of how to name it. EventArgs
sounds better, thanks.
tracing-attributes/src/attr.rs
Outdated
} else { | ||
if result.mode != FormatMode::default() { | ||
return Err(content.error("expected only a single format argument")); | ||
} | ||
let maybe_mode: Option<Ident> = content.parse()?; | ||
if let Some(ident) = maybe_mode { | ||
match ident.to_string().as_str() { | ||
"Debug" => result.mode = FormatMode::Debug, | ||
"Display" => result.mode = FormatMode::Display, | ||
_ => { | ||
return Err(syn::Error::new( | ||
ident.span(), | ||
"unknown error mode, must be Debug or Display", | ||
)) | ||
} | ||
} | ||
} | ||
} |
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.
style nit: i think we can maybe simplify the nested if
chain here a little bit if we wrote something like this:
} else { | |
if result.mode != FormatMode::default() { | |
return Err(content.error("expected only a single format argument")); | |
} | |
let maybe_mode: Option<Ident> = content.parse()?; | |
if let Some(ident) = maybe_mode { | |
match ident.to_string().as_str() { | |
"Debug" => result.mode = FormatMode::Debug, | |
"Display" => result.mode = FormatMode::Display, | |
_ => { | |
return Err(syn::Error::new( | |
ident.span(), | |
"unknown error mode, must be Debug or Display", | |
)) | |
} | |
} | |
} | |
} | |
} else if result.mode != FormatMode::default() { | |
return Err(content.error("expected only a single format argument")); | |
} else if let Some(maybe_mode) = content.parse::<Option<Ident>>?() { | |
match maybe_mode.to_string().as_str() { | |
"Debug" => result.mode = FormatMode::Debug, | |
"Display" => result.mode = FormatMode::Display, | |
_ => return Err(syn::Error::new( | |
ident.span(), | |
"unknown error mode, must be Debug or Display", | |
)), | |
} | |
} |
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 less nested, thanks.
tracing-attributes/tests/ret.rs
Outdated
|
||
#[test] | ||
fn test_warn_info() { | ||
let span = span::mock().named("ret_warn_info"); |
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.
let's assert that the span itself is at the WARN
level here:
let span = span::mock().named("ret_warn_info"); | |
let span = span::mock().named("ret_warn_info").at_level(Level::WARN); |
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.
tracing-attributes/tests/ret.rs
Outdated
|
||
#[test] | ||
fn test_dbg_warn() { | ||
let span = span::mock().named("ret_dbg_warn"); |
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 let's assert that the span is at the WARN
level here, as well
let span = span::mock().named("ret_dbg_warn"); | |
let span = span::mock().named("ret_dbg_warn").at_level(Level::WARN) |
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.
Also done.
tracing-attributes/src/attr.rs
Outdated
@@ -386,6 +383,50 @@ impl Parse for Level { | |||
} | |||
} | |||
|
|||
pub(crate) fn level_to_tokens(level: &Option<Level>, default_error: bool) -> impl ToTokens { |
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'm not a big fan of how we pass in an Option<Level>
and a bool
that indicates whether the default should be ERROR
or not...this feels a little awkward. What if, instead, we changed this so that it's a method on &Level
, and changed the code that currently calls this with an &Option<Level>
so that it uses Option::unwrap_or
with the default value and just calls level.to_tokens()
afterwards? This might require a change to how the Level
type is represented internally, but it seems like it might be a bit neater...
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.
Cleaned up. Now level()
returns a Level
since it implements ToTokens
. For EventArgs
, we can pass the default explicitly.
tracing-attributes/src/attr.rs
Outdated
match level { | ||
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("trace") => { | ||
quote!(tracing::Level::TRACE) | ||
} | ||
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("debug") => { | ||
quote!(tracing::Level::DEBUG) | ||
} | ||
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("info") => { | ||
quote!(tracing::Level::INFO) | ||
} | ||
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("warn") => { | ||
quote!(tracing::Level::WARN) | ||
} | ||
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("error") => { | ||
quote!(tracing::Level::ERROR) | ||
} | ||
Some(Level::Int(ref lit)) if is_level(lit, 1) => quote!(tracing::Level::TRACE), | ||
Some(Level::Int(ref lit)) if is_level(lit, 2) => quote!(tracing::Level::DEBUG), | ||
Some(Level::Int(ref lit)) if is_level(lit, 3) => quote!(tracing::Level::INFO), | ||
Some(Level::Int(ref lit)) if is_level(lit, 4) => quote!(tracing::Level::WARN), | ||
Some(Level::Int(ref lit)) if is_level(lit, 5) => quote!(tracing::Level::ERROR), | ||
Some(Level::Path(ref pat)) => quote!(#pat), | ||
Some(_) => quote! { | ||
compile_error!( | ||
"unknown verbosity level, expected one of \"trace\", \ | ||
\"debug\", \"info\", \"warn\", or \"error\", or a number 1-5" | ||
) | ||
}, | ||
None => if default_error { | ||
quote!(tracing::Level::ERROR) | ||
} else { | ||
quote!(tracing::Level::INFO) | ||
}, | ||
} |
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 isn't a big deal, especially since i know this code was already here, but i wonder if it would be a bit nicer to change this code so that determining which tracing::Level
a string or integer value corresponds to occurred in a Parse
implementation for Level
, rather than in ToTokens
?
That way, the error we emit if we got an unexpected level value could happen at parsing-time, when we have access to the span that the level tokens correspond to, rather than quote!
ing a compile_error!
in the ToTokens
impl. That way, I believe the invalid level value would be underlined in the compiler error, which is a bit nicer especially now that we have multiple places where a level might occur in the #[instrument]
arguments.
It's fine if you'd rather not address this suggestion right now, since this isn't code that's added in your PR, but it might be worth doing if you're interested. Otherwise, I'm happy to take care of it later.
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 moved the errors to the Parse
implementation, and implemented ToTokens
for Level
, which leads to a much cleaner interface.
e547d24
to
75b6c30
Compare
Rebased, cleaned up, ready for review. |
@@ -116,7 +116,8 @@ fn gen_block<B: ToTokens>( | |||
.map(|name| quote!(#name)) | |||
.unwrap_or_else(|| quote!(#instrumented_function_name)); | |||
|
|||
let level = args.level(); | |||
let args_level = args.level(); | |||
let level = args_level.clone(); |
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 need a second copy because one gets moved out.
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, the code change looks great now, thanks for taking the time to address my suggestions! I commented on a couple minor style tweaks you may want to make, but none of them are important.
One last thing before we merge this, though: let's make sure the documentation is updated to show how to change the level of an err
/ret
event. The documentation for ret
states that it will always have the same level as the span, here:
tracing/tracing-attributes/src/lib.rs
Lines 230 to 232 in 75b6c30
/// The return value event will have the same level as the span generated by `#[instrument]`. | |
/// By default, this will be `TRACE`, but if the level is overridden, the event will be at the same | |
/// level. |
so let's make sure to explain how to override it. Similarly, the
err
docs here should also probably be updated:tracing/tracing-attributes/src/lib.rs
Lines 249 to 262 in 75b6c30
/// If the function returns a `Result<T, E>` and `E` implements `std::fmt::Display`, you can add | |
/// `err` or `err(Display)` 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(()) | |
/// } | |
/// ``` | |
/// | |
/// 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)`: |
CI failure is unrelated to this change, PR #2344 should fix that. |
Yeah, actually I thought of it today and it's been bothering me all day :D What do you think of the new changes? I also rebased on top of the new master, so the CI should work now. Note: If you want, I can squash my commits into one for a cleaner PR. I keep them separate for now for easier review (you see what changed since your last review). |
Thanks, I'm about to take a look :)
We typically merge PRs by squashing anyway, so you don't have to worry about squashing your commits pre-merge. It's generally much nicer for reviewers to see all the changes as separate commits. Thanks for asking! |
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! i left a couple very minor suggestions on the documentation changes.
Co-authored-by: Eliza Weisman <[email protected]>
Co-authored-by: Eliza Weisman <[email protected]>
Committed your suggestions, they were good. |
This looks great! I'm going to merge it now. Thanks again for working on this! |
This branch adds the ability to override the level of the events generated by the `ret` and `err` arguments to `#[instrument]`. An overridden level can be specified with: ```rust ``` ```rust ``` and so on. This syntax is fully backwards compatible with existing uses of the attribute. In addition, some refactoring was done to how levels are parsed and how the tokens for a specified level is generated. Fixes #2330 ; Conflicts: ; tracing-attributes/src/lib.rs
This branch adds the ability to override the level of the events generated by the `ret` and `err` arguments to `#[instrument]`. An overridden level can be specified with: ```rust ``` ```rust ``` and so on. This syntax is fully backwards compatible with existing uses of the attribute. In addition, some refactoring was done to how levels are parsed and how the tokens for a specified level is generated. Fixes #2330 ; Conflicts: ; tracing-attributes/src/lib.rs
# 0.1.24 (April 24th, 2023) This release of `tracing-attributes` adds support for passing an optional `level` to the `err` and `ret` arguments to `#[instrument]`, allowing the level of the generated return-value event to be overridden. For example, ```rust #[instrument(err(level = "info"))] fn my_great_function() -> Result<(), &'static str> { // ... } ``` will emit an `INFO`-level event if the function returns an `Err`. In addition, this release updates the [`syn`] dependency to v2.x.x. ### Added - `level` argument to `err` and `ret` to override the level of the generated return value event (#2335) - Improved compiler error message when `#[instrument]` is added to a `const fn` (#2418) ### Changed - Updated `syn` dependency to 2.0 (#2516) ### Fixed - Fix `clippy::unreachable` warnings in `#[instrument]`-generated code (#2356) - Removed unused "visit" feature flag from `syn` dependency (#2530) ### Documented - Documented default level for `err` (#2433) - Improved documentation for levels in `#[instrument]` (#2350) Thanks to @nitnelave, @jsgf, @Abhicodes-crypto, @LukeMathWalker, @andrewpollack, @quad, @klensy, @davidpdrsn, and @dbidwell94 for contributign to this release! [`syn`]: https://crates.io/crates/syn
# 0.1.24 (April 24th, 2023) This release of `tracing-attributes` adds support for passing an optional `level` to the `err` and `ret` arguments to `#[instrument]`, allowing the level of the generated return-value event to be overridden. For example, ```rust #[instrument(err(level = "info"))] fn my_great_function() -> Result<(), &'static str> { // ... } ``` will emit an `INFO`-level event if the function returns an `Err`. In addition, this release updates the [`syn`] dependency to v2.x.x. ### Added - `level` argument to `err` and `ret` to override the level of the generated return value event (#2335) - Improved compiler error message when `#[instrument]` is added to a `const fn` (#2418) ### Changed - Updated `syn` dependency to 2.0 (#2516) ### Fixed - Fix `clippy::unreachable` warnings in `#[instrument]`-generated code (#2356) - Removed unused "visit" feature flag from `syn` dependency (#2530) ### Documented - Documented default level for `err` (#2433) - Improved documentation for levels in `#[instrument]` (#2350) Thanks to @nitnelave, @jsgf, @Abhicodes-crypto, @LukeMathWalker, @andrewpollack, @quad, @klensy, @davidpdrsn, and @dbidwell94 for contributign to this release! [`syn`]: https://crates.io/crates/syn
# 0.1.38 (April 25th, 2023) This `tracing` release changes the `Drop` implementation for `Instrumented` `Future`s so that the attached `Span` is entered when dropping the `Future`. This means that events emitted by the `Future`'s `Drop` implementation will now be recorded within its `Span`. It also adds `#[inline]` hints to methods called in the `event!` macro's expansion, for an improvement in both binary size and performance. Additionally, this release updates the `tracing-attributes` dependency to [v0.1.24][attrs-0.1.24], which updates the [`syn`] dependency to v2.x.x. `tracing-attributes` v0.1.24 also includes improvements to the `#[instrument]` macro; see [the `tracing-attributes` 0.1.24 release notes][attrs-0.1.24] for details. ### Added - `Instrumented` futures will now enter the attached `Span` in their `Drop` implementation, allowing events emitted when dropping the future to occur within the span (#2562) - `#[inline]` attributes for methods called by the `event!` macros, making generated code smaller (#2555) - **attributes**: `level` argument to `#[instrument(err)]` and `#[instrument(ret)]` to override the level of the generated return value event (#2335) - **attributes**: Improved compiler error message when `#[instrument]` is added to a `const fn` (#2418) ### Changed - `tracing-attributes`: updated to [0.1.24][attrs-0.1.24] - Removed unneeded `cfg-if` dependency (#2553) - **attributes**: Updated [`syn`] dependency to 2.0 (#2516) ### Fixed - **attributes**: Fix `clippy::unreachable` warnings in `#[instrument]`-generated code (#2356) - **attributes**: Removed unused "visit" feature flag from `syn` dependency (#2530) ### Documented - **attributes**: Documented default level for `#[instrument(err)]` (#2433) - **attributes**: Improved documentation for levels in `#[instrument]` (#2350) Thanks to @nitnelave, @jsgf, @Abhicodes-crypto, @LukeMathWalker, @andrewpollack, @quad, @klensy, @davidpdrsn, @dbidwell94, @ldm0, @NobodyXu, @ilsv, and @daxpedda for contributing to this release! [`syn`]: https://crates.io/crates/syn [attrs-0.1.24]: https://github.com/tokio-rs/tracing/releases/tag/tracing-attributes-0.1.24
# 0.1.38 (April 25th, 2023) This `tracing` release changes the `Drop` implementation for `Instrumented` `Future`s so that the attached `Span` is entered when dropping the `Future`. This means that events emitted by the `Future`'s `Drop` implementation will now be recorded within its `Span`. It also adds `#[inline]` hints to methods called in the `event!` macro's expansion, for an improvement in both binary size and performance. Additionally, this release updates the `tracing-attributes` dependency to [v0.1.24][attrs-0.1.24], which updates the [`syn`] dependency to v2.x.x. `tracing-attributes` v0.1.24 also includes improvements to the `#[instrument]` macro; see [the `tracing-attributes` 0.1.24 release notes][attrs-0.1.24] for details. ### Added - `Instrumented` futures will now enter the attached `Span` in their `Drop` implementation, allowing events emitted when dropping the future to occur within the span (#2562) - `#[inline]` attributes for methods called by the `event!` macros, making generated code smaller (#2555) - **attributes**: `level` argument to `#[instrument(err)]` and `#[instrument(ret)]` to override the level of the generated return value event (#2335) - **attributes**: Improved compiler error message when `#[instrument]` is added to a `const fn` (#2418) ### Changed - `tracing-attributes`: updated to [0.1.24][attrs-0.1.24] - Removed unneeded `cfg-if` dependency (#2553) - **attributes**: Updated [`syn`] dependency to 2.0 (#2516) ### Fixed - **attributes**: Fix `clippy::unreachable` warnings in `#[instrument]`-generated code (#2356) - **attributes**: Removed unused "visit" feature flag from `syn` dependency (#2530) ### Documented - **attributes**: Documented default level for `#[instrument(err)]` (#2433) - **attributes**: Improved documentation for levels in `#[instrument]` (#2350) Thanks to @nitnelave, @jsgf, @Abhicodes-crypto, @LukeMathWalker, @andrewpollack, @quad, @klensy, @davidpdrsn, @dbidwell94, @ldm0, @NobodyXu, @ilsv, and @daxpedda for contributing to this release! [`syn`]: https://crates.io/crates/syn [attrs-0.1.24]: https://github.com/tokio-rs/tracing/releases/tag/tracing-attributes-0.1.24
This branch adds the ability to override the level of the events generated by the `ret` and `err` arguments to `#[instrument]`. An overridden level can be specified with: ```rust ``` ```rust ``` and so on. This syntax is fully backwards compatible with existing uses of the attribute. In addition, some refactoring was done to how levels are parsed and how the tokens for a specified level is generated. Fixes tokio-rs#2330 ; Conflicts: ; tracing-attributes/src/lib.rs
# 0.1.24 (April 24th, 2023) This release of `tracing-attributes` adds support for passing an optional `level` to the `err` and `ret` arguments to `#[instrument]`, allowing the level of the generated return-value event to be overridden. For example, ```rust #[instrument(err(level = "info"))] fn my_great_function() -> Result<(), &'static str> { // ... } ``` will emit an `INFO`-level event if the function returns an `Err`. In addition, this release updates the [`syn`] dependency to v2.x.x. ### Added - `level` argument to `err` and `ret` to override the level of the generated return value event (tokio-rs#2335) - Improved compiler error message when `#[instrument]` is added to a `const fn` (tokio-rs#2418) ### Changed - Updated `syn` dependency to 2.0 (tokio-rs#2516) ### Fixed - Fix `clippy::unreachable` warnings in `#[instrument]`-generated code (tokio-rs#2356) - Removed unused "visit" feature flag from `syn` dependency (tokio-rs#2530) ### Documented - Documented default level for `err` (tokio-rs#2433) - Improved documentation for levels in `#[instrument]` (tokio-rs#2350) Thanks to @nitnelave, @jsgf, @Abhicodes-crypto, @LukeMathWalker, @andrewpollack, @quad, @klensy, @davidpdrsn, and @dbidwell94 for contributign to this release! [`syn`]: https://crates.io/crates/syn
# 0.1.38 (April 25th, 2023) This `tracing` release changes the `Drop` implementation for `Instrumented` `Future`s so that the attached `Span` is entered when dropping the `Future`. This means that events emitted by the `Future`'s `Drop` implementation will now be recorded within its `Span`. It also adds `#[inline]` hints to methods called in the `event!` macro's expansion, for an improvement in both binary size and performance. Additionally, this release updates the `tracing-attributes` dependency to [v0.1.24][attrs-0.1.24], which updates the [`syn`] dependency to v2.x.x. `tracing-attributes` v0.1.24 also includes improvements to the `#[instrument]` macro; see [the `tracing-attributes` 0.1.24 release notes][attrs-0.1.24] for details. ### Added - `Instrumented` futures will now enter the attached `Span` in their `Drop` implementation, allowing events emitted when dropping the future to occur within the span (tokio-rs#2562) - `#[inline]` attributes for methods called by the `event!` macros, making generated code smaller (tokio-rs#2555) - **attributes**: `level` argument to `#[instrument(err)]` and `#[instrument(ret)]` to override the level of the generated return value event (tokio-rs#2335) - **attributes**: Improved compiler error message when `#[instrument]` is added to a `const fn` (tokio-rs#2418) ### Changed - `tracing-attributes`: updated to [0.1.24][attrs-0.1.24] - Removed unneeded `cfg-if` dependency (tokio-rs#2553) - **attributes**: Updated [`syn`] dependency to 2.0 (tokio-rs#2516) ### Fixed - **attributes**: Fix `clippy::unreachable` warnings in `#[instrument]`-generated code (tokio-rs#2356) - **attributes**: Removed unused "visit" feature flag from `syn` dependency (tokio-rs#2530) ### Documented - **attributes**: Documented default level for `#[instrument(err)]` (tokio-rs#2433) - **attributes**: Improved documentation for levels in `#[instrument]` (tokio-rs#2350) Thanks to @nitnelave, @jsgf, @Abhicodes-crypto, @LukeMathWalker, @andrewpollack, @quad, @klensy, @davidpdrsn, @dbidwell94, @ldm0, @NobodyXu, @ilsv, and @daxpedda for contributing to this release! [`syn`]: https://crates.io/crates/syn [attrs-0.1.24]: https://github.com/tokio-rs/tracing/releases/tag/tracing-attributes-0.1.24
Fixes #2330
The change is fairly straightforward, just adding support for customizable levels for
ret
anderr
in a fully backward-compatible way.I made
Level
pub(crate)
to be able to exposelevel_to_tokens
, there may be a better way of doing it but it seemed simple enough and anyway it doesn't escape the crate.