Skip to content

Commit f5aac47

Browse files
committed
attributes: refactor into separate modules (#1643)
## Motivation Currently, `tracing-attributes` consists of one very large `lib.rs` module that's 1358 lines long. In my opinion, this makes the code somewhat hard to navigate. ## Solution This branch refactors `tracing-attributes` so that most of the code is split into two separate modules: `attrs.rs`, which contains the types representing the attribute arguments and the code for parsing them, and `expand.rs`, which contains the code for actually expanding an `#[instrument]` macro to generate the instrumented code. For the most part, this was a pretty clean split. I also did a small change to the way `async-trait` support is implemented; moving the two steps for generating instrumented code for `async-trait` to two methods on the `AsyncTraitInfo` struct; one for determining if the instrumented function is also an `async-trait` method and finding the necessary information to instrument it, and one for generating the instrumented code. This feels a bit neater than doing all of that in the `gen_function` function. There shouldn't be any functional changes or significant implementatation changes (besides the `async-trait` change) in this branch; just moving code around. Signed-off-by: Eliza Weisman <[email protected]>
1 parent 8540abc commit f5aac47

File tree

4 files changed

+1050
-1018
lines changed

4 files changed

+1050
-1018
lines changed

Diff for: tracing-attributes/src/attr.rs

+373
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
use std::collections::HashSet;
2+
use syn::{punctuated::Punctuated, Expr, Ident, LitInt, LitStr, Path, Token};
3+
4+
use proc_macro2::TokenStream;
5+
use quote::{quote, quote_spanned, ToTokens};
6+
use syn::ext::IdentExt as _;
7+
use syn::parse::{Parse, ParseStream};
8+
9+
#[derive(Clone, Default, Debug)]
10+
pub(crate) struct InstrumentArgs {
11+
level: Option<Level>,
12+
pub(crate) name: Option<LitStr>,
13+
target: Option<LitStr>,
14+
pub(crate) skips: HashSet<Ident>,
15+
pub(crate) skip_all: bool,
16+
pub(crate) fields: Option<Fields>,
17+
pub(crate) err_mode: Option<ErrorMode>,
18+
/// Errors describing any unrecognized parse inputs that we skipped.
19+
parse_warnings: Vec<syn::Error>,
20+
}
21+
22+
impl InstrumentArgs {
23+
pub(crate) fn level(&self) -> impl ToTokens {
24+
fn is_level(lit: &LitInt, expected: u64) -> bool {
25+
match lit.base10_parse::<u64>() {
26+
Ok(value) => value == expected,
27+
Err(_) => false,
28+
}
29+
}
30+
31+
match &self.level {
32+
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("trace") => {
33+
quote!(tracing::Level::TRACE)
34+
}
35+
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("debug") => {
36+
quote!(tracing::Level::DEBUG)
37+
}
38+
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("info") => {
39+
quote!(tracing::Level::INFO)
40+
}
41+
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("warn") => {
42+
quote!(tracing::Level::WARN)
43+
}
44+
Some(Level::Str(ref lit)) if lit.value().eq_ignore_ascii_case("error") => {
45+
quote!(tracing::Level::ERROR)
46+
}
47+
Some(Level::Int(ref lit)) if is_level(lit, 1) => quote!(tracing::Level::TRACE),
48+
Some(Level::Int(ref lit)) if is_level(lit, 2) => quote!(tracing::Level::DEBUG),
49+
Some(Level::Int(ref lit)) if is_level(lit, 3) => quote!(tracing::Level::INFO),
50+
Some(Level::Int(ref lit)) if is_level(lit, 4) => quote!(tracing::Level::WARN),
51+
Some(Level::Int(ref lit)) if is_level(lit, 5) => quote!(tracing::Level::ERROR),
52+
Some(Level::Path(ref pat)) => quote!(#pat),
53+
Some(_) => quote! {
54+
compile_error!(
55+
"unknown verbosity level, expected one of \"trace\", \
56+
\"debug\", \"info\", \"warn\", or \"error\", or a number 1-5"
57+
)
58+
},
59+
None => quote!(tracing::Level::INFO),
60+
}
61+
}
62+
63+
pub(crate) fn target(&self) -> impl ToTokens {
64+
if let Some(ref target) = self.target {
65+
quote!(#target)
66+
} else {
67+
quote!(module_path!())
68+
}
69+
}
70+
71+
/// Generate "deprecation" warnings for any unrecognized attribute inputs
72+
/// that we skipped.
73+
///
74+
/// For backwards compatibility, we need to emit compiler warnings rather
75+
/// than errors for unrecognized inputs. Generating a fake deprecation is
76+
/// the only way to do this on stable Rust right now.
77+
pub(crate) fn warnings(&self) -> impl ToTokens {
78+
let warnings = self.parse_warnings.iter().map(|err| {
79+
let msg = format!("found unrecognized input, {}", err);
80+
let msg = LitStr::new(&msg, err.span());
81+
// TODO(eliza): This is a bit of a hack, but it's just about the
82+
// only way to emit warnings from a proc macro on stable Rust.
83+
// Eventually, when the `proc_macro::Diagnostic` API stabilizes, we
84+
// should definitely use that instead.
85+
quote_spanned! {err.span()=>
86+
#[warn(deprecated)]
87+
{
88+
#[deprecated(since = "not actually deprecated", note = #msg)]
89+
const TRACING_INSTRUMENT_WARNING: () = ();
90+
let _ = TRACING_INSTRUMENT_WARNING;
91+
}
92+
}
93+
});
94+
quote! {
95+
{ #(#warnings)* }
96+
}
97+
}
98+
}
99+
100+
impl Parse for InstrumentArgs {
101+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
102+
let mut args = Self::default();
103+
while !input.is_empty() {
104+
let lookahead = input.lookahead1();
105+
if lookahead.peek(kw::name) {
106+
if args.name.is_some() {
107+
return Err(input.error("expected only a single `name` argument"));
108+
}
109+
let name = input.parse::<StrArg<kw::name>>()?.value;
110+
args.name = Some(name);
111+
} else if lookahead.peek(LitStr) {
112+
// XXX: apparently we support names as either named args with an
113+
// sign, _or_ as unnamed string literals. That's weird, but
114+
// changing it is apparently breaking.
115+
if args.name.is_some() {
116+
return Err(input.error("expected only a single `name` argument"));
117+
}
118+
args.name = Some(input.parse()?);
119+
} else if lookahead.peek(kw::target) {
120+
if args.target.is_some() {
121+
return Err(input.error("expected only a single `target` argument"));
122+
}
123+
let target = input.parse::<StrArg<kw::target>>()?.value;
124+
args.target = Some(target);
125+
} else if lookahead.peek(kw::level) {
126+
if args.level.is_some() {
127+
return Err(input.error("expected only a single `level` argument"));
128+
}
129+
args.level = Some(input.parse()?);
130+
} else if lookahead.peek(kw::skip) {
131+
if !args.skips.is_empty() {
132+
return Err(input.error("expected only a single `skip` argument"));
133+
}
134+
if args.skip_all {
135+
return Err(input.error("expected either `skip` or `skip_all` argument"));
136+
}
137+
let Skips(skips) = input.parse()?;
138+
args.skips = skips;
139+
} else if lookahead.peek(kw::skip_all) {
140+
if args.skip_all {
141+
return Err(input.error("expected only a single `skip_all` argument"));
142+
}
143+
if !args.skips.is_empty() {
144+
return Err(input.error("expected either `skip` or `skip_all` argument"));
145+
}
146+
let _ = input.parse::<kw::skip_all>()?;
147+
args.skip_all = true;
148+
} else if lookahead.peek(kw::fields) {
149+
if args.fields.is_some() {
150+
return Err(input.error("expected only a single `fields` argument"));
151+
}
152+
args.fields = Some(input.parse()?);
153+
} else if lookahead.peek(kw::err) {
154+
let _ = input.parse::<kw::err>();
155+
let mode = ErrorMode::parse(input)?;
156+
args.err_mode = Some(mode);
157+
} else if lookahead.peek(Token![,]) {
158+
let _ = input.parse::<Token![,]>()?;
159+
} else {
160+
// We found a token that we didn't expect!
161+
// We want to emit warnings for these, rather than errors, so
162+
// we'll add it to the list of unrecognized inputs we've seen so
163+
// far and keep going.
164+
args.parse_warnings.push(lookahead.error());
165+
// Parse the unrecognized token tree to advance the parse
166+
// stream, and throw it away so we can keep parsing.
167+
let _ = input.parse::<proc_macro2::TokenTree>();
168+
}
169+
}
170+
Ok(args)
171+
}
172+
}
173+
174+
struct StrArg<T> {
175+
value: LitStr,
176+
_p: std::marker::PhantomData<T>,
177+
}
178+
179+
impl<T: Parse> Parse for StrArg<T> {
180+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
181+
let _ = input.parse::<T>()?;
182+
let _ = input.parse::<Token![=]>()?;
183+
let value = input.parse()?;
184+
Ok(Self {
185+
value,
186+
_p: std::marker::PhantomData,
187+
})
188+
}
189+
}
190+
191+
struct Skips(HashSet<Ident>);
192+
193+
impl Parse for Skips {
194+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
195+
let _ = input.parse::<kw::skip>();
196+
let content;
197+
let _ = syn::parenthesized!(content in input);
198+
let names: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse_any)?;
199+
let mut skips = HashSet::new();
200+
for name in names {
201+
if skips.contains(&name) {
202+
return Err(syn::Error::new(
203+
name.span(),
204+
"tried to skip the same field twice",
205+
));
206+
} else {
207+
skips.insert(name);
208+
}
209+
}
210+
Ok(Self(skips))
211+
}
212+
}
213+
214+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
215+
pub(crate) enum ErrorMode {
216+
Display,
217+
Debug,
218+
}
219+
220+
impl Default for ErrorMode {
221+
fn default() -> Self {
222+
ErrorMode::Display
223+
}
224+
}
225+
226+
impl Parse for ErrorMode {
227+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
228+
if !input.peek(syn::token::Paren) {
229+
return Ok(ErrorMode::default());
230+
}
231+
let content;
232+
let _ = syn::parenthesized!(content in input);
233+
let maybe_mode: Option<Ident> = content.parse()?;
234+
maybe_mode.map_or(Ok(ErrorMode::default()), |ident| {
235+
match ident.to_string().as_str() {
236+
"Debug" => Ok(ErrorMode::Debug),
237+
"Display" => Ok(ErrorMode::Display),
238+
_ => Err(syn::Error::new(
239+
ident.span(),
240+
"unknown error mode, must be Debug or Display",
241+
)),
242+
}
243+
})
244+
}
245+
}
246+
247+
#[derive(Clone, Debug)]
248+
pub(crate) struct Fields(pub(crate) Punctuated<Field, Token![,]>);
249+
250+
#[derive(Clone, Debug)]
251+
pub(crate) struct Field {
252+
pub(crate) name: Punctuated<Ident, Token![.]>,
253+
pub(crate) value: Option<Expr>,
254+
pub(crate) kind: FieldKind,
255+
}
256+
257+
#[derive(Clone, Debug, Eq, PartialEq)]
258+
pub(crate) enum FieldKind {
259+
Debug,
260+
Display,
261+
Value,
262+
}
263+
264+
impl Parse for Fields {
265+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
266+
let _ = input.parse::<kw::fields>();
267+
let content;
268+
let _ = syn::parenthesized!(content in input);
269+
let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?;
270+
Ok(Self(fields))
271+
}
272+
}
273+
274+
impl ToTokens for Fields {
275+
fn to_tokens(&self, tokens: &mut TokenStream) {
276+
self.0.to_tokens(tokens)
277+
}
278+
}
279+
280+
impl Parse for Field {
281+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
282+
let mut kind = FieldKind::Value;
283+
if input.peek(Token![%]) {
284+
input.parse::<Token![%]>()?;
285+
kind = FieldKind::Display;
286+
} else if input.peek(Token![?]) {
287+
input.parse::<Token![?]>()?;
288+
kind = FieldKind::Debug;
289+
};
290+
let name = Punctuated::parse_separated_nonempty_with(input, Ident::parse_any)?;
291+
let value = if input.peek(Token![=]) {
292+
input.parse::<Token![=]>()?;
293+
if input.peek(Token![%]) {
294+
input.parse::<Token![%]>()?;
295+
kind = FieldKind::Display;
296+
} else if input.peek(Token![?]) {
297+
input.parse::<Token![?]>()?;
298+
kind = FieldKind::Debug;
299+
};
300+
Some(input.parse()?)
301+
} else {
302+
None
303+
};
304+
Ok(Self { name, value, kind })
305+
}
306+
}
307+
308+
impl ToTokens for Field {
309+
fn to_tokens(&self, tokens: &mut TokenStream) {
310+
if let Some(ref value) = self.value {
311+
let name = &self.name;
312+
let kind = &self.kind;
313+
tokens.extend(quote! {
314+
#name = #kind#value
315+
})
316+
} else if self.kind == FieldKind::Value {
317+
// XXX(eliza): I don't like that fields without values produce
318+
// empty fields rather than local variable shorthand...but,
319+
// we've released a version where field names without values in
320+
// `instrument` produce empty field values, so changing it now
321+
// is a breaking change. agh.
322+
let name = &self.name;
323+
tokens.extend(quote!(#name = tracing::field::Empty))
324+
} else {
325+
self.kind.to_tokens(tokens);
326+
self.name.to_tokens(tokens);
327+
}
328+
}
329+
}
330+
331+
impl ToTokens for FieldKind {
332+
fn to_tokens(&self, tokens: &mut TokenStream) {
333+
match self {
334+
FieldKind::Debug => tokens.extend(quote! { ? }),
335+
FieldKind::Display => tokens.extend(quote! { % }),
336+
_ => {}
337+
}
338+
}
339+
}
340+
341+
#[derive(Clone, Debug)]
342+
enum Level {
343+
Str(LitStr),
344+
Int(LitInt),
345+
Path(Path),
346+
}
347+
348+
impl Parse for Level {
349+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
350+
let _ = input.parse::<kw::level>()?;
351+
let _ = input.parse::<Token![=]>()?;
352+
let lookahead = input.lookahead1();
353+
if lookahead.peek(LitStr) {
354+
Ok(Self::Str(input.parse()?))
355+
} else if lookahead.peek(LitInt) {
356+
Ok(Self::Int(input.parse()?))
357+
} else if lookahead.peek(Ident) {
358+
Ok(Self::Path(input.parse()?))
359+
} else {
360+
Err(lookahead.error())
361+
}
362+
}
363+
}
364+
365+
mod kw {
366+
syn::custom_keyword!(fields);
367+
syn::custom_keyword!(skip);
368+
syn::custom_keyword!(skip_all);
369+
syn::custom_keyword!(level);
370+
syn::custom_keyword!(target);
371+
syn::custom_keyword!(name);
372+
syn::custom_keyword!(err);
373+
}

0 commit comments

Comments
 (0)