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}; #[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_mode: Option, pub(crate) ret_mode: Option, /// Errors describing any unrecognized parse inputs that we skipped. parse_warnings: Vec, } impl InstrumentArgs { pub(crate) fn level(&self) -> impl ToTokens { fn is_level(lit: &LitInt, expected: u64) -> bool { match lit.base10_parse::() { 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 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 mode = FormatMode::parse(input)?; args.err_mode = Some(mode); } else if lookahead.peek(kw::ret) { let _ = input.parse::()?; let mode = FormatMode::parse(input)?; args.ret_mode = Some(mode); } 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) } } 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: Punctuated = content.parse_terminated(Ident::parse_any)?; 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 } } impl Parse for FormatMode { fn parse(input: ParseStream<'_>) -> syn::Result { if !input.peek(syn::token::Paren) { return Ok(FormatMode::default()); } let content; let _ = syn::parenthesized!(content in input); let maybe_mode: Option = 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); #[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: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?; 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)] enum Level { Str(LitStr), Int(LitInt), 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) { Ok(Self::Str(input.parse()?)) } else if lookahead.peek(LitInt) { Ok(Self::Int(input.parse()?)) } else if lookahead.peek(Ident) { Ok(Self::Path(input.parse()?)) } else { Err(lookahead.error()) } } } 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); }