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