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