opentelemetry_traceable_macros/
lib.rs1use proc_macro::TokenStream;
17use proc_macro2::TokenStream as TokenStream2;
18use quote::{ToTokens, quote};
19use syn::{
20 Expr, Ident, ItemFn, LitStr, Token,
21 parse::{Parse, ParseStream},
22 parse_macro_input,
23 punctuated::Punctuated,
24};
25
26enum FieldKey {
27 Lit(LitStr),
28 Path(syn::Path),
29}
30
31impl ToTokens for FieldKey {
32 fn to_tokens(&self, tokens: &mut TokenStream2) {
33 match self {
34 FieldKey::Lit(lit) => lit.to_tokens(tokens),
35 FieldKey::Path(path) => path.to_tokens(tokens),
36 }
37 }
38}
39
40struct Field {
41 key: FieldKey,
42 value: Expr,
43}
44
45impl Parse for Field {
46 fn parse(input: ParseStream) -> syn::Result<Self> {
47 let key = if input.peek(LitStr) {
48 FieldKey::Lit(input.parse()?)
49 } else {
50 FieldKey::Path(input.parse()?)
51 };
52 input.parse::<Token![=]>()?;
53 let value: Expr = input.parse()?;
54 Ok(Field { key, value })
55 }
56}
57
58#[derive(Default)]
59struct TraceableArgs {
60 name: Option<LitStr>,
61 fields: Option<Vec<Field>>,
62}
63
64impl Parse for TraceableArgs {
65 fn parse(input: ParseStream) -> syn::Result<Self> {
66 let mut args = TraceableArgs::default();
67 while !input.is_empty() {
68 let ident: Ident = input.parse()?;
69 match ident.to_string().as_str() {
70 "name" => {
71 input.parse::<Token![=]>()?;
72 let lit: LitStr = input.parse()?;
73 if args.name.replace(lit).is_some() {
74 return Err(syn::Error::new(ident.span(), "duplicate `name` argument"));
75 }
76 }
77 "fields" => {
78 let content;
79 syn::parenthesized!(content in input);
80 let parsed: Punctuated<Field, Token![,]> =
81 content.parse_terminated(Field::parse, Token![,])?;
82 if args.fields.replace(parsed.into_iter().collect()).is_some() {
83 return Err(syn::Error::new(ident.span(), "duplicate `fields` argument"));
84 }
85 }
86 other => {
87 return Err(syn::Error::new(
88 ident.span(),
89 format!("unknown argument `{other}`"),
90 ));
91 }
92 }
93 if !input.is_empty() {
94 input.parse::<Token![,]>()?;
95 }
96 }
97 Ok(args)
98 }
99}
100
101#[proc_macro_attribute]
125pub fn traceable(attr: TokenStream, item: TokenStream) -> TokenStream {
126 let args = parse_macro_input!(attr as TraceableArgs);
127 let func = parse_macro_input!(item as ItemFn);
128 expand(args, func).into()
129}
130
131fn expand(args: TraceableArgs, func: ItemFn) -> TokenStream2 {
132 let ItemFn {
133 attrs,
134 vis,
135 sig,
136 block,
137 } = func;
138 let fn_ident_str = sig.ident.to_string();
139 let is_async = sig.asyncness.is_some();
140
141 let span_name = match &args.name {
142 Some(lit) => quote! { #lit },
143 None => quote! { #fn_ident_str },
144 };
145
146 let registry_key = match &args.name {
147 Some(lit) => quote! { #lit },
148 None => quote! { ::std::concat!(::std::module_path!(), "::", #fn_ident_str) },
149 };
150
151 let kvs: Vec<TokenStream2> = args
152 .fields
153 .iter()
154 .flatten()
155 .map(|f| {
156 let (key, value) = (&f.key, &f.value);
157 quote! { ::opentelemetry::KeyValue::new(#key, #value) }
158 })
159 .collect();
160
161 let traced = if is_async {
165 quote! {
166 ::opentelemetry::trace::FutureExt::with_context(async #block, __traceable_cx).await
167 }
168 } else {
169 quote! {
170 let __traceable_guard = __traceable_cx.attach();
171 let __traceable_ret = #block;
172 ::std::mem::drop(__traceable_guard);
173 __traceable_ret
174 }
175 };
176
177 quote! {
182 #(#attrs)*
183 #vis #sig {
184 #[::opentelemetry_traceable::__private::linkme::distributed_slice(::opentelemetry_traceable::registry::REGISTRY)]
185 #[linkme(crate = ::opentelemetry_traceable::__private::linkme)]
186 static __TRACEABLE_SITE: ::opentelemetry_traceable::registry::TraceSite =
187 ::opentelemetry_traceable::registry::TraceSite::new(#registry_key);
188
189 let __traceable_mask =
190 __TRACEABLE_SITE.enabled_slots.load(::std::sync::atomic::Ordering::Relaxed);
191 if __traceable_mask == 0u64 {
192 #block
193 } else {
194 match ::opentelemetry_traceable::instrumentation::start_spans(
195 __traceable_mask,
196 #span_name,
197 ::std::vec![#(#kvs),*],
198 ) {
199 ::std::option::Option::None => #block,
200 ::std::option::Option::Some(__traceable_cx) => { #traced }
201 }
202 }
203 }
204 }
205}