| 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); | |
| } |