| Crates.io | crustrace |
| lib.rs | crustrace |
| version | 0.1.9 |
| created_at | 2025-05-27 20:50:21.428579+00 |
| updated_at | 2025-09-04 19:16:11.126183+00 |
| description | Function and module-level procedural macro attributes to instrument functions with tracing |
| homepage | https://github.com/lmmx/crustrace |
| repository | https://github.com/lmmx/crustace |
| max_upload_size | |
| id | 1691760 |
| size | 23,262 |
Crustrace is a procedural macro that automatically instruments all functions in a module with tracing spans, eliminating the need to manually add #[instrument] to every function.
Use Crustrace when you want comprehensive tracing with minimal effort to add and remove.
Stick with manual instrumentation when you need fine-grained control over which functions are traced.
When adding distributed tracing to Rust applications, developers typically need to annotate every function they want to trace:
#[tracing::instrument(level = "info", ret)]
fn foo() { ... }
#[tracing::instrument(level = "info", ret)]
fn bar() { ... }
#[tracing::instrument(level = "info", ret)]
fn baz() { ... }
This is tedious and a barrier to quick instrumentation of anything more than a function or two (we really want module and crate-level instrumentation).
Crustrace solves this by automatically instrumenting all functions in a module, giving you complete call-chain tracing with minimal code changes. It's a simple initial solution but would be extensible to filter the functions it's applied to by name, by crate in a workspace, and so on.
Add Crustrace to your Cargo.toml:
[dependencies]
crustrace = "0.1"
tracing = "0.1"
tracing-subscriber = "0.3"
Apply the #[omni] attribute to any module:
examples/omni_mod_fib and alongside it as a Rust-scriptuse crustrace::omni;
#[omni]
mod my_functions {
fn foo(x: i32) -> i32 {
bar(x * 2)
}
fn bar(y: i32) -> i32 {
baz(y + 1)
}
fn baz(z: i32) -> i32 {
z * 3
}
}
or more typically, by putting #![omni] (note the !) at the top of a module not declared by a mod block.
All functions in the module are then automatically instrumented as if you had written:
#[tracing::instrument(level = "info", ret)]
fn foo(x: i32) -> i32 { ... }
#[tracing::instrument(level = "info", ret)]
fn bar(y: i32) -> i32 { ... }
#[tracing::instrument(level = "info", ret)]
fn baz(z: i32) -> i32 { ... }
Crustrace also works on impl blocks, automatically instrumenting all methods:
examples/omni_struct_fib and alongside it as a Rust-scriptuse crustrace::omni;
struct Calculator;
#[omni]
impl Calculator {
pub fn new() -> Self {
Self
}
pub fn add(&self, a: i32, b: i32) -> i32 {
self.multiply(a, 1) + self.multiply(b, 1)
}
pub fn multiply(&self, x: i32, y: i32) -> i32 {
x * y
}
fn internal_helper(&self, value: i32) -> i32 {
value * 2
}
}
All methods in the impl block get automatically instrumented, including:
new()This gives you complete tracing of method calls within your types.
WORK IN PROGRESS
crustrace::instrument is a syn-free (simpler, yet functional!) version
of the tracing-attributes::instrument macro.
In turn, crustrace::omni no longer uses tracing::instrument, it is
entirely using crustrace::instrument.
use crustrace::omni;
use tracing_subscriber;
#[omni]
mod calculations {
fn fibonacci(n: u64) -> u64 {
if n <= 1 {
n
} else {
add_numbers(fibonacci(n - 1), fibonacci(n - 2))
}
}
fn add_numbers(a: u64, b: u64) -> u64 {
a + b
}
}
// Initialize tracing
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ENTER |
tracing_subscriber::fmt::format::FmtSpan::EXIT)
.init();
use calculations::*;
let result = fibonacci(5);
println!("Result: {}", result);
This produces detailed tracing output showing the complete call hierarchy:
INFO fibonacci{n=5}: enter
INFO fibonacci{n=4}: enter
INFO fibonacci{n=3}: enter
INFO fibonacci{n=2}: enter
INFO fibonacci{n=1}: enter
INFO fibonacci{n=1}: return=1
INFO fibonacci{n=1}: exit
INFO fibonacci{n=0}: enter
INFO fibonacci{n=0}: return=0
INFO fibonacci{n=0}: exit
INFO add_numbers{a=1 b=0}: enter
INFO add_numbers{a=1 b=0}: return=1
INFO add_numbers{a=1 b=0}: exit
INFO fibonacci{n=2}: return=1
INFO fibonacci{n=2}: exit
// ... and so on
Crustrace uses a procedural macro to parse the token stream of a module and automatically inject #[tracing::instrument(level = "info", ret)] before every function definition.
It uses proc-macro2 (it's free of syn!) to
parse tokens rather than doing string replacement or full on AST creation.
The macro:
fn is followed by an identifierfn foo() { ... }fn foo<T>(x: T) { ... }fn foo(x: impl Display) -> Result<String, Error> { ... }impl MyStruct { fn method(&self) { ... } }impl<T> Container<T> { ... }some_fn_call()"fn not a function"// fn somethingBy default, Crustrace applies these instrument settings:
inforet)Future versions may support customising these settings via macro parameters (please feel free to suggest ideas and submit at least some test for it if you can't figure out how it'd be implemented). PRs would be ideal!
RUST_LOG environment variable to control tracing levels in future#[instrument] attributestracing crateThis project is licensed under either of:
at your option.