veecle_telemetry_macros/
lib.rs1#![recursion_limit = "256"]
9#![cfg_attr(not(feature = "enable"), allow(dead_code))]
10#![cfg_attr(not(feature = "enable"), allow(unreachable_code))]
11
12use std::collections::HashMap;
13
14use proc_macro2::{Ident, Span};
15use quote::{quote, quote_spanned};
16use syn::parse::{Parse, ParseStream};
17use syn::punctuated::Punctuated;
18use syn::*;
19
20struct Arguments {
21 name: Option<LitStr>,
22 short_name: bool,
23 properties: Vec<Property>,
24 span: Span,
25}
26
27struct Property {
28 key: LitStr,
29 value: Lit,
30 span: Span,
31}
32
33impl Parse for Property {
34 fn parse(input: ParseStream) -> Result<Self> {
35 let key: LitStr = input.parse()?;
36 input.parse::<Token![:]>()?;
37 let value: Lit = input.parse()?;
38
39 let span = key.span().join(value.span()).unwrap_or_else(|| key.span());
41 Ok(Property { key, value, span })
42 }
43}
44
45impl Parse for Arguments {
46 fn parse(input: ParseStream) -> Result<Self> {
47 let mut name = None;
48 let mut short_name = false;
49 let mut properties = Vec::<Property>::new();
50 let mut seen = HashMap::new();
51
52 while !input.is_empty() {
53 let ident: Ident = input.parse()?;
54 if seen.contains_key(&ident.to_string()) {
55 return Err(Error::new(ident.span(), "duplicate argument"));
56 }
57 seen.insert(ident.to_string(), ());
58 input.parse::<Token![=]>()?;
59 match ident.to_string().as_str() {
60 "name" => {
61 let parsed_name: LitStr = input.parse()?;
62 name = Some(parsed_name);
63 }
64 "short_name" => {
65 let parsed_short_name: LitBool = input.parse()?;
66 short_name = parsed_short_name.value;
67 }
68 "properties" => {
69 let content;
70 let _brace_token = braced!(content in input);
71 let property_list = content.parse_terminated(Property::parse, Token![,])?;
72 for property in property_list {
73 if properties
74 .iter()
75 .any(|existing| existing.key == property.key)
76 {
77 return Err(Error::new(Span::call_site(), "duplicate property key"));
78 }
79 properties.push(property);
80 }
81 }
82 _ => return Err(Error::new(Span::call_site(), "unexpected identifier")),
83 }
84 if !input.is_empty() {
85 let _ = input.parse::<Token![,]>();
86 }
87 }
88
89 Ok(Arguments {
90 name,
91 short_name,
92 properties,
93 span: input.span(),
94 })
95 }
96}
97
98#[proc_macro_attribute]
172pub fn instrument(
173 arguments: proc_macro::TokenStream,
174 item: proc_macro::TokenStream,
175) -> proc_macro::TokenStream {
176 #[cfg(not(feature = "enable"))]
177 {
178 let _ = parse_macro_input!(arguments as Arguments);
179 return item;
180 }
181
182 let arguments = parse_macro_input!(arguments as Arguments);
183 let input = parse_macro_input!(item as ItemFn);
184
185 let function_name = &input.sig.ident;
186
187 let function_body = match generate_block(
189 function_name,
190 &input.block,
191 input.sig.asyncness.is_some(),
192 &arguments,
193 ) {
194 Ok(body) => body,
195 Err(error) => return error.to_compile_error().into(),
196 };
197
198 let ItemFn {
199 attrs, vis, sig, ..
200 } = input;
201
202 let Signature {
203 output: return_type,
204 inputs: params,
205 unsafety,
206 constness,
207 abi,
208 ident,
209 asyncness,
210 generics:
211 Generics {
212 params: gen_params,
213 where_clause,
214 ..
215 },
216 ..
217 } = sig;
218
219 quote::quote!(
220 #(#attrs) *
221 #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #return_type
222 #where_clause
223 {
224 #function_body
225 }
226 )
227 .into()
228}
229
230fn generate_name(
231 function_name: &Ident,
232 arguments: &Arguments,
233 async_closure: bool,
234) -> syn::Result<proc_macro2::TokenStream> {
235 let span = function_name.span();
236 if let Some(name) = &arguments.name {
237 if name.value().is_empty() {
238 return Err(Error::new(span, "`name` can not be empty"));
239 }
240
241 if arguments.short_name {
242 return Err(Error::new(
243 Span::call_site(),
244 "`name` and `short_name` can not be used together",
245 ));
246 }
247
248 Ok(quote_spanned!(span=>
249 #name
250 ))
251 } else if arguments.short_name {
252 let function_name = function_name.to_string();
253 Ok(quote_spanned!(span=>
254 #function_name
255 ))
256 } else {
257 Ok(quote_spanned!(span=>
258 veecle_telemetry::macro_helpers::strip_closure_suffix(core::any::type_name_of_val(&|| {}), #async_closure)
259 ))
260 }
261}
262
263fn generate_properties(arguments: &Arguments) -> proc_macro2::TokenStream {
264 if arguments.properties.is_empty() {
265 return quote::quote!(&[]);
266 }
267
268 let span = arguments.span;
269 let properties = arguments
270 .properties
271 .iter()
272 .map(|Property { key, value, span }| {
273 quote_spanned!(*span=>
274 veecle_telemetry::value::KeyValue::new(#key, #value)
275 )
276 });
277 let properties = Punctuated::<_, Token![,]>::from_iter(properties);
278 quote_spanned!(span=>
279 &[ #properties ]
280 )
281}
282
283fn generate_block(
285 func_name: &Ident,
286 block: &Block,
287 async_context: bool,
288 arguments: &Arguments,
289) -> syn::Result<proc_macro2::TokenStream> {
290 let name = generate_name(func_name, arguments, async_context)?;
291 let properties = generate_properties(arguments);
292
293 if async_context {
297 Ok(quote!(
298 veecle_telemetry::future::FutureExt::with_span(
299 async move { #block },
300 veecle_telemetry::Span::new(#name, #properties),
301 ).await
302 ))
303 } else {
304 Ok(quote!(
305 let __guard__= veecle_telemetry::Span::new(#name, #properties).entered();
306 #block
307 ))
308 }
309}