| 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<Level>, |
| pub(crate) mode: FormatMode, |
| } |
| |
| #[derive(Clone, Default, Debug)] |
| pub(crate) struct InstrumentArgs { |
| level: Option<Level>, |
| pub(crate) name: Option<LitStr>, |
| target: Option<LitStr>, |
| 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_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) -> 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<Self> { |
| 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::<StrArg<kw::name>>()?.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::<StrArg<kw::target>>()?.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::<ExprArg<kw::parent>>()?; |
| 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::<ExprArg<kw::follows_from>>()?; |
| 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::<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")); |
| } |
| args.fields = Some(input.parse()?); |
| } else if lookahead.peek(kw::err) { |
| let _ = input.parse::<kw::err>(); |
| let err_args = EventArgs::parse(input)?; |
| args.err_args = Some(err_args); |
| } else if lookahead.peek(kw::ret) { |
| let _ = input.parse::<kw::ret>()?; |
| let ret_args = EventArgs::parse(input)?; |
| args.ret_args = Some(ret_args); |
| } else if lookahead.peek(Token![,]) { |
| let _ = input.parse::<Token![,]>()?; |
| } 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::<proc_macro2::TokenTree>(); |
| } |
| } |
| 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<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>, |
| } |
| |
| impl<T: Parse> Parse for StrArg<T> { |
| fn parse(input: ParseStream<'_>) -> syn::Result<Self> { |
| let _ = input.parse::<T>()?; |
| let _ = input.parse::<Token![=]>()?; |
| let value = input.parse()?; |
| Ok(Self { |
| value, |
| _p: std::marker::PhantomData, |
| }) |
| } |
| } |
| |
| struct ExprArg<T> { |
| value: Expr, |
| _p: std::marker::PhantomData<T>, |
| } |
| |
| impl<T: Parse> Parse for ExprArg<T> { |
| fn parse(input: ParseStream<'_>) -> syn::Result<Self> { |
| let _ = input.parse::<T>()?; |
| let _ = input.parse::<Token![=]>()?; |
| let value = input.parse()?; |
| Ok(Self { |
| value, |
| _p: std::marker::PhantomData, |
| }) |
| } |
| } |
| |
| struct Skips(HashSet<Ident>); |
| |
| impl Parse for Skips { |
| fn parse(input: ParseStream<'_>) -> syn::Result<Self> { |
| let _ = input.parse::<kw::skip>(); |
| 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<Field, Token![,]>); |
| |
| #[derive(Clone, Debug)] |
| pub(crate) struct Field { |
| pub(crate) name: Punctuated<Ident, Token![.]>, |
| pub(crate) value: Option<Expr>, |
| 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<Self> { |
| let _ = input.parse::<kw::fields>(); |
| 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<Self> { |
| let mut kind = FieldKind::Value; |
| if input.peek(Token![%]) { |
| input.parse::<Token![%]>()?; |
| kind = FieldKind::Display; |
| } else if input.peek(Token![?]) { |
| input.parse::<Token![?]>()?; |
| kind = FieldKind::Debug; |
| }; |
| let name = Punctuated::parse_separated_nonempty_with(input, Ident::parse_any)?; |
| let value = if input.peek(Token![=]) { |
| input.parse::<Token![=]>()?; |
| if input.peek(Token![%]) { |
| input.parse::<Token![%]>()?; |
| kind = FieldKind::Display; |
| } else if input.peek(Token![?]) { |
| input.parse::<Token![?]>()?; |
| 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<Self> { |
| let _ = input.parse::<kw::level>()?; |
| let _ = input.parse::<Token![=]>()?; |
| 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::<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 { |
| 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); |
| } |