170 lines
4.8 KiB
Rust
170 lines
4.8 KiB
Rust
use proc_macro::TokenStream;
|
|
use quote::quote;
|
|
use syn::{parse_macro_input, ItemFn, Lit, Expr, Stmt, ExprCall, ExprMethodCall};
|
|
use syn::visit_mut::{self, VisitMut};
|
|
|
|
#[proc_macro_attribute]
|
|
pub fn instrument(args: TokenStream, input: TokenStream) -> TokenStream {
|
|
let input_fn = parse_macro_input!(input as ItemFn);
|
|
|
|
// Parse custom name from attributes
|
|
let mut custom_name: Option<String> = None;
|
|
|
|
let args_parser = syn::meta::parser(|meta| {
|
|
if meta.path.is_ident("name") {
|
|
let value: Lit = meta.value()?.parse()?;
|
|
if let Lit::Str(lit_str) = value {
|
|
custom_name = Some(lit_str.value());
|
|
}
|
|
Ok(())
|
|
} else {
|
|
Err(meta.error("unsupported attribute"))
|
|
}
|
|
});
|
|
|
|
let _ = parse_macro_input!(args with args_parser);
|
|
|
|
let fn_name = &input_fn.sig.ident;
|
|
let span_name = custom_name.unwrap_or_else(|| fn_name.to_string());
|
|
|
|
let fn_vis = &input_fn.vis;
|
|
let fn_sig = &input_fn.sig;
|
|
let fn_block = &input_fn.block;
|
|
let fn_attrs = &input_fn.attrs;
|
|
|
|
// Check if function is async
|
|
let is_async = fn_sig.asyncness.is_some();
|
|
|
|
let instrumented = if is_async {
|
|
// For async functions, we need to instrument the future
|
|
quote! {
|
|
#(#fn_attrs)*
|
|
#fn_vis #fn_sig {
|
|
let _guard = ::teleprof::SpanGuard::new(#span_name);
|
|
async move #fn_block
|
|
}
|
|
}
|
|
} else {
|
|
// For sync functions, straightforward instrumentation
|
|
quote! {
|
|
#(#fn_attrs)*
|
|
#fn_vis #fn_sig {
|
|
let _guard = ::teleprof::SpanGuard::new(#span_name);
|
|
#fn_block
|
|
}
|
|
}
|
|
};
|
|
|
|
TokenStream::from(instrumented)
|
|
}
|
|
|
|
#[proc_macro_attribute]
|
|
pub fn instrument_calls(_args: TokenStream, input: TokenStream) -> TokenStream {
|
|
let mut input_fn = parse_macro_input!(input as ItemFn);
|
|
|
|
// Transform the function body to wrap all function calls
|
|
let mut visitor = CallInstrumenter;
|
|
visitor.visit_block_mut(&mut input_fn.block);
|
|
|
|
// Add an outer span for the function itself
|
|
let fn_name = input_fn.sig.ident.to_string();
|
|
let fn_vis = &input_fn.vis;
|
|
let fn_sig = &input_fn.sig;
|
|
let fn_block = &input_fn.block;
|
|
let fn_attrs = &input_fn.attrs;
|
|
|
|
let instrumented = quote! {
|
|
#(#fn_attrs)*
|
|
#fn_vis #fn_sig {
|
|
let _guard = ::teleprof::SpanGuard::new(#fn_name);
|
|
#fn_block
|
|
}
|
|
};
|
|
|
|
TokenStream::from(instrumented)
|
|
}
|
|
|
|
struct CallInstrumenter;
|
|
|
|
impl VisitMut for CallInstrumenter {
|
|
fn visit_expr_mut(&mut self, expr: &mut Expr) {
|
|
// First, recurse into children
|
|
visit_mut::visit_expr_mut(self, expr);
|
|
|
|
// Then wrap this expression if it's a function call
|
|
match expr {
|
|
Expr::Call(call) => {
|
|
*expr = wrap_call(call);
|
|
}
|
|
Expr::MethodCall(method_call) => {
|
|
*expr = wrap_method_call(method_call);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn visit_stmt_mut(&mut self, stmt: &mut Stmt) {
|
|
// Handle statements that contain expressions
|
|
match stmt {
|
|
Stmt::Expr(expr, _) => {
|
|
self.visit_expr_mut(expr);
|
|
}
|
|
Stmt::Local(local) => {
|
|
if let Some(init) = &mut local.init {
|
|
self.visit_expr_mut(&mut init.expr);
|
|
}
|
|
}
|
|
_ => {
|
|
// Use default visitor for other statement types
|
|
visit_mut::visit_stmt_mut(self, stmt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn wrap_call(call: &ExprCall) -> Expr {
|
|
// Extract function name from the call expression
|
|
let func_name = extract_function_name(&call.func);
|
|
|
|
let func = &call.func;
|
|
let args = &call.args;
|
|
let attrs = &call.attrs;
|
|
|
|
syn::parse_quote! {
|
|
{
|
|
#(#attrs)*
|
|
let _guard = ::teleprof::SpanGuard::new(#func_name);
|
|
(#func)(#args)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn wrap_method_call(method_call: &ExprMethodCall) -> Expr {
|
|
let method_name = method_call.method.to_string();
|
|
|
|
let receiver = &method_call.receiver;
|
|
let method = &method_call.method;
|
|
let args = &method_call.args;
|
|
let turbofish = &method_call.turbofish;
|
|
let attrs = &method_call.attrs;
|
|
|
|
syn::parse_quote! {
|
|
{
|
|
#(#attrs)*
|
|
let _guard = ::teleprof::SpanGuard::new(#method_name);
|
|
(#receiver).#method #turbofish(#args)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn extract_function_name(func: &Expr) -> String {
|
|
match func {
|
|
Expr::Path(path) => {
|
|
// Get the last segment of the path
|
|
path.path.segments.last()
|
|
.map(|seg| seg.ident.to_string())
|
|
.unwrap_or_else(|| "unknown".to_string())
|
|
}
|
|
_ => "unknown".to_string(),
|
|
}
|
|
} |