Skip to content

Commit f515e9d

Browse files
ciuncanhawkw
authored andcommitted
attributes: add err(Debug) meta to use Debug impl (#1631)
## 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`.
1 parent 00aebfa commit f515e9d

File tree

2 files changed

+114
-15
lines changed

2 files changed

+114
-15
lines changed

tracing-attributes/src/lib.rs

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ use syn::{
374374
/// ```
375375
///
376376
/// If the function returns a `Result<T, E>` and `E` implements `std::fmt::Display`, you can add
377-
/// `err` to emit error events when the function returns `Err`:
377+
/// `err` or `err(Display)` to emit error events when the function returns `Err`:
378378
///
379379
/// ```
380380
/// # use tracing_attributes::instrument;
@@ -384,6 +384,18 @@ use syn::{
384384
/// }
385385
/// ```
386386
///
387+
/// By default, error values will be recorded using their `std::fmt::Display` implementations.
388+
/// If an error implements `std::fmt::Debug`, it can be recorded using its `Debug` implementation
389+
/// instead, by writing `err(Debug)`:
390+
///
391+
/// ```
392+
/// # use tracing_attributes::instrument;
393+
/// #[instrument(err(Debug))]
394+
/// fn my_function(arg: usize) -> Result<(), std::io::Error> {
395+
/// Ok(())
396+
/// }
397+
/// ```
398+
///
387399
/// `async fn`s may also be instrumented:
388400
///
389401
/// ```
@@ -591,8 +603,6 @@ fn gen_block(
591603
instrumented_function_name: &str,
592604
self_type: Option<&syn::TypePath>,
593605
) -> proc_macro2::TokenStream {
594-
let err = args.err;
595-
596606
// generate the span's name
597607
let span_name = args
598608
// did the user override the span's name?
@@ -705,29 +715,34 @@ fn gen_block(
705715
))
706716
})();
707717

718+
let err_event = match args.err_mode {
719+
Some(ErrorMode::Display) => Some(quote!(tracing::error!(error = %e))),
720+
Some(ErrorMode::Debug) => Some(quote!(tracing::error!(error = ?e))),
721+
_ => None,
722+
};
723+
708724
// Generate the instrumented function body.
709725
// If the function is an `async fn`, this will wrap it in an async block,
710726
// which is `instrument`ed using `tracing-futures`. Otherwise, this will
711727
// enter the span and then perform the rest of the body.
712728
// If `err` is in args, instrument any resulting `Err`s.
713729
if async_context {
714-
let mk_fut = if err {
715-
quote_spanned!(block.span()=>
730+
let mk_fut = match err_event {
731+
Some(err_event) => quote_spanned!(block.span()=>
716732
async move {
717733
match async move { #block }.await {
718734
#[allow(clippy::unit_arg)]
719735
Ok(x) => Ok(x),
720736
Err(e) => {
721-
tracing::error!(error = %e);
737+
#err_event;
722738
Err(e)
723739
}
724740
}
725741
}
726-
)
727-
} else {
728-
quote_spanned!(block.span()=>
742+
),
743+
None => quote_spanned!(block.span()=>
729744
async move { #block }
730-
)
745+
),
731746
};
732747

733748
return quote!(
@@ -764,15 +779,15 @@ fn gen_block(
764779
}
765780
);
766781

767-
if err {
782+
if let Some(err_event) = err_event {
768783
return quote_spanned!(block.span()=>
769784
#span
770785
#[allow(clippy::redundant_closure_call)]
771786
match (move || #block)() {
772787
#[allow(clippy::unit_arg)]
773788
Ok(x) => Ok(x),
774789
Err(e) => {
775-
tracing::error!(error = %e);
790+
#err_event;
776791
Err(e)
777792
}
778793
}
@@ -802,7 +817,7 @@ struct InstrumentArgs {
802817
skips: HashSet<Ident>,
803818
skip_all: bool,
804819
fields: Option<Fields>,
805-
err: bool,
820+
err_mode: Option<ErrorMode>,
806821
/// Errors describing any unrecognized parse inputs that we skipped.
807822
parse_warnings: Vec<syn::Error>,
808823
}
@@ -939,8 +954,9 @@ impl Parse for InstrumentArgs {
939954
}
940955
args.fields = Some(input.parse()?);
941956
} else if lookahead.peek(kw::err) {
942-
let _ = input.parse::<kw::err>()?;
943-
args.err = true;
957+
let _ = input.parse::<kw::err>();
958+
let mode = ErrorMode::parse(input)?;
959+
args.err_mode = Some(mode);
944960
} else if lookahead.peek(Token![,]) {
945961
let _ = input.parse::<Token![,]>()?;
946962
} else {
@@ -998,6 +1014,39 @@ impl Parse for Skips {
9981014
}
9991015
}
10001016

1017+
#[derive(Debug, Hash, PartialEq, Eq)]
1018+
enum ErrorMode {
1019+
Display,
1020+
Debug,
1021+
}
1022+
1023+
impl Default for ErrorMode {
1024+
fn default() -> Self {
1025+
ErrorMode::Display
1026+
}
1027+
}
1028+
1029+
impl Parse for ErrorMode {
1030+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
1031+
if !input.peek(syn::token::Paren) {
1032+
return Ok(ErrorMode::default());
1033+
}
1034+
let content;
1035+
let _ = syn::parenthesized!(content in input);
1036+
let maybe_mode: Option<Ident> = content.parse()?;
1037+
maybe_mode.map_or(Ok(ErrorMode::default()), |ident| {
1038+
match ident.to_string().as_str() {
1039+
"Debug" => Ok(ErrorMode::Debug),
1040+
"Display" => Ok(ErrorMode::Display),
1041+
_ => Err(syn::Error::new(
1042+
ident.span(),
1043+
"unknown error mode, must be Debug or Display",
1044+
)),
1045+
}
1046+
})
1047+
}
1048+
}
1049+
10011050
#[derive(Debug)]
10021051
struct Fields(Punctuated<Field, Token![,]>);
10031052

tracing-attributes/tests/err.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,53 @@ fn impl_trait_return_type() {
153153

154154
handle.assert_finished();
155155
}
156+
157+
#[instrument(err(Debug))]
158+
fn err_dbg() -> Result<u8, TryFromIntError> {
159+
u8::try_from(1234)
160+
}
161+
162+
#[test]
163+
fn test_err_dbg() {
164+
let span = span::mock().named("err_dbg");
165+
let (collector, handle) = collector::mock()
166+
.new_span(span.clone())
167+
.enter(span.clone())
168+
.event(
169+
event::mock().at_level(Level::ERROR).with_fields(
170+
field::mock("error")
171+
// use the actual error value that will be emitted, so
172+
// that this test doesn't break if the standard library
173+
// changes the `fmt::Debug` output from the error type
174+
// in the future.
175+
.with_value(&tracing::field::debug(u8::try_from(1234).unwrap_err())),
176+
),
177+
)
178+
.exit(span.clone())
179+
.drop_span(span)
180+
.done()
181+
.run_with_handle();
182+
with_default(collector, || err_dbg().ok());
183+
handle.assert_finished();
184+
}
185+
186+
#[test]
187+
fn test_err_display_default() {
188+
let span = span::mock().named("err");
189+
let (collector, handle) = collector::mock()
190+
.new_span(span.clone())
191+
.enter(span.clone())
192+
.event(
193+
event::mock().at_level(Level::ERROR).with_fields(
194+
field::mock("error")
195+
// by default, errors will be emitted with their display values
196+
.with_value(&tracing::field::display(u8::try_from(1234).unwrap_err())),
197+
),
198+
)
199+
.exit(span.clone())
200+
.drop_span(span)
201+
.done()
202+
.run_with_handle();
203+
with_default(collector, || err().ok());
204+
handle.assert_finished();
205+
}

0 commit comments

Comments
 (0)