| 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) { |