Revision control
Copy as Markdown
Other Tools
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);
}