use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use syn::{
Expr, Ident, ItemFn, LitStr, Token,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
};
enum FieldKey {
Lit(LitStr),
Path(syn::Path),
}
impl ToTokens for FieldKey {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
FieldKey::Lit(lit) => lit.to_tokens(tokens),
FieldKey::Path(path) => path.to_tokens(tokens),
}
}
}
struct Field {
key: FieldKey,
value: Expr,
}
impl Parse for Field {
fn parse(input: ParseStream) -> syn::Result<Self> {
let key = if input.peek(LitStr) {
FieldKey::Lit(input.parse()?)
} else {
FieldKey::Path(input.parse()?)
};
input.parse::<Token![=]>()?;
let value: Expr = input.parse()?;
Ok(Field { key, value })
}
}
#[derive(Default)]
struct TraceableArgs {
name: Option<LitStr>,
fields: Option<Vec<Field>>,
}
impl Parse for TraceableArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut args = TraceableArgs::default();
while !input.is_empty() {
let ident: Ident = input.parse()?;
match ident.to_string().as_str() {
"name" => {
input.parse::<Token![=]>()?;
let lit: LitStr = input.parse()?;
if args.name.replace(lit).is_some() {
return Err(syn::Error::new(ident.span(), "duplicate `name` argument"));
}
}
"fields" => {
let content;
syn::parenthesized!(content in input);
let parsed: Punctuated<Field, Token![,]> =
content.parse_terminated(Field::parse, Token![,])?;
if args.fields.replace(parsed.into_iter().collect()).is_some() {
return Err(syn::Error::new(ident.span(), "duplicate `fields` argument"));
}
}
other => {
return Err(syn::Error::new(
ident.span(),
format!("unknown argument `{other}`"),
));
}
}
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(args)
}
}
#[proc_macro_attribute]
pub fn traceable(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as TraceableArgs);
let func = parse_macro_input!(item as ItemFn);
expand(args, func).into()
}
fn expand(args: TraceableArgs, func: ItemFn) -> TokenStream2 {
let ItemFn {
attrs,
vis,
sig,
block,
} = func;
let fn_ident_str = sig.ident.to_string();
let is_async = sig.asyncness.is_some();
let span_name = match &args.name {
Some(lit) => quote! { #lit },
None => quote! { #fn_ident_str },
};
let registry_key = match &args.name {
Some(lit) => quote! { #lit },
None => quote! { ::std::concat!(::std::module_path!(), "::", #fn_ident_str) },
};
let kvs: Vec<TokenStream2> = args
.fields
.iter()
.flatten()
.map(|f| {
let (key, value) = (&f.key, &f.value);
quote! { ::opentelemetry::KeyValue::new(#key, #value) }
})
.collect();
let traced = if is_async {
quote! {
::opentelemetry::trace::FutureExt::with_context(async #block, __traceable_cx).await
}
} else {
quote! {
let __traceable_guard = __traceable_cx.attach();
let __traceable_ret = #block;
::std::mem::drop(__traceable_guard);
__traceable_ret
}
};
quote! {
#(#attrs)*
#vis #sig {
#[::opentelemetry_traceable::__private::linkme::distributed_slice(::opentelemetry_traceable::registry::REGISTRY)]
#[linkme(crate = ::opentelemetry_traceable::__private::linkme)]
static __TRACEABLE_SITE: ::opentelemetry_traceable::registry::TraceSite =
::opentelemetry_traceable::registry::TraceSite::new(#registry_key);
let __traceable_mask =
__TRACEABLE_SITE.enabled_slots.load(::std::sync::atomic::Ordering::Relaxed);
if __traceable_mask == 0u64 {
#block
} else {
match ::opentelemetry_traceable::instrumentation::start_spans(
__traceable_mask,
#span_name,
::std::vec![#(#kvs),*],
) {
::std::option::Option::None => #block,
::std::option::Option::Some(__traceable_cx) => { #traced }
}
}
}
}
}