```rust
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, FnArg, PatType, Ident, Signature, Type};
fn is_trace_context_type(ty: &Type) -> bool {
match ty {
Type::Path(type_path) if type_path.qself.is_none() => {
type_path.path.is_ident("TraceContext") || type_path.path.is_ident("OptionalTraceContext")
},
_ => false,
}
}
fn find_context_arg(sig: &Signature) -> Ident {
sig.inputs.iter().rev()
.filter_map(|arg| {
if let FnArg::Typed(pat_type) = arg {
if is_trace_context_type(&pat_type.ty) {
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
return Some(pat_ident.ident.clone());
}
}
}
None
})
.next()
.unwrap_or_else(|| Ident::new("ctx", proc_macro2::Span::call_site()))
}
#[proc_macro_attribute]
pub fn trace(attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
let ctx_ident = find_context_arg(&input_fn.sig);
let span_type = if attr.is_empty() {
quote! { rust_observer::tracing::SpanType::Internal }
} else {
let span_type_ident = parse_macro_input!(attr as Ident);
quote! { rust_observer::tracing::SpanType::#span_type_ident }
};
let name = &input_fn.sig.ident;
let vis = &input_fn.vis;
let asyncness = &input_fn.sig.asyncness;
let output = &input_fn.sig.output;
let block = &input_fn.block;
let parsed_inputs: Vec<_> = input_fn.sig.inputs.iter().map(|i| {
match i {
FnArg::Typed(PatType { pat, ty, .. }) => quote! { #pat: #ty },
_ => quote! { #i }
}
}).collect();
let call_args: Vec<_> = input_fn.sig.inputs.iter().map(|i| {
match i {
FnArg::Typed(PatType { pat, .. }) => quote! { #pat.clone() },
_ => quote! { #i }
}
}).collect();
let result = if asyncness.is_some() {
quote! {
#vis #asyncness fn #name(#(#parsed_inputs),*) #output {
let start_ts = rust_observer::chrono::Utc::now();
let mut #ctx_ident = if #ctx_ident.as_ref().map_or(true, |ctx| ctx.trace_id.is_empty() || ctx.trace_id.len() != 32) {
rust_observer::tracing::TraceContext::new_optional(#span_type, stringify!(#name).to_string())
} else {
Some(#ctx_ident.as_ref().unwrap().new_span(#span_type, stringify!(#name).to_string(), start_ts))
};
let function_name = stringify!(#name);
// push trace to stdout/gRPC
rust_observer::logging::info!(format!("Entering function: {function_name}"), #ctx_ident.clone());
let start = std::time::Instant::now();
#asyncness fn inner(#(#parsed_inputs),*) #output #block
let result = inner(#(#call_args),*).await;
let end_ts = rust_observer::chrono::Utc::now();
#ctx_ident.as_mut().unwrap().end_span(end_ts);
let duration = start.elapsed();
rust_observer::logging::info!(format!("Exiting function: {function_name} took {duration:?}"), #ctx_ident.clone());
result
}
}
} else {
quote! {
#vis fn #name(#(#parsed_inputs),*) #output {
let start_ts = rust_observer::chrono::Utc::now();
let mut #ctx_ident = if #ctx_ident.as_ref().map_or(true, |ctx| ctx.trace_id.is_empty() || ctx.trace_id.len() != 32) {
rust_observer::tracing::TraceContext::new_optional(#span_type, stringify!(#name).to_string())
} else {
Some(#ctx_ident.as_ref().unwrap().new_span(#span_type, stringify!(#name).to_string(), start_ts))
};
let function_name = stringify!(#name);
rust_observer::logging::info!(format!("Entering function: {function_name}"), #ctx_ident.clone());
let start = std::time::Instant::now();
let result = (|| {
#block
})();
let end_ts = rust_observer::chrono::Utc::now();
#ctx_ident.as_mut().unwrap().end_span(end_ts);
let duration = start.elapsed();
rust_observer::logging::info!(format!("Exiting function: {function_name} took {duration:?}"), #ctx_ident.clone());
result
}
}
};
result.into()
}
```