diff options
author | Matthew Maurer <mmaurer@google.com> | 2023-05-27 02:07:11 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-05-27 02:07:11 +0000 |
commit | 2a38b027701437f0fb5e2371d489b6fa48c4e623 (patch) | |
tree | 68d809c14ec549cf5514c7341a8dbc914a1932eb | |
parent | f7206b858090548451488326d3a18e027657b536 (diff) | |
parent | e1d7e208401b8f141080169c3399d01ac31a8cad (diff) | |
download | tracing-attributes-2a38b027701437f0fb5e2371d489b6fa48c4e623.tar.gz |
Update to syn-2 am: ed8925a283 am: 6a66d6d156 am: 7c8a97910f am: 39283bb395 am: e1d7e20840
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/tracing-attributes/+/2523895
Change-Id: Id85471df4a8f4b65108f73b3fce9733b7a3eb8cf
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | patches/syn-2.diff | 906 | ||||
-rw-r--r-- | src/attr.rs | 221 | ||||
-rw-r--r-- | src/expand.rs | 52 | ||||
-rw-r--r-- | src/lib.rs | 318 |
4 files changed, 1137 insertions, 360 deletions
diff --git a/patches/syn-2.diff b/patches/syn-2.diff new file mode 100644 index 0000000..247e761 --- /dev/null +++ b/patches/syn-2.diff @@ -0,0 +1,906 @@ +diff --git a/src/attr.rs b/src/attr.rs +index ff875e1..9b778c8 100644 +--- a/src/attr.rs ++++ b/src/attr.rs +@@ -6,6 +6,14 @@ use quote::{quote, quote_spanned, ToTokens}; + use syn::ext::IdentExt as _; + use syn::parse::{Parse, ParseStream}; + ++/// Arguments to `#[instrument(err(...))]` and `#[instrument(ret(...))]` which describe how the ++/// return value event should be emitted. ++#[derive(Clone, Default, Debug)] ++pub(crate) struct EventArgs { ++ level: Option<Level>, ++ pub(crate) mode: FormatMode, ++} ++ + #[derive(Clone, Default, Debug)] + pub(crate) struct InstrumentArgs { + level: Option<Level>, +@@ -14,53 +22,16 @@ pub(crate) struct InstrumentArgs { + pub(crate) parent: Option<Expr>, + pub(crate) follows_from: Option<Expr>, + pub(crate) skips: HashSet<Ident>, +- pub(crate) skip_all: bool, + pub(crate) fields: Option<Fields>, +- pub(crate) err_mode: Option<FormatMode>, +- pub(crate) ret_mode: Option<FormatMode>, ++ pub(crate) err_args: Option<EventArgs>, ++ pub(crate) ret_args: Option<EventArgs>, + /// Errors describing any unrecognized parse inputs that we skipped. + parse_warnings: Vec<syn::Error>, + } + + impl InstrumentArgs { +- pub(crate) fn level(&self) -> impl ToTokens { +- fn is_level(lit: &LitInt, expected: u64) -> bool { +- match lit.base10_parse::<u64>() { +- Ok(value) => value == expected, +- Err(_) => false, +- } +- } +- +- match &self.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 => quote!(tracing::Level::INFO), +- } ++ pub(crate) fn level(&self) -> Level { ++ self.level.clone().unwrap_or(Level::Info) + } + + pub(crate) fn target(&self) -> impl ToTokens { +@@ -146,20 +117,8 @@ impl Parse for InstrumentArgs { + if !args.skips.is_empty() { + return Err(input.error("expected only a single `skip` argument")); + } +- if args.skip_all { +- return Err(input.error("expected either `skip` or `skip_all` argument")); +- } + let Skips(skips) = input.parse()?; + args.skips = skips; +- } else if lookahead.peek(kw::skip_all) { +- if args.skip_all { +- return Err(input.error("expected only a single `skip_all` argument")); +- } +- if !args.skips.is_empty() { +- return Err(input.error("expected either `skip` or `skip_all` argument")); +- } +- let _ = input.parse::<kw::skip_all>()?; +- args.skip_all = true; + } else if lookahead.peek(kw::fields) { + if args.fields.is_some() { + return Err(input.error("expected only a single `fields` argument")); +@@ -167,12 +126,12 @@ impl Parse for InstrumentArgs { + args.fields = Some(input.parse()?); + } else if lookahead.peek(kw::err) { + let _ = input.parse::<kw::err>(); +- let mode = FormatMode::parse(input)?; +- args.err_mode = Some(mode); ++ let err_args = EventArgs::parse(input)?; ++ args.err_args = Some(err_args); + } else if lookahead.peek(kw::ret) { + let _ = input.parse::<kw::ret>()?; +- let mode = FormatMode::parse(input)?; +- args.ret_mode = Some(mode); ++ let ret_args = EventArgs::parse(input)?; ++ args.ret_args = Some(ret_args); + } else if lookahead.peek(Token![,]) { + let _ = input.parse::<Token![,]>()?; + } else { +@@ -190,6 +149,55 @@ impl Parse for InstrumentArgs { + } + } + ++impl EventArgs { ++ pub(crate) fn level(&self, default: Level) -> Level { ++ self.level.clone().unwrap_or(default) ++ } ++} ++ ++impl Parse for EventArgs { ++ fn parse(input: ParseStream<'_>) -> syn::Result<Self> { ++ if !input.peek(syn::token::Paren) { ++ return Ok(Self::default()); ++ } ++ let content; ++ let _ = syn::parenthesized!(content in input); ++ let mut result = Self::default(); ++ let mut parse_one_arg = ++ || { ++ let lookahead = content.lookahead1(); ++ if lookahead.peek(kw::level) { ++ if result.level.is_some() { ++ return Err(content.error("expected only a single `level` argument")); ++ } ++ result.level = Some(content.parse()?); ++ } else if result.mode != FormatMode::default() { ++ return Err(content.error("expected only a single format argument")); ++ } else if let Some(ident) = content.parse::<Option<Ident>>()? { ++ match ident.to_string().as_str() { ++ "Debug" => result.mode = FormatMode::Debug, ++ "Display" => result.mode = FormatMode::Display, ++ _ => return Err(syn::Error::new( ++ ident.span(), ++ "unknown event formatting mode, expected either `Debug` or `Display`", ++ )), ++ } ++ } ++ Ok(()) ++ }; ++ parse_one_arg()?; ++ if !content.is_empty() { ++ if content.lookahead1().peek(Token![,]) { ++ let _ = content.parse::<Token![,]>()?; ++ parse_one_arg()?; ++ } else { ++ return Err(content.error("expected `,` or `)`")); ++ } ++ } ++ Ok(result) ++ } ++} ++ + struct StrArg<T> { + value: LitStr, + _p: std::marker::PhantomData<T>, +@@ -224,6 +232,19 @@ impl<T: Parse> Parse for ExprArg<T> { + } + } + ++struct IdentOrSelf(Ident); ++impl Parse for IdentOrSelf { ++ fn parse(input: ParseStream<'_>) -> syn::Result<Self> { ++ Ok(Self( ++ if let Ok(self_token) = input.parse::<Token![self]>() { ++ Ident::new("self", self_token.span) ++ } else { ++ input.parse()? ++ }, ++ )) ++ } ++} ++ + struct Skips(HashSet<Ident>); + + impl Parse for Skips { +@@ -231,16 +252,16 @@ impl Parse for Skips { + let _ = input.parse::<kw::skip>(); + let content; + let _ = syn::parenthesized!(content in input); +- let names: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse_any)?; ++ let names: Punctuated<IdentOrSelf, Token![,]> = Punctuated::parse_terminated(&content)?; + let mut skips = HashSet::new(); + for name in names { +- if skips.contains(&name) { ++ if skips.contains(&name.0) { + return Err(syn::Error::new( +- name.span(), ++ name.0.span(), + "tried to skip the same field twice", + )); + } else { +- skips.insert(name); ++ skips.insert(name.0); + } + } + Ok(Self(skips)) +@@ -260,27 +281,6 @@ impl Default for FormatMode { + } + } + +-impl Parse for FormatMode { +- fn parse(input: ParseStream<'_>) -> syn::Result<Self> { +- if !input.peek(syn::token::Paren) { +- return Ok(FormatMode::default()); +- } +- let content; +- let _ = syn::parenthesized!(content in input); +- let maybe_mode: Option<Ident> = content.parse()?; +- maybe_mode.map_or(Ok(FormatMode::default()), |ident| { +- match ident.to_string().as_str() { +- "Debug" => Ok(FormatMode::Debug), +- "Display" => Ok(FormatMode::Display), +- _ => Err(syn::Error::new( +- ident.span(), +- "unknown error mode, must be Debug or Display", +- )), +- } +- }) +- } +-} +- + #[derive(Clone, Debug)] + pub(crate) struct Fields(pub(crate) Punctuated<Field, Token![,]>); + +@@ -303,7 +303,7 @@ impl Parse for Fields { + let _ = input.parse::<kw::fields>(); + let content; + let _ = syn::parenthesized!(content in input); +- let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?; ++ let fields: Punctuated<_, Token![,]> = Punctuated::parse_terminated(&content)?; + Ok(Self(fields)) + } + } +@@ -376,9 +376,12 @@ impl ToTokens for FieldKind { + } + + #[derive(Clone, Debug)] +-enum Level { +- Str(LitStr), +- Int(LitInt), ++pub(crate) enum Level { ++ Trace, ++ Debug, ++ Info, ++ Warn, ++ Error, + Path(Path), + } + +@@ -388,9 +391,37 @@ impl Parse for Level { + let _ = input.parse::<Token![=]>()?; + let lookahead = input.lookahead1(); + if lookahead.peek(LitStr) { +- Ok(Self::Str(input.parse()?)) ++ let str: LitStr = input.parse()?; ++ match str.value() { ++ s if s.eq_ignore_ascii_case("trace") => Ok(Level::Trace), ++ s if s.eq_ignore_ascii_case("debug") => Ok(Level::Debug), ++ s if s.eq_ignore_ascii_case("info") => Ok(Level::Info), ++ s if s.eq_ignore_ascii_case("warn") => Ok(Level::Warn), ++ s if s.eq_ignore_ascii_case("error") => Ok(Level::Error), ++ _ => Err(input.error( ++ "unknown verbosity level, expected one of \"trace\", \ ++ \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", ++ )), ++ } + } else if lookahead.peek(LitInt) { +- Ok(Self::Int(input.parse()?)) ++ fn is_level(lit: &LitInt, expected: u64) -> bool { ++ match lit.base10_parse::<u64>() { ++ Ok(value) => value == expected, ++ Err(_) => false, ++ } ++ } ++ let int: LitInt = input.parse()?; ++ match &int { ++ i if is_level(i, 1) => Ok(Level::Trace), ++ i if is_level(i, 2) => Ok(Level::Debug), ++ i if is_level(i, 3) => Ok(Level::Info), ++ i if is_level(i, 4) => Ok(Level::Warn), ++ i if is_level(i, 5) => Ok(Level::Error), ++ _ => Err(input.error( ++ "unknown verbosity level, expected one of \"trace\", \ ++ \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", ++ )), ++ } + } else if lookahead.peek(Ident) { + Ok(Self::Path(input.parse()?)) + } else { +@@ -399,10 +430,22 @@ impl Parse for Level { + } + } + ++impl ToTokens for Level { ++ fn to_tokens(&self, tokens: &mut TokenStream) { ++ match self { ++ Level::Trace => tokens.extend(quote!(tracing::Level::TRACE)), ++ Level::Debug => tokens.extend(quote!(tracing::Level::DEBUG)), ++ Level::Info => tokens.extend(quote!(tracing::Level::INFO)), ++ Level::Warn => tokens.extend(quote!(tracing::Level::WARN)), ++ Level::Error => tokens.extend(quote!(tracing::Level::ERROR)), ++ Level::Path(ref pat) => tokens.extend(quote!(#pat)), ++ } ++ } ++} ++ + mod kw { + syn::custom_keyword!(fields); + syn::custom_keyword!(skip); +- syn::custom_keyword!(skip_all); + syn::custom_keyword!(level); + syn::custom_keyword!(target); + syn::custom_keyword!(parent); +diff --git a/src/expand.rs b/src/expand.rs +index 7005b44..a4a463a 100644 +--- a/src/expand.rs ++++ b/src/expand.rs +@@ -10,7 +10,7 @@ use syn::{ + }; + + use crate::{ +- attr::{Field, Fields, FormatMode, InstrumentArgs}, ++ attr::{Field, Fields, FormatMode, InstrumentArgs, Level}, + MaybeItemFn, MaybeItemFnRef, + }; + +@@ -64,7 +64,7 @@ pub(crate) fn gen_function<'a, B: ToTokens + 'a>( + // unreachable, but does affect inference, so it needs to be written + // exactly that way for it to do its magic. + let fake_return_edge = quote_spanned! {return_span=> +- #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value)] ++ #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value, clippy::unreachable)] + if false { + let __tracing_attr_fake_return: #return_type = + unreachable!("this is just for type inference, and is unreachable code"); +@@ -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(); + + let follows_from = args.follows_from.iter(); + let follows_from = quote! { +@@ -134,7 +135,7 @@ fn gen_block<B: ToTokens>( + .into_iter() + .flat_map(|param| match param { + FnArg::Typed(PatType { pat, ty, .. }) => { +- param_names(*pat, RecordType::parse_from_ty(&*ty)) ++ param_names(*pat, RecordType::parse_from_ty(&ty)) + } + FnArg::Receiver(_) => Box::new(iter::once(( + Ident::new("self", param.span()), +@@ -178,7 +179,7 @@ fn gen_block<B: ToTokens>( + let quoted_fields: Vec<_> = param_names + .iter() + .filter(|(param, _)| { +- if args.skip_all || args.skips.contains(param) { ++ if args.skips.contains(param) { + return false; + } + +@@ -232,21 +233,33 @@ fn gen_block<B: ToTokens>( + + let target = args.target(); + +- let err_event = match args.err_mode { +- Some(FormatMode::Default) | Some(FormatMode::Display) => { +- Some(quote!(tracing::error!(target: #target, error = %e))) ++ let err_event = match args.err_args { ++ Some(event_args) => { ++ let level_tokens = event_args.level(Level::Error); ++ match event_args.mode { ++ FormatMode::Default | FormatMode::Display => Some(quote!( ++ tracing::event!(target: #target, #level_tokens, error = %e) ++ )), ++ FormatMode::Debug => Some(quote!( ++ tracing::event!(target: #target, #level_tokens, error = ?e) ++ )), ++ } + } +- Some(FormatMode::Debug) => Some(quote!(tracing::error!(target: #target, error = ?e))), + _ => None, + }; + +- let ret_event = match args.ret_mode { +- Some(FormatMode::Display) => Some(quote!( +- tracing::event!(target: #target, #level, return = %x) +- )), +- Some(FormatMode::Default) | Some(FormatMode::Debug) => Some(quote!( +- tracing::event!(target: #target, #level, return = ?x) +- )), ++ let ret_event = match args.ret_args { ++ Some(event_args) => { ++ let level_tokens = event_args.level(args_level); ++ match event_args.mode { ++ FormatMode::Display => Some(quote!( ++ tracing::event!(target: #target, #level_tokens, return = %x) ++ )), ++ FormatMode::Default | FormatMode::Debug => Some(quote!( ++ tracing::event!(target: #target, #level_tokens, return = ?x) ++ )), ++ } ++ } + _ => None, + }; + +@@ -464,10 +477,7 @@ fn param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Id + .into_iter() + .flat_map(|p| param_names(p, RecordType::Debug)), + ), +- Pat::TupleStruct(PatTupleStruct { +- pat: PatTuple { elems, .. }, +- .. +- }) => Box::new( ++ Pat::TupleStruct(PatTupleStruct { elems, .. }) => Box::new( + elems + .into_iter() + .flat_map(|p| param_names(p, RecordType::Debug)), +@@ -551,7 +561,7 @@ impl<'block> AsyncInfo<'block> { + // last expression of the block: it determines the return value of the + // block, this is quite likely a `Box::pin` statement or an async block + let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| { +- if let Stmt::Expr(expr) = stmt { ++ if let Stmt::Expr(expr, _) = stmt { + Some((stmt, expr)) + } else { + None +diff --git a/src/lib.rs b/src/lib.rs +index f5974e4..4b92520 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -12,11 +12,11 @@ + //! + //! ## Usage + //! +-//! First, add this to your `Cargo.toml`: ++//! In the `Cargo.toml`: + //! + //! ```toml + //! [dependencies] +-//! tracing-attributes = "0.1.23" ++//! tracing = "0.1" + //! ``` + //! + //! The [`#[instrument]`][instrument] attribute can now be added to a function +@@ -24,7 +24,7 @@ + //! called. For example: + //! + //! ``` +-//! use tracing_attributes::instrument; ++//! use tracing::instrument; + //! + //! #[instrument] + //! pub fn my_function(my_arg: usize) { +@@ -35,8 +35,8 @@ + //! ``` + //! + //! [`tracing`]: https://crates.io/crates/tracing ++//! [instrument]: macro@instrument + //! [span]: https://docs.rs/tracing/latest/tracing/span/index.html +-//! [instrument]: macro@self::instrument + //! + //! ## Supported Rust Versions + //! +@@ -52,19 +52,17 @@ + //! supported compiler version is not considered a semver breaking change as + //! long as doing so complies with this policy. + //! +-#![doc(html_root_url = "https://docs.rs/tracing-attributes/0.1.23")] + #![doc( + html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", ++ html_favicon_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/favicon.ico", + issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" + )] +-#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))] + #![warn( + missing_debug_implementations, + missing_docs, + rust_2018_idioms, + unreachable_pub, + bad_style, +- const_err, + dead_code, + improper_ctypes, + non_shorthand_field_patterns, +@@ -79,12 +77,9 @@ + unused_parens, + while_true + )] +-// TODO: once `tracing` bumps its MSRV to 1.42, remove this allow. +-#![allow(unused)] +-extern crate proc_macro; + + use proc_macro2::TokenStream; +-use quote::ToTokens; ++use quote::{quote, ToTokens}; + use syn::parse::{Parse, ParseStream}; + use syn::{Attribute, ItemFn, Signature, Visibility}; + +@@ -93,7 +88,7 @@ mod expand; + /// Instruments a function to create and enter a `tracing` [span] every time + /// the function is called. + /// +-/// Unless overriden, a span with the [`INFO`] [level] will be generated. ++/// Unless overriden, a span with `info` level will be generated. + /// The generated span's name will be the name of the function. + /// By default, all arguments to the function are included as fields on the + /// span. Arguments that are `tracing` [primitive types] implementing the +@@ -101,216 +96,27 @@ mod expand; + /// not implement `Value` will be recorded using [`std::fmt::Debug`]. + /// + /// [primitive types]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html#foreign-impls +-/// [`Value` trait]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html. +-/// +-/// # Overriding Span Attributes +-/// +-/// To change the [name] of the generated span, add a `name` argument to the +-/// `#[instrument]` macro, followed by an equals sign and a string literal. For +-/// example: +-/// +-/// ``` +-/// # use tracing_attributes::instrument; +-/// +-/// // The generated span's name will be "my_span" rather than "my_function". +-/// #[instrument(name = "my_span")] +-/// pub fn my_function() { +-/// // ... do something incredibly interesting and important ... +-/// } +-/// ``` +-/// +-/// To override the [target] of the generated span, add a `target` argument to +-/// the `#[instrument]` macro, followed by an equals sign and a string literal +-/// for the new target. The [module path] is still recorded separately. For +-/// example: +-/// +-/// ``` +-/// pub mod my_module { +-/// # use tracing_attributes::instrument; +-/// // The generated span's target will be "my_crate::some_special_target", +-/// // rather than "my_crate::my_module". +-/// #[instrument(target = "my_crate::some_special_target")] +-/// pub fn my_function() { +-/// // ... all kinds of neat code in here ... +-/// } +-/// } +-/// ``` +-/// +-/// Finally, to override the [level] of the generated span, add a `level` +-/// argument, followed by an equals sign and a string literal with the name of +-/// the desired level. Level names are not case sensitive. For example: +-/// +-/// ``` +-/// # use tracing_attributes::instrument; +-/// // The span's level will be TRACE rather than INFO. +-/// #[instrument(level = "trace")] +-/// pub fn my_function() { +-/// // ... I have written a truly marvelous implementation of this function, +-/// // which this example is too narrow to contain ... +-/// } +-/// ``` +-/// +-/// # Skipping Fields +-/// +-/// To skip recording one or more arguments to a function or method, pass +-/// the argument's name inside the `skip()` argument on the `#[instrument]` +-/// macro. This can be used when an argument to an instrumented function does +-/// not implement [`fmt::Debug`], or to exclude an argument with a verbose or +-/// costly `Debug` implementation. Note that: ++/// [`Value` trait]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html + /// ++/// To skip recording a function's or method's argument, pass the argument's name ++/// to the `skip` argument on the `#[instrument]` macro. For example, ++/// `skip` can be used when an argument to an instrumented function does ++/// not implement [`fmt::Debug`], or to exclude an argument with a verbose ++/// or costly Debug implementation. Note that: + /// - multiple argument names can be passed to `skip`. + /// - arguments passed to `skip` do _not_ need to implement `fmt::Debug`. + /// +-/// You can also use `skip_all` to skip all arguments. +-/// +-/// ## Examples +-/// +-/// ``` +-/// # use tracing_attributes::instrument; +-/// # use std::collections::HashMap; +-/// // This type doesn't implement `fmt::Debug`! +-/// struct NonDebug; +-/// +-/// // `arg` will be recorded, while `non_debug` will not. +-/// #[instrument(skip(non_debug))] +-/// fn my_function(arg: usize, non_debug: NonDebug) { +-/// // ... +-/// } +-/// +-/// // These arguments are huge +-/// #[instrument(skip_all)] +-/// fn my_big_data_function(large: Vec<u8>, also_large: HashMap<String, String>) { +-/// // ... +-/// } +-/// ``` +-/// +-/// Skipping the `self` parameter: +-/// +-/// ``` +-/// # use tracing_attributes::instrument; +-/// #[derive(Debug)] +-/// struct MyType { +-/// data: Vec<u8>, // Suppose this buffer is often quite long... +-/// } +-/// +-/// impl MyType { +-/// // Suppose we don't want to print an entire kilobyte of `data` +-/// // every time this is called... +-/// #[instrument(skip(self))] +-/// pub fn my_method(&mut self, an_interesting_argument: usize) { +-/// // ... do something (hopefully, using all that `data`!) +-/// } +-/// } +-/// ``` +-/// +-/// # Adding Fields +-/// +-/// Additional fields (key-value pairs with arbitrary data) may be added to the +-/// generated span using the `fields` argument on the `#[instrument]` macro. Any +-/// Rust expression can be used as a field value in this manner. These +-/// expressions will be evaluated at the beginning of the function's body, so +-/// arguments to the function may be used in these expressions. Field names may +-/// also be specified *without* values. Doing so will result in an [empty field] +-/// whose value may be recorded later within the function body. +-/// +-/// This supports the same [field syntax] as the `span!` and `event!` macros. ++/// Additional fields (key-value pairs with arbitrary data) can be passed to ++/// to the generated span through the `fields` argument on the ++/// `#[instrument]` macro. Strings, integers or boolean literals are accepted values ++/// for each field. The name of the field must be a single valid Rust ++/// identifier, nested (dotted) field names are not supported. + /// + /// Note that overlap between the names of fields and (non-skipped) arguments + /// will result in a compile error. + /// +-/// ## Examples +-/// +-/// Adding a new field based on the value of an argument: +-/// +-/// ``` +-/// # use tracing_attributes::instrument; +-/// +-/// // This will record a field named "i" with the value of `i` *and* a field +-/// // named "next" with the value of `i` + 1. +-/// #[instrument(fields(next = i + 1))] +-/// pub fn my_function(i: usize) { +-/// // ... +-/// } +-/// ``` +-/// +-/// Recording specific properties of a struct as their own fields: +-/// +-/// ``` +-/// # mod http { +-/// # pub struct Error; +-/// # pub struct Response<B> { pub(super) _b: std::marker::PhantomData<B> } +-/// # pub struct Request<B> { _b: B } +-/// # impl<B> std::fmt::Debug for Request<B> { +-/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +-/// # f.pad("request") +-/// # } +-/// # } +-/// # impl<B> Request<B> { +-/// # pub fn uri(&self) -> &str { "fake" } +-/// # pub fn method(&self) -> &str { "GET" } +-/// # } +-/// # } +-/// # use tracing_attributes::instrument; +-/// +-/// // This will record the request's URI and HTTP method as their own separate +-/// // fields. +-/// #[instrument(fields(http.uri = req.uri(), http.method = req.method()))] +-/// pub fn handle_request<B>(req: http::Request<B>) -> http::Response<B> { +-/// // ... handle the request ... +-/// # http::Response { _b: std::marker::PhantomData } +-/// } +-/// ``` +-/// +-/// This can be used in conjunction with `skip` or `skip_all` to record only +-/// some fields of a struct: +-/// ``` +-/// # use tracing_attributes::instrument; +-/// // Remember the struct with the very large `data` field from the earlier +-/// // example? Now it also has a `name`, which we might want to include in +-/// // our span. +-/// #[derive(Debug)] +-/// struct MyType { +-/// name: &'static str, +-/// data: Vec<u8>, +-/// } +-/// +-/// impl MyType { +-/// // This will skip the `data` field, but will include `self.name`, +-/// // formatted using `fmt::Display`. +-/// #[instrument(skip(self), fields(self.name = %self.name))] +-/// pub fn my_method(&mut self, an_interesting_argument: usize) { +-/// // ... do something (hopefully, using all that `data`!) +-/// } +-/// } +-/// ``` +-/// +-/// Adding an empty field to be recorded later: +-/// +-/// ``` +-/// # use tracing_attributes::instrument; +-/// +-/// // This function does a very interesting and important mathematical calculation. +-/// // Suppose we want to record both the inputs to the calculation *and* its result... +-/// #[instrument(fields(result))] +-/// pub fn do_calculation(input_1: usize, input_2: usize) -> usize { +-/// // Rerform the calculation. +-/// let result = input_1 + input_2; +-/// +-/// // Record the result as part of the current span. +-/// tracing::Span::current().record("result", &result); +-/// +-/// // Now, the result will also be included on this event! +-/// tracing::info!("calculation complete!"); +-/// +-/// // ... etc ... +-/// # 0 +-/// } +-/// ``` +-/// + /// # Examples +-/// + /// Instrumenting a function: +-/// + /// ``` + /// # use tracing_attributes::instrument; + /// #[instrument] +@@ -324,11 +130,15 @@ mod expand; + /// Setting the level for the generated span: + /// ``` + /// # use tracing_attributes::instrument; +-/// #[instrument(level = "debug")] ++/// # use tracing::Level; ++/// #[instrument(level = Level::DEBUG)] + /// pub fn my_function() { + /// // ... + /// } + /// ``` ++/// Levels can be specified either with [`Level`] constants, literal strings ++/// (e.g., `"debug"`, `"info"`) or numerically (1—5, corresponding to [`Level::TRACE`]—[`Level::ERROR`]). ++/// + /// Overriding the generated span's name: + /// ``` + /// # use tracing_attributes::instrument; +@@ -399,7 +209,7 @@ mod expand; + /// } + /// ``` + /// +-/// To add an additional context to the span, pass key-value pairs to `fields`: ++/// To add additional context to the span, pass key-value pairs to `fields`: + /// + /// ``` + /// # use tracing_attributes::instrument; +@@ -419,10 +229,21 @@ mod expand; + /// 42 + /// } + /// ``` +-/// The return value event will have the same level as the span generated by `#[instrument]`. +-/// By default, this will be [`INFO`], but if the level is overridden, the event will be at the same ++/// The level of the return value event defaults to the same level as the span generated by `#[instrument]`. ++/// By default, this will be `TRACE`, but if the span level is overridden, the event will be at the same + /// level. + /// ++/// It's also possible to override the level for the `ret` event independently: ++/// ++/// ``` ++/// # use tracing_attributes::instrument; ++/// # use tracing::Level; ++/// #[instrument(ret(level = Level::WARN))] ++/// fn my_function() -> i32 { ++/// 42 ++/// } ++/// ``` ++/// + /// **Note**: if the function returns a `Result<T, E>`, `ret` will record returned values if and + /// only if the function returns [`Result::Ok`]. + /// +@@ -438,8 +259,8 @@ mod expand; + /// } + /// ``` + /// +-/// 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`: ++/// If the function returns a `Result<T, E>` and `E` implements `std::fmt::Display`, adding ++/// `err` or `err(Display)` will emit error events when the function returns `Err`: + /// + /// ``` + /// # use tracing_attributes::instrument; +@@ -449,9 +270,22 @@ mod expand; + /// } + /// ``` + /// ++/// The level of the error value event defaults to `ERROR`. ++/// ++/// Similarly, overriding the level of the `err` event : ++/// ++/// ``` ++/// # use tracing_attributes::instrument; ++/// # use tracing::Level; ++/// #[instrument(err(level = Level::INFO))] ++/// 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)`: ++/// instead by writing `err(Debug)`: + /// + /// ``` + /// # use tracing_attributes::instrument; +@@ -510,44 +344,21 @@ mod expand; + /// } + /// ``` + /// +-/// Note than on `async-trait` <= 0.1.43, references to the `Self` +-/// type inside the `fields` argument were only allowed when the instrumented +-/// function is a method (i.e., the function receives `self` as an argument). +-/// For example, this *used to not work* because the instrument function +-/// didn't receive `self`: +-/// ``` +-/// # use tracing::instrument; +-/// use async_trait::async_trait; +-/// +-/// #[async_trait] +-/// pub trait Bar { +-/// async fn bar(); +-/// } +-/// +-/// #[derive(Debug)] +-/// struct BarImpl(usize); ++/// `const fn` cannot be instrumented, and will result in a compilation failure: + /// +-/// #[async_trait] +-/// impl Bar for BarImpl { +-/// #[instrument(fields(tmp = std::any::type_name::<Self>()))] +-/// async fn bar() {} +-/// } ++/// ```compile_fail ++/// # use tracing_attributes::instrument; ++/// #[instrument] ++/// const fn my_const_function() {} + /// ``` +-/// Instead, you should manually rewrite any `Self` types as the type for +-/// which you implement the trait: `#[instrument(fields(tmp = std::any::type_name::<Bar>()))]` +-/// (or maybe you can just bump `async-trait`). + /// + /// [span]: https://docs.rs/tracing/latest/tracing/span/index.html +-/// [name]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.name +-/// [target]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.target +-/// [level]: https://docs.rs/tracing/latest/tracing/struct.Level.html +-/// [module path]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.module_path +-/// [`INFO`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.INFO +-/// [empty field]: https://docs.rs/tracing/latest/tracing/field/struct.Empty.html +-/// [field syntax]: https://docs.rs/tracing/latest/tracing/#recording-fields + /// [`follows_from`]: https://docs.rs/tracing/latest/tracing/struct.Span.html#method.follows_from + /// [`tracing`]: https://github.com/tokio-rs/tracing + /// [`fmt::Debug`]: std::fmt::Debug ++/// [`Level`]: https://docs.rs/tracing/latest/tracing/struct.Level.html ++/// [`Level::TRACE`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.TRACE ++/// [`Level::ERROR`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.ERROR + #[proc_macro_attribute] + pub fn instrument( + args: proc_macro::TokenStream, +@@ -584,6 +395,13 @@ fn instrument_precise( + let input = syn::parse::<ItemFn>(item)?; + let instrumented_function_name = input.sig.ident.to_string(); + ++ if input.sig.constness.is_some() { ++ return Ok(quote! { ++ compile_error!("the `#[instrument]` attribute may not be used with `const fn`s") ++ } ++ .into()); ++ } ++ + // check for async_trait-like patterns in the block, and instrument + // the future instead of the wrapper + if let Some(async_like) = expand::AsyncInfo::from_fn(&input) { diff --git a/src/attr.rs b/src/attr.rs index ff875e1..9b778c8 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -6,6 +6,14 @@ use quote::{quote, quote_spanned, ToTokens}; use syn::ext::IdentExt as _; use syn::parse::{Parse, ParseStream}; +/// Arguments to `#[instrument(err(...))]` and `#[instrument(ret(...))]` which describe how the +/// return value event should be emitted. +#[derive(Clone, Default, Debug)] +pub(crate) struct EventArgs { + level: Option<Level>, + pub(crate) mode: FormatMode, +} + #[derive(Clone, Default, Debug)] pub(crate) struct InstrumentArgs { level: Option<Level>, @@ -14,53 +22,16 @@ pub(crate) struct InstrumentArgs { pub(crate) parent: Option<Expr>, pub(crate) follows_from: Option<Expr>, pub(crate) skips: HashSet<Ident>, - pub(crate) skip_all: bool, pub(crate) fields: Option<Fields>, - pub(crate) err_mode: Option<FormatMode>, - pub(crate) ret_mode: Option<FormatMode>, + pub(crate) err_args: Option<EventArgs>, + pub(crate) ret_args: Option<EventArgs>, /// Errors describing any unrecognized parse inputs that we skipped. parse_warnings: Vec<syn::Error>, } impl InstrumentArgs { - pub(crate) fn level(&self) -> impl ToTokens { - fn is_level(lit: &LitInt, expected: u64) -> bool { - match lit.base10_parse::<u64>() { - Ok(value) => value == expected, - Err(_) => false, - } - } - - match &self.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 => quote!(tracing::Level::INFO), - } + pub(crate) fn level(&self) -> Level { + self.level.clone().unwrap_or(Level::Info) } pub(crate) fn target(&self) -> impl ToTokens { @@ -146,20 +117,8 @@ impl Parse for InstrumentArgs { if !args.skips.is_empty() { return Err(input.error("expected only a single `skip` argument")); } - if args.skip_all { - return Err(input.error("expected either `skip` or `skip_all` argument")); - } let Skips(skips) = input.parse()?; args.skips = skips; - } else if lookahead.peek(kw::skip_all) { - if args.skip_all { - return Err(input.error("expected only a single `skip_all` argument")); - } - if !args.skips.is_empty() { - return Err(input.error("expected either `skip` or `skip_all` argument")); - } - let _ = input.parse::<kw::skip_all>()?; - args.skip_all = true; } else if lookahead.peek(kw::fields) { if args.fields.is_some() { return Err(input.error("expected only a single `fields` argument")); @@ -167,12 +126,12 @@ impl Parse for InstrumentArgs { args.fields = Some(input.parse()?); } else if lookahead.peek(kw::err) { let _ = input.parse::<kw::err>(); - let mode = FormatMode::parse(input)?; - args.err_mode = Some(mode); + let err_args = EventArgs::parse(input)?; + args.err_args = Some(err_args); } else if lookahead.peek(kw::ret) { let _ = input.parse::<kw::ret>()?; - let mode = FormatMode::parse(input)?; - args.ret_mode = Some(mode); + let ret_args = EventArgs::parse(input)?; + args.ret_args = Some(ret_args); } else if lookahead.peek(Token![,]) { let _ = input.parse::<Token![,]>()?; } else { @@ -190,6 +149,55 @@ impl Parse for InstrumentArgs { } } +impl EventArgs { + pub(crate) fn level(&self, default: Level) -> Level { + self.level.clone().unwrap_or(default) + } +} + +impl Parse for EventArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + if !input.peek(syn::token::Paren) { + return Ok(Self::default()); + } + let content; + let _ = syn::parenthesized!(content in input); + let mut result = Self::default(); + let mut parse_one_arg = + || { + let lookahead = content.lookahead1(); + if lookahead.peek(kw::level) { + if result.level.is_some() { + return Err(content.error("expected only a single `level` argument")); + } + result.level = Some(content.parse()?); + } else if result.mode != FormatMode::default() { + return Err(content.error("expected only a single format argument")); + } else if let Some(ident) = content.parse::<Option<Ident>>()? { + match ident.to_string().as_str() { + "Debug" => result.mode = FormatMode::Debug, + "Display" => result.mode = FormatMode::Display, + _ => return Err(syn::Error::new( + ident.span(), + "unknown event formatting mode, expected either `Debug` or `Display`", + )), + } + } + Ok(()) + }; + parse_one_arg()?; + if !content.is_empty() { + if content.lookahead1().peek(Token![,]) { + let _ = content.parse::<Token![,]>()?; + parse_one_arg()?; + } else { + return Err(content.error("expected `,` or `)`")); + } + } + Ok(result) + } +} + struct StrArg<T> { value: LitStr, _p: std::marker::PhantomData<T>, @@ -224,6 +232,19 @@ impl<T: Parse> Parse for ExprArg<T> { } } +struct IdentOrSelf(Ident); +impl Parse for IdentOrSelf { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + Ok(Self( + if let Ok(self_token) = input.parse::<Token![self]>() { + Ident::new("self", self_token.span) + } else { + input.parse()? + }, + )) + } +} + struct Skips(HashSet<Ident>); impl Parse for Skips { @@ -231,16 +252,16 @@ impl Parse for Skips { let _ = input.parse::<kw::skip>(); let content; let _ = syn::parenthesized!(content in input); - let names: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse_any)?; + let names: Punctuated<IdentOrSelf, Token![,]> = Punctuated::parse_terminated(&content)?; let mut skips = HashSet::new(); for name in names { - if skips.contains(&name) { + if skips.contains(&name.0) { return Err(syn::Error::new( - name.span(), + name.0.span(), "tried to skip the same field twice", )); } else { - skips.insert(name); + skips.insert(name.0); } } Ok(Self(skips)) @@ -260,27 +281,6 @@ impl Default for FormatMode { } } -impl Parse for FormatMode { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - if !input.peek(syn::token::Paren) { - return Ok(FormatMode::default()); - } - let content; - let _ = syn::parenthesized!(content in input); - let maybe_mode: Option<Ident> = content.parse()?; - maybe_mode.map_or(Ok(FormatMode::default()), |ident| { - match ident.to_string().as_str() { - "Debug" => Ok(FormatMode::Debug), - "Display" => Ok(FormatMode::Display), - _ => Err(syn::Error::new( - ident.span(), - "unknown error mode, must be Debug or Display", - )), - } - }) - } -} - #[derive(Clone, Debug)] pub(crate) struct Fields(pub(crate) Punctuated<Field, Token![,]>); @@ -303,7 +303,7 @@ impl Parse for Fields { let _ = input.parse::<kw::fields>(); let content; let _ = syn::parenthesized!(content in input); - let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?; + let fields: Punctuated<_, Token![,]> = Punctuated::parse_terminated(&content)?; Ok(Self(fields)) } } @@ -376,9 +376,12 @@ impl ToTokens for FieldKind { } #[derive(Clone, Debug)] -enum Level { - Str(LitStr), - Int(LitInt), +pub(crate) enum Level { + Trace, + Debug, + Info, + Warn, + Error, Path(Path), } @@ -388,9 +391,37 @@ impl Parse for Level { let _ = input.parse::<Token![=]>()?; let lookahead = input.lookahead1(); if lookahead.peek(LitStr) { - Ok(Self::Str(input.parse()?)) + let str: LitStr = input.parse()?; + match str.value() { + s if s.eq_ignore_ascii_case("trace") => Ok(Level::Trace), + s if s.eq_ignore_ascii_case("debug") => Ok(Level::Debug), + s if s.eq_ignore_ascii_case("info") => Ok(Level::Info), + s if s.eq_ignore_ascii_case("warn") => Ok(Level::Warn), + s if s.eq_ignore_ascii_case("error") => Ok(Level::Error), + _ => Err(input.error( + "unknown verbosity level, expected one of \"trace\", \ + \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", + )), + } } else if lookahead.peek(LitInt) { - Ok(Self::Int(input.parse()?)) + fn is_level(lit: &LitInt, expected: u64) -> bool { + match lit.base10_parse::<u64>() { + Ok(value) => value == expected, + Err(_) => false, + } + } + let int: LitInt = input.parse()?; + match &int { + i if is_level(i, 1) => Ok(Level::Trace), + i if is_level(i, 2) => Ok(Level::Debug), + i if is_level(i, 3) => Ok(Level::Info), + i if is_level(i, 4) => Ok(Level::Warn), + i if is_level(i, 5) => Ok(Level::Error), + _ => Err(input.error( + "unknown verbosity level, expected one of \"trace\", \ + \"debug\", \"info\", \"warn\", or \"error\", or a number 1-5", + )), + } } else if lookahead.peek(Ident) { Ok(Self::Path(input.parse()?)) } else { @@ -399,10 +430,22 @@ impl Parse for Level { } } +impl ToTokens for Level { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Level::Trace => tokens.extend(quote!(tracing::Level::TRACE)), + Level::Debug => tokens.extend(quote!(tracing::Level::DEBUG)), + Level::Info => tokens.extend(quote!(tracing::Level::INFO)), + Level::Warn => tokens.extend(quote!(tracing::Level::WARN)), + Level::Error => tokens.extend(quote!(tracing::Level::ERROR)), + Level::Path(ref pat) => tokens.extend(quote!(#pat)), + } + } +} + mod kw { syn::custom_keyword!(fields); syn::custom_keyword!(skip); - syn::custom_keyword!(skip_all); syn::custom_keyword!(level); syn::custom_keyword!(target); syn::custom_keyword!(parent); diff --git a/src/expand.rs b/src/expand.rs index 7005b44..a4a463a 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -10,7 +10,7 @@ use syn::{ }; use crate::{ - attr::{Field, Fields, FormatMode, InstrumentArgs}, + attr::{Field, Fields, FormatMode, InstrumentArgs, Level}, MaybeItemFn, MaybeItemFnRef, }; @@ -64,7 +64,7 @@ pub(crate) fn gen_function<'a, B: ToTokens + 'a>( // unreachable, but does affect inference, so it needs to be written // exactly that way for it to do its magic. let fake_return_edge = quote_spanned! {return_span=> - #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value)] + #[allow(unreachable_code, clippy::diverging_sub_expression, clippy::let_unit_value, clippy::unreachable)] if false { let __tracing_attr_fake_return: #return_type = unreachable!("this is just for type inference, and is unreachable code"); @@ -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(); let follows_from = args.follows_from.iter(); let follows_from = quote! { @@ -134,7 +135,7 @@ fn gen_block<B: ToTokens>( .into_iter() .flat_map(|param| match param { FnArg::Typed(PatType { pat, ty, .. }) => { - param_names(*pat, RecordType::parse_from_ty(&*ty)) + param_names(*pat, RecordType::parse_from_ty(&ty)) } FnArg::Receiver(_) => Box::new(iter::once(( Ident::new("self", param.span()), @@ -178,7 +179,7 @@ fn gen_block<B: ToTokens>( let quoted_fields: Vec<_> = param_names .iter() .filter(|(param, _)| { - if args.skip_all || args.skips.contains(param) { + if args.skips.contains(param) { return false; } @@ -232,21 +233,33 @@ fn gen_block<B: ToTokens>( let target = args.target(); - let err_event = match args.err_mode { - Some(FormatMode::Default) | Some(FormatMode::Display) => { - Some(quote!(tracing::error!(target: #target, error = %e))) + let err_event = match args.err_args { + Some(event_args) => { + let level_tokens = event_args.level(Level::Error); + match event_args.mode { + FormatMode::Default | FormatMode::Display => Some(quote!( + tracing::event!(target: #target, #level_tokens, error = %e) + )), + FormatMode::Debug => Some(quote!( + tracing::event!(target: #target, #level_tokens, error = ?e) + )), + } } - Some(FormatMode::Debug) => Some(quote!(tracing::error!(target: #target, error = ?e))), _ => None, }; - let ret_event = match args.ret_mode { - Some(FormatMode::Display) => Some(quote!( - tracing::event!(target: #target, #level, return = %x) - )), - Some(FormatMode::Default) | Some(FormatMode::Debug) => Some(quote!( - tracing::event!(target: #target, #level, return = ?x) - )), + let ret_event = match args.ret_args { + Some(event_args) => { + let level_tokens = event_args.level(args_level); + match event_args.mode { + FormatMode::Display => Some(quote!( + tracing::event!(target: #target, #level_tokens, return = %x) + )), + FormatMode::Default | FormatMode::Debug => Some(quote!( + tracing::event!(target: #target, #level_tokens, return = ?x) + )), + } + } _ => None, }; @@ -464,10 +477,7 @@ fn param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Id .into_iter() .flat_map(|p| param_names(p, RecordType::Debug)), ), - Pat::TupleStruct(PatTupleStruct { - pat: PatTuple { elems, .. }, - .. - }) => Box::new( + Pat::TupleStruct(PatTupleStruct { elems, .. }) => Box::new( elems .into_iter() .flat_map(|p| param_names(p, RecordType::Debug)), @@ -551,7 +561,7 @@ impl<'block> AsyncInfo<'block> { // last expression of the block: it determines the return value of the // block, this is quite likely a `Box::pin` statement or an async block let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| { - if let Stmt::Expr(expr) = stmt { + if let Stmt::Expr(expr, _) = stmt { Some((stmt, expr)) } else { None @@ -12,11 +12,11 @@ //! //! ## Usage //! -//! First, add this to your `Cargo.toml`: +//! In the `Cargo.toml`: //! //! ```toml //! [dependencies] -//! tracing-attributes = "0.1.23" +//! tracing = "0.1" //! ``` //! //! The [`#[instrument]`][instrument] attribute can now be added to a function @@ -24,7 +24,7 @@ //! called. For example: //! //! ``` -//! use tracing_attributes::instrument; +//! use tracing::instrument; //! //! #[instrument] //! pub fn my_function(my_arg: usize) { @@ -35,8 +35,8 @@ //! ``` //! //! [`tracing`]: https://crates.io/crates/tracing +//! [instrument]: macro@instrument //! [span]: https://docs.rs/tracing/latest/tracing/span/index.html -//! [instrument]: macro@self::instrument //! //! ## Supported Rust Versions //! @@ -52,19 +52,17 @@ //! supported compiler version is not considered a semver breaking change as //! long as doing so complies with this policy. //! -#![doc(html_root_url = "https://docs.rs/tracing-attributes/0.1.23")] #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/logo-type.png", + html_favicon_url = "https://raw.githubusercontent.com/tokio-rs/tracing/master/assets/favicon.ico", issue_tracker_base_url = "https://github.com/tokio-rs/tracing/issues/" )] -#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))] #![warn( missing_debug_implementations, missing_docs, rust_2018_idioms, unreachable_pub, bad_style, - const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, @@ -79,12 +77,9 @@ unused_parens, while_true )] -// TODO: once `tracing` bumps its MSRV to 1.42, remove this allow. -#![allow(unused)] -extern crate proc_macro; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; use syn::{Attribute, ItemFn, Signature, Visibility}; @@ -93,7 +88,7 @@ mod expand; /// Instruments a function to create and enter a `tracing` [span] every time /// the function is called. /// -/// Unless overriden, a span with the [`INFO`] [level] will be generated. +/// Unless overriden, a span with `info` level will be generated. /// The generated span's name will be the name of the function. /// By default, all arguments to the function are included as fields on the /// span. Arguments that are `tracing` [primitive types] implementing the @@ -101,216 +96,27 @@ mod expand; /// not implement `Value` will be recorded using [`std::fmt::Debug`]. /// /// [primitive types]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html#foreign-impls -/// [`Value` trait]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html. -/// -/// # Overriding Span Attributes -/// -/// To change the [name] of the generated span, add a `name` argument to the -/// `#[instrument]` macro, followed by an equals sign and a string literal. For -/// example: -/// -/// ``` -/// # use tracing_attributes::instrument; -/// -/// // The generated span's name will be "my_span" rather than "my_function". -/// #[instrument(name = "my_span")] -/// pub fn my_function() { -/// // ... do something incredibly interesting and important ... -/// } -/// ``` -/// -/// To override the [target] of the generated span, add a `target` argument to -/// the `#[instrument]` macro, followed by an equals sign and a string literal -/// for the new target. The [module path] is still recorded separately. For -/// example: -/// -/// ``` -/// pub mod my_module { -/// # use tracing_attributes::instrument; -/// // The generated span's target will be "my_crate::some_special_target", -/// // rather than "my_crate::my_module". -/// #[instrument(target = "my_crate::some_special_target")] -/// pub fn my_function() { -/// // ... all kinds of neat code in here ... -/// } -/// } -/// ``` -/// -/// Finally, to override the [level] of the generated span, add a `level` -/// argument, followed by an equals sign and a string literal with the name of -/// the desired level. Level names are not case sensitive. For example: -/// -/// ``` -/// # use tracing_attributes::instrument; -/// // The span's level will be TRACE rather than INFO. -/// #[instrument(level = "trace")] -/// pub fn my_function() { -/// // ... I have written a truly marvelous implementation of this function, -/// // which this example is too narrow to contain ... -/// } -/// ``` -/// -/// # Skipping Fields -/// -/// To skip recording one or more arguments to a function or method, pass -/// the argument's name inside the `skip()` argument on the `#[instrument]` -/// macro. This can be used when an argument to an instrumented function does -/// not implement [`fmt::Debug`], or to exclude an argument with a verbose or -/// costly `Debug` implementation. Note that: +/// [`Value` trait]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html /// +/// To skip recording a function's or method's argument, pass the argument's name +/// to the `skip` argument on the `#[instrument]` macro. For example, +/// `skip` can be used when an argument to an instrumented function does +/// not implement [`fmt::Debug`], or to exclude an argument with a verbose +/// or costly Debug implementation. Note that: /// - multiple argument names can be passed to `skip`. /// - arguments passed to `skip` do _not_ need to implement `fmt::Debug`. /// -/// You can also use `skip_all` to skip all arguments. -/// -/// ## Examples -/// -/// ``` -/// # use tracing_attributes::instrument; -/// # use std::collections::HashMap; -/// // This type doesn't implement `fmt::Debug`! -/// struct NonDebug; -/// -/// // `arg` will be recorded, while `non_debug` will not. -/// #[instrument(skip(non_debug))] -/// fn my_function(arg: usize, non_debug: NonDebug) { -/// // ... -/// } -/// -/// // These arguments are huge -/// #[instrument(skip_all)] -/// fn my_big_data_function(large: Vec<u8>, also_large: HashMap<String, String>) { -/// // ... -/// } -/// ``` -/// -/// Skipping the `self` parameter: -/// -/// ``` -/// # use tracing_attributes::instrument; -/// #[derive(Debug)] -/// struct MyType { -/// data: Vec<u8>, // Suppose this buffer is often quite long... -/// } -/// -/// impl MyType { -/// // Suppose we don't want to print an entire kilobyte of `data` -/// // every time this is called... -/// #[instrument(skip(self))] -/// pub fn my_method(&mut self, an_interesting_argument: usize) { -/// // ... do something (hopefully, using all that `data`!) -/// } -/// } -/// ``` -/// -/// # Adding Fields -/// -/// Additional fields (key-value pairs with arbitrary data) may be added to the -/// generated span using the `fields` argument on the `#[instrument]` macro. Any -/// Rust expression can be used as a field value in this manner. These -/// expressions will be evaluated at the beginning of the function's body, so -/// arguments to the function may be used in these expressions. Field names may -/// also be specified *without* values. Doing so will result in an [empty field] -/// whose value may be recorded later within the function body. -/// -/// This supports the same [field syntax] as the `span!` and `event!` macros. +/// Additional fields (key-value pairs with arbitrary data) can be passed to +/// to the generated span through the `fields` argument on the +/// `#[instrument]` macro. Strings, integers or boolean literals are accepted values +/// for each field. The name of the field must be a single valid Rust +/// identifier, nested (dotted) field names are not supported. /// /// Note that overlap between the names of fields and (non-skipped) arguments /// will result in a compile error. /// -/// ## Examples -/// -/// Adding a new field based on the value of an argument: -/// -/// ``` -/// # use tracing_attributes::instrument; -/// -/// // This will record a field named "i" with the value of `i` *and* a field -/// // named "next" with the value of `i` + 1. -/// #[instrument(fields(next = i + 1))] -/// pub fn my_function(i: usize) { -/// // ... -/// } -/// ``` -/// -/// Recording specific properties of a struct as their own fields: -/// -/// ``` -/// # mod http { -/// # pub struct Error; -/// # pub struct Response<B> { pub(super) _b: std::marker::PhantomData<B> } -/// # pub struct Request<B> { _b: B } -/// # impl<B> std::fmt::Debug for Request<B> { -/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -/// # f.pad("request") -/// # } -/// # } -/// # impl<B> Request<B> { -/// # pub fn uri(&self) -> &str { "fake" } -/// # pub fn method(&self) -> &str { "GET" } -/// # } -/// # } -/// # use tracing_attributes::instrument; -/// -/// // This will record the request's URI and HTTP method as their own separate -/// // fields. -/// #[instrument(fields(http.uri = req.uri(), http.method = req.method()))] -/// pub fn handle_request<B>(req: http::Request<B>) -> http::Response<B> { -/// // ... handle the request ... -/// # http::Response { _b: std::marker::PhantomData } -/// } -/// ``` -/// -/// This can be used in conjunction with `skip` or `skip_all` to record only -/// some fields of a struct: -/// ``` -/// # use tracing_attributes::instrument; -/// // Remember the struct with the very large `data` field from the earlier -/// // example? Now it also has a `name`, which we might want to include in -/// // our span. -/// #[derive(Debug)] -/// struct MyType { -/// name: &'static str, -/// data: Vec<u8>, -/// } -/// -/// impl MyType { -/// // This will skip the `data` field, but will include `self.name`, -/// // formatted using `fmt::Display`. -/// #[instrument(skip(self), fields(self.name = %self.name))] -/// pub fn my_method(&mut self, an_interesting_argument: usize) { -/// // ... do something (hopefully, using all that `data`!) -/// } -/// } -/// ``` -/// -/// Adding an empty field to be recorded later: -/// -/// ``` -/// # use tracing_attributes::instrument; -/// -/// // This function does a very interesting and important mathematical calculation. -/// // Suppose we want to record both the inputs to the calculation *and* its result... -/// #[instrument(fields(result))] -/// pub fn do_calculation(input_1: usize, input_2: usize) -> usize { -/// // Rerform the calculation. -/// let result = input_1 + input_2; -/// -/// // Record the result as part of the current span. -/// tracing::Span::current().record("result", &result); -/// -/// // Now, the result will also be included on this event! -/// tracing::info!("calculation complete!"); -/// -/// // ... etc ... -/// # 0 -/// } -/// ``` -/// /// # Examples -/// /// Instrumenting a function: -/// /// ``` /// # use tracing_attributes::instrument; /// #[instrument] @@ -324,11 +130,15 @@ mod expand; /// Setting the level for the generated span: /// ``` /// # use tracing_attributes::instrument; -/// #[instrument(level = "debug")] +/// # use tracing::Level; +/// #[instrument(level = Level::DEBUG)] /// pub fn my_function() { /// // ... /// } /// ``` +/// Levels can be specified either with [`Level`] constants, literal strings +/// (e.g., `"debug"`, `"info"`) or numerically (1—5, corresponding to [`Level::TRACE`]—[`Level::ERROR`]). +/// /// Overriding the generated span's name: /// ``` /// # use tracing_attributes::instrument; @@ -399,7 +209,7 @@ mod expand; /// } /// ``` /// -/// To add an additional context to the span, pass key-value pairs to `fields`: +/// To add additional context to the span, pass key-value pairs to `fields`: /// /// ``` /// # use tracing_attributes::instrument; @@ -419,10 +229,21 @@ mod expand; /// 42 /// } /// ``` -/// The return value event will have the same level as the span generated by `#[instrument]`. -/// By default, this will be [`INFO`], but if the level is overridden, the event will be at the same +/// The level of the return value event defaults to the same level as the span generated by `#[instrument]`. +/// By default, this will be `TRACE`, but if the span level is overridden, the event will be at the same /// level. /// +/// It's also possible to override the level for the `ret` event independently: +/// +/// ``` +/// # use tracing_attributes::instrument; +/// # use tracing::Level; +/// #[instrument(ret(level = Level::WARN))] +/// fn my_function() -> i32 { +/// 42 +/// } +/// ``` +/// /// **Note**: if the function returns a `Result<T, E>`, `ret` will record returned values if and /// only if the function returns [`Result::Ok`]. /// @@ -438,8 +259,8 @@ mod expand; /// } /// ``` /// -/// 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`: +/// If the function returns a `Result<T, E>` and `E` implements `std::fmt::Display`, adding +/// `err` or `err(Display)` will emit error events when the function returns `Err`: /// /// ``` /// # use tracing_attributes::instrument; @@ -449,9 +270,22 @@ mod expand; /// } /// ``` /// +/// The level of the error value event defaults to `ERROR`. +/// +/// Similarly, overriding the level of the `err` event : +/// +/// ``` +/// # use tracing_attributes::instrument; +/// # use tracing::Level; +/// #[instrument(err(level = Level::INFO))] +/// 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)`: +/// instead by writing `err(Debug)`: /// /// ``` /// # use tracing_attributes::instrument; @@ -510,44 +344,21 @@ mod expand; /// } /// ``` /// -/// Note than on `async-trait` <= 0.1.43, references to the `Self` -/// type inside the `fields` argument were only allowed when the instrumented -/// function is a method (i.e., the function receives `self` as an argument). -/// For example, this *used to not work* because the instrument function -/// didn't receive `self`: -/// ``` -/// # use tracing::instrument; -/// use async_trait::async_trait; -/// -/// #[async_trait] -/// pub trait Bar { -/// async fn bar(); -/// } -/// -/// #[derive(Debug)] -/// struct BarImpl(usize); +/// `const fn` cannot be instrumented, and will result in a compilation failure: /// -/// #[async_trait] -/// impl Bar for BarImpl { -/// #[instrument(fields(tmp = std::any::type_name::<Self>()))] -/// async fn bar() {} -/// } +/// ```compile_fail +/// # use tracing_attributes::instrument; +/// #[instrument] +/// const fn my_const_function() {} /// ``` -/// Instead, you should manually rewrite any `Self` types as the type for -/// which you implement the trait: `#[instrument(fields(tmp = std::any::type_name::<Bar>()))]` -/// (or maybe you can just bump `async-trait`). /// /// [span]: https://docs.rs/tracing/latest/tracing/span/index.html -/// [name]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.name -/// [target]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.target -/// [level]: https://docs.rs/tracing/latest/tracing/struct.Level.html -/// [module path]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html#method.module_path -/// [`INFO`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.INFO -/// [empty field]: https://docs.rs/tracing/latest/tracing/field/struct.Empty.html -/// [field syntax]: https://docs.rs/tracing/latest/tracing/#recording-fields /// [`follows_from`]: https://docs.rs/tracing/latest/tracing/struct.Span.html#method.follows_from /// [`tracing`]: https://github.com/tokio-rs/tracing /// [`fmt::Debug`]: std::fmt::Debug +/// [`Level`]: https://docs.rs/tracing/latest/tracing/struct.Level.html +/// [`Level::TRACE`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.TRACE +/// [`Level::ERROR`]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.ERROR #[proc_macro_attribute] pub fn instrument( args: proc_macro::TokenStream, @@ -584,6 +395,13 @@ fn instrument_precise( let input = syn::parse::<ItemFn>(item)?; let instrumented_function_name = input.sig.ident.to_string(); + if input.sig.constness.is_some() { + return Ok(quote! { + compile_error!("the `#[instrument]` attribute may not be used with `const fn`s") + } + .into()); + } + // check for async_trait-like patterns in the block, and instrument // the future instead of the wrapper if let Some(async_like) = expand::AsyncInfo::from_fn(&input) { |