aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Maurer <mmaurer@google.com>2023-05-27 02:07:11 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-05-27 02:07:11 +0000
commit2a38b027701437f0fb5e2371d489b6fa48c4e623 (patch)
tree68d809c14ec549cf5514c7341a8dbc914a1932eb
parentf7206b858090548451488326d3a18e027657b536 (diff)
parente1d7e208401b8f141080169c3399d01ac31a8cad (diff)
downloadtracing-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.diff906
-rw-r--r--src/attr.rs221
-rw-r--r--src/expand.rs52
-rw-r--r--src/lib.rs318
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
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) {