use std::collections::HashSet; use syn::{punctuated::Punctuated, Expr, Ident, LitInt, LitStr, Path, Token}; use proc_macro2::TokenStream; 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, pub(crate) mode: FormatMode, } #[derive(Clone, Default, Debug)] pub(crate) struct InstrumentArgs { level: Option, pub(crate) name: Option, target: Option, pub(crate) parent: Option, pub(crate) follows_from: Option, pub(crate) skips: HashSet, pub(crate) skip_all: bool, pub(crate) fields: Option, pub(crate) err_args: Option, pub(crate) ret_args: Option, /// Errors describing any unrecognized parse inputs that we skipped. parse_warnings: Vec, } impl InstrumentArgs { pub(crate) fn level(&self) -> Level { self.level.clone().unwrap_or(Level::Info) } pub(crate) fn target(&self) -> impl ToTokens { if let Some(ref target) = self.target { quote!(#target) } else { quote!(module_path!()) } } /// Generate "deprecation" warnings for any unrecognized attribute inputs /// that we skipped. /// /// For backwards compatibility, we need to emit compiler warnings rather /// than errors for unrecognized inputs. Generating a fake deprecation is /// the only way to do this on stable Rust right now. pub(crate) fn warnings(&self) -> impl ToTokens { let warnings = self.parse_warnings.iter().map(|err| { let msg = format!("found unrecognized input, {}", err); let msg = LitStr::new(&msg, err.span()); // TODO(eliza): This is a bit of a hack, but it's just about the // only way to emit warnings from a proc macro on stable Rust. // Eventually, when the `proc_macro::Diagnostic` API stabilizes, we // should definitely use that instead. quote_spanned! {err.span()=> #[warn(deprecated)] { #[deprecated(since = "not actually deprecated", note = #msg)] const TRACING_INSTRUMENT_WARNING: () = (); let _ = TRACING_INSTRUMENT_WARNING; } } }); quote! { { #(#warnings)* } } } } impl Parse for InstrumentArgs { fn parse(input: ParseStream<'_>) -> syn::Result { let mut args = Self::default(); while !input.is_empty() { let lookahead = input.lookahead1(); if lookahead.peek(kw::name) { if args.name.is_some() { return Err(input.error("expected only a single `name` argument")); } let name = input.parse::>()?.value; args.name = Some(name); } else if lookahead.peek(LitStr) { // XXX: apparently we support names as either named args with an // sign, _or_ as unnamed string literals. That's weird, but // changing it is apparently breaking. if args.name.is_some() { return Err(input.error("expected only a single `name` argument")); } args.name = Some(input.parse()?); } else if lookahead.peek(kw::target) { if args.target.is_some() { return Err(input.error("expected only a single `target` argument")); } let target = input.parse::>()?.value; args.target = Some(target); } else if lookahead.peek(kw::parent) { if args.target.is_some() { return Err(input.error("expected only a single `parent` argument")); } let parent = input.parse::>()?; args.parent = Some(parent.value); } else if lookahead.peek(kw::follows_from) { if args.target.is_some() { return Err(input.error("expected only a single `follows_from` argument")); } let follows_from = input.parse::>()?; args.follows_from = Some(follows_from.value); } else if lookahead.peek(kw::level) { if args.level.is_some() { return Err(input.error("expected only a single `level` argument")); } args.level = Some(input.parse()?); } else if lookahead.peek(kw::skip) { 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::()?; 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")); } args.fields = Some(input.parse()?); } else if lookahead.peek(kw::err) { let _ = input.parse::(); let err_args = EventArgs::parse(input)?; args.err_args = Some(err_args); } else if lookahead.peek(kw::ret) { let _ = input.parse::()?; let ret_args = EventArgs::parse(input)?; args.ret_args = Some(ret_args); } else if lookahead.peek(Token![,]) { let _ = input.parse::()?; } else { // We found a token that we didn't expect! // We want to emit warnings for these, rather than errors, so // we'll add it to the list of unrecognized inputs we've seen so // far and keep going. args.parse_warnings.push(lookahead.error()); // Parse the unrecognized token tree to advance the parse // stream, and throw it away so we can keep parsing. let _ = input.parse::(); } } Ok(args) } } 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 { 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::>()? { 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::()?; parse_one_arg()?; } else { return Err(content.error("expected `,` or `)`")); } } Ok(result) } } struct StrArg { value: LitStr, _p: std::marker::PhantomData, } impl Parse for StrArg { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::()?; let _ = input.parse::()?; let value = input.parse()?; Ok(Self { value, _p: std::marker::PhantomData, }) } } struct ExprArg { value: Expr, _p: std::marker::PhantomData, } impl Parse for ExprArg { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::()?; let _ = input.parse::()?; let value = input.parse()?; Ok(Self { value, _p: std::marker::PhantomData, }) } } struct Skips(HashSet); impl Parse for Skips { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); let names = content.parse_terminated(Ident::parse_any, Token![,])?; let mut skips = HashSet::new(); for name in names { if skips.contains(&name) { return Err(syn::Error::new( name.span(), "tried to skip the same field twice", )); } else { skips.insert(name); } } Ok(Self(skips)) } } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub(crate) enum FormatMode { Default, Display, Debug, } impl Default for FormatMode { fn default() -> Self { FormatMode::Default } } #[derive(Clone, Debug)] pub(crate) struct Fields(pub(crate) Punctuated); #[derive(Clone, Debug)] pub(crate) struct Field { pub(crate) name: Punctuated, pub(crate) value: Option, pub(crate) kind: FieldKind, } #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum FieldKind { Debug, Display, Value, } impl Parse for Fields { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::(); let content; let _ = syn::parenthesized!(content in input); let fields = content.parse_terminated(Field::parse, Token![,])?; Ok(Self(fields)) } } impl ToTokens for Fields { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens) } } impl Parse for Field { fn parse(input: ParseStream<'_>) -> syn::Result { let mut kind = FieldKind::Value; if input.peek(Token![%]) { input.parse::()?; kind = FieldKind::Display; } else if input.peek(Token![?]) { input.parse::()?; kind = FieldKind::Debug; }; let name = Punctuated::parse_separated_nonempty_with(input, Ident::parse_any)?; let value = if input.peek(Token![=]) { input.parse::()?; if input.peek(Token![%]) { input.parse::()?; kind = FieldKind::Display; } else if input.peek(Token![?]) { input.parse::()?; kind = FieldKind::Debug; }; Some(input.parse()?) } else { None }; Ok(Self { name, value, kind }) } } impl ToTokens for Field { fn to_tokens(&self, tokens: &mut TokenStream) { if let Some(ref value) = self.value { let name = &self.name; let kind = &self.kind; tokens.extend(quote! { #name = #kind #value }) } else if self.kind == FieldKind::Value { // XXX(eliza): I don't like that fields without values produce // empty fields rather than local variable shorthand...but, // we've released a version where field names without values in // `instrument` produce empty field values, so changing it now // is a breaking change. agh. let name = &self.name; tokens.extend(quote!(#name = tracing::field::Empty)) } else { self.kind.to_tokens(tokens); self.name.to_tokens(tokens); } } } impl ToTokens for FieldKind { fn to_tokens(&self, tokens: &mut TokenStream) { match self { FieldKind::Debug => tokens.extend(quote! { ? }), FieldKind::Display => tokens.extend(quote! { % }), _ => {} } } } #[derive(Clone, Debug)] pub(crate) enum Level { Trace, Debug, Info, Warn, Error, Path(Path), } impl Parse for Level { fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::()?; let _ = input.parse::()?; let lookahead = input.lookahead1(); if lookahead.peek(LitStr) { 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) { fn is_level(lit: &LitInt, expected: u64) -> bool { match lit.base10_parse::() { 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 { Err(lookahead.error()) } } } 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); syn::custom_keyword!(follows_from); syn::custom_keyword!(name); syn::custom_keyword!(err); syn::custom_keyword!(ret); }