micromegas_tracing_proc_macros/
lib.rs

1//! Procedural macros for instrumenting Rust functions with tracing spans.
2//!
3//! This crate provides `#[span_fn]` and `#[log_fn]` attribute macros that automatically
4//! inject instrumentation into functions. These macros are the primary way to instrument
5//! async code in micromegas.
6//!
7//! # Quick Start
8//!
9//! ```rust,ignore
10//! use micromegas_tracing::prelude::*;
11//!
12//! #[span_fn]
13//! async fn fetch_user(id: u64) -> User {
14//!     // This async function is automatically instrumented
15//!     database.get_user(id).await
16//! }
17//!
18//! #[span_fn]
19//! fn compute_hash(data: &[u8]) -> Hash {
20//!     // Sync functions work too
21//!     hasher.hash(data)
22//! }
23//! ```
24//!
25//! # Why use `#[span_fn]`?
26//!
27//! The `#[span_fn]` macro solves a fundamental challenge in async Rust: tracking execution
28//! time across `.await` points. When an async function awaits, it yields control and may
29//! resume on a different thread. `#[span_fn]` wraps your async code in an `InstrumentedFuture`
30//! that correctly tracks wall-clock time even across these suspension points.
31//!
32//! For sync functions, it creates a scope-based span that measures the function's execution.
33//!
34//! # Import
35//!
36//! These macros are re-exported through the tracing prelude:
37//!
38//! ```rust,ignore
39//! use micromegas_tracing::prelude::*;
40//! ```
41
42// crate-specific lint exceptions:
43//#![allow()]
44
45use proc_macro::TokenStream;
46use proc_macro2::Literal;
47use quote::quote;
48use syn::{
49    ItemFn, ReturnType, Type, TypePath,
50    parse::{Parse, ParseStream, Result},
51    parse_macro_input, parse_quote,
52};
53
54struct TraceArgs {
55    alternative_name: Option<Literal>,
56}
57
58impl Parse for TraceArgs {
59    fn parse(input: ParseStream<'_>) -> Result<Self> {
60        if input.is_empty() {
61            Ok(Self {
62                alternative_name: None,
63            })
64        } else {
65            Ok(Self {
66                alternative_name: Some(Literal::parse(input)?),
67            })
68        }
69    }
70}
71
72/// Check if the function returns a Future (indicating it's an async trait method)
73fn returns_future(function: &ItemFn) -> bool {
74    match &function.sig.output {
75        ReturnType::Type(_, ty) => is_future_type(ty),
76        ReturnType::Default => false,
77    }
78}
79
80/// Check if a type is a Future type (Pin<Box<dyn Future>> or impl Future)
81fn is_future_type(ty: &Type) -> bool {
82    match ty {
83        // Check for Pin<Box<dyn Future<...>>>
84        Type::Path(TypePath { path, .. }) => {
85            if let Some(last_segment) = path.segments.last()
86                && last_segment.ident == "Pin"
87            {
88                // This is the pattern async-trait generates
89                return true;
90            }
91            false
92        }
93        // Check for impl Future<...>
94        Type::ImplTrait(impl_trait) => impl_trait.bounds.iter().any(|bound| {
95            if let syn::TypeParamBound::Trait(trait_bound) = bound
96                && let Some(segment) = trait_bound.path.segments.last()
97            {
98                return segment.ident == "Future";
99            }
100            false
101        }),
102        _ => false,
103    }
104}
105
106/// Instruments a function with automatic span tracing.
107///
108/// This is the primary macro for instrumenting both sync and async functions in micromegas.
109/// It automatically detects the function type and applies the appropriate instrumentation.
110///
111/// # Supported Function Types
112///
113/// - **Sync functions**: Wrapped with `span_scope!` for scope-based timing
114/// - **Async functions**: Wrapped with `InstrumentedFuture` for accurate async timing
115/// - **Async trait methods**: Works with `#[async_trait]` - place `#[span_fn]` after `#[async_trait]`
116///
117/// # Basic Usage
118///
119/// ```rust,ignore
120/// use micromegas_tracing::prelude::*;
121///
122/// // Async function - tracks time across .await points
123/// #[span_fn]
124/// async fn process_request(req: Request) -> Response {
125///     let data = fetch_data(req.id).await;
126///     transform(data).await
127/// }
128///
129/// // Sync function - tracks wall-clock execution time
130/// #[span_fn]
131/// fn calculate_checksum(data: &[u8]) -> u32 {
132///     data.iter().fold(0u32, |acc, &b| acc.wrapping_add(b as u32))
133/// }
134/// ```
135///
136/// # Custom Span Names
137///
138/// By default, the span name is the function name prefixed with the module path.
139/// You can override this with a custom name:
140///
141/// ```rust,ignore
142/// #[span_fn("custom_operation_name")]
143/// async fn internal_impl() {
144///     // Span will be named "module::path::custom_operation_name"
145/// }
146/// ```
147///
148/// # With Async Traits
149///
150/// When using `#[async_trait]`, place `#[span_fn]` on the method *after* the
151/// `#[async_trait]` attribute on the impl block:
152///
153/// ```rust,ignore
154/// use async_trait::async_trait;
155/// use micromegas_tracing::prelude::*;
156///
157/// #[async_trait]
158/// trait DataService {
159///     async fn fetch(&self, id: u64) -> Data;
160/// }
161///
162/// #[async_trait]
163/// impl DataService for MyService {
164///     #[span_fn]
165///     async fn fetch(&self, id: u64) -> Data {
166///         self.db.query(id).await
167///     }
168/// }
169/// ```
170///
171/// # How It Works
172///
173/// For **async functions**, the macro:
174/// 1. Removes the `async` keyword
175/// 2. Changes the return type to `impl Future<Output = T>`
176/// 3. Wraps the body in an `InstrumentedFuture` that tracks timing
177///
178/// For **sync functions**, the macro:
179/// 1. Inserts a `span_scope!` call at the start of the function
180/// 2. The span automatically closes when the function returns
181///
182/// # Performance
183///
184/// The overhead is approximately 40ns per span (20ns per event, with a span
185/// recording both begin and end). This makes it suitable for high-frequency
186/// instrumentation. Spans are collected in thread-local storage and batched
187/// for efficient transmission.
188///
189/// # See Also
190///
191/// - [`log_fn`] - For simple function entry logging without timing
192/// - `span_scope!` - For manual scope-based spans within a function
193/// - `span_async_named!` - For manual async spans with dynamic names
194#[proc_macro_attribute]
195pub fn span_fn(args: TokenStream, input: TokenStream) -> TokenStream {
196    let args = parse_macro_input!(args as TraceArgs);
197    let mut function = parse_macro_input!(input as ItemFn);
198
199    let function_name = args
200        .alternative_name
201        .map_or(function.sig.ident.to_string(), |n| n.to_string());
202
203    if returns_future(&function) {
204        // Case 1: Async trait method (after #[async_trait] transformation)
205        // Function returns Pin<Box<dyn Future<Output = T>>> and has no async keyword
206        let stmts = &function.block.stmts;
207
208        // Extract and instrument the async block from Box::pin(async move { ... })
209        if stmts.len() == 1
210            && let syn::Stmt::Expr(syn::Expr::Call(call_expr)) = &stmts[0]
211            && call_expr.args.len() == 1
212        {
213            let async_block = &call_expr.args[0];
214
215            // Replace the function body with instrumented version
216            function.block = parse_quote! {
217                {
218                    static_span_desc!(_SCOPE_DESC, concat!(module_path!(), "::", #function_name));
219                    Box::pin(InstrumentedFuture::new(
220                        #async_block,
221                        &_SCOPE_DESC
222                    ))
223                }
224            };
225        } else {
226            // For complex async functions that don't match the simple Box::pin pattern,
227            // wrap the entire body in an async block and instrument it
228            let original_block = &function.block;
229            function.block = parse_quote! {
230                {
231                    static_span_desc!(_SCOPE_DESC, concat!(module_path!(), "::", #function_name));
232                    Box::pin(InstrumentedFuture::new(
233                        async move #original_block,
234                        &_SCOPE_DESC
235                    ))
236                }
237            };
238        }
239    } else if function.sig.asyncness.is_some() {
240        // Case 2: Regular async function
241        let original_block = &function.block;
242        let output_type = match &function.sig.output {
243            syn::ReturnType::Type(_, ty) => quote! { #ty },
244            syn::ReturnType::Default => quote! { () },
245        };
246
247        // Remove async and change return type to impl Future
248        function.sig.asyncness = None;
249        function.sig.output = parse_quote! { -> impl std::future::Future<Output = #output_type> };
250        function.block = parse_quote! {
251            {
252                static_span_desc!(_SCOPE_DESC, concat!(module_path!(), "::", #function_name));
253                let fut = async move #original_block;
254                InstrumentedFuture::new(fut, &_SCOPE_DESC)
255            }
256        };
257    } else {
258        // Case 3: Regular sync function
259        function.block.stmts.insert(
260            0,
261            parse_quote! {
262                span_scope!(_METADATA_FUNC, concat!(module_path!(), "::", #function_name));
263            },
264        );
265    }
266
267    TokenStream::from(quote! {
268        #function
269    })
270}
271
272/// Logs function entry with the function name.
273///
274/// This macro injects a `trace!` call at the start of the function, logging
275/// the function name. Unlike [`span_fn`], it does not measure execution time
276/// or track function exit.
277///
278/// # Usage
279///
280/// ```rust,ignore
281/// use micromegas_tracing::prelude::*;
282///
283/// #[log_fn]
284/// fn handle_event(event: Event) {
285///     // Logs "handle_event" at trace level when called
286///     process(event);
287/// }
288/// ```
289///
290/// # When to Use
291///
292/// Use `log_fn` when you only need to know that a function was called, without
293/// timing data. Note that log entries are typically more expensive than span
294/// events, so for performance instrumentation prefer [`span_fn`].
295///
296/// `log_fn` is useful when you want function calls to appear in the log stream
297/// rather than the spans/traces stream.
298#[proc_macro_attribute]
299pub fn log_fn(args: TokenStream, input: TokenStream) -> TokenStream {
300    assert!(args.is_empty());
301    let mut function = parse_macro_input!(input as ItemFn);
302    let function_name = function.sig.ident.to_string();
303
304    function.block.stmts.insert(
305        0,
306        parse_quote! {
307            trace!(#function_name);
308        },
309    );
310    TokenStream::from(quote! {
311        #function
312    })
313}