| Crates.io | log_args |
| lib.rs | log_args |
| version | 0.1.6 |
| created_at | 2025-07-11 02:47:12.163173+00 |
| updated_at | 2025-08-21 02:59:32.062418+00 |
| description | A simple procedural macro to log function arguments using the tracing crate. |
| homepage | https://github.com/MKJSM/log-args |
| repository | https://github.com/MKJSM/log-args |
| max_upload_size | |
| id | 1747352 |
| size | 84,157 |
Procedural macro crate providing the #[params] attribute for automatic parameter logging and context propagation in Rust applications.
Note: This is part of the log-args workspace. For complete setup and examples, see the main documentation.
#[params(span(...))]fields(...)custom(...)Add these dependencies to your Cargo.toml:
[dependencies]
log_args = "0.1.6"
log-args-runtime = { version = "0.1.4", features = ["with_context"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json"] }
Set up structured JSON logging in your main.rs:
use log_args::params;
use tracing::{info, Level};
fn main() {
// Initialize JSON logging with flattened events
tracing_subscriber::fmt()
.with_max_level(Level::DEBUG)
.json()
.flatten_event(true) // Important: flattens fields to top level
.init();
// Your application code
handle_user_request("req-123".to_string(), "user-456".to_string());
}
Begin with the secure default behavior:
#[params] // Secure: no parameters logged
fn handle_user_request(request_id: String, user_id: String) {
info!("Processing user request");
// Output: {"message": "Processing user request", "target": "my_app::handle_user_request"}
}
Enable automatic context inheritance:
#[params(span(request_id, user_id))] // Set up context
fn handle_user_request(request_id: String, user_id: String) {
info!("Processing user request");
validate_request(); // Child function inherits context
}
#[params] // Inherits request_id and user_id automatically
fn validate_request() {
info!("Validating request");
// Output: {"request_id": "req-123", "user_id": "user-456", "message": "Validating request"}
}
Log only specific parameters:
#[params(fields(user_id, action))] // Only log safe parameters
fn user_action(user_id: String, action: String, password: String) {
info!("User performed action");
// Output: {"user_id": "user-456", "action": "login", "message": "User performed action"}
// Note: password is NOT logged - secure!
}
🎉 Congratulations! You now have automatic context propagation and secure parameter logging set up.
use log_args::params;
use tracing::{info, Level};
fn init_logging() {
tracing_subscriber::fmt()
.with_max_level(Level::DEBUG)
.json()
.flatten_event(true)
.init();
}
// Default behavior - context propagation only
#[params]
fn greet(name: String) {
info!("Greeting user");
}
// Span context propagation
#[params(span(tenant_id, session_id))]
fn handle_request(tenant_id: String, session_id: String, data: String) {
info!("Handling request");
process_data(data); // Child inherits context
}
#[params]
fn process_data(data: String) {
info!("Processing data"); // Includes tenant_id and session_id
}
fn main() {
init_logging();
greet("Ada".to_string());
}
## 📖 Complete Attribute Reference
The `#[params]` macro supports multiple attributes that can be combined to create flexible logging strategies. Here's a comprehensive guide to all available attributes:
### `#[params]` - Default (Secure) Behavior
**Purpose**: Enables context inheritance without logging any parameters.
```rust
#[params]
fn authenticate_user(username: String, password: String) {
info!("Authentication attempt"); // No parameters logged - secure!
}
When to use:
#[params(fields(...))] - Selective Parameter LoggingPurpose: Log only specific function parameters as individual fields.
#[params(fields(user_id, action))]
fn user_action(user_id: String, action: String, password: String) {
info!("User performed action");
// Output: {"user_id": "123", "action": "login", "message": "User performed action"}
// Note: password is NOT logged
}
When to use:
#[params(span(...))] - Context PropagationPurpose: Set up automatic context inheritance for child functions.
#[params(span(request_id, user_id))]
fn handle_api_request(request_id: String, user_id: String, payload: String) {
info!("API request received");
validate_payload(payload); // Child inherits request_id and user_id
process_business_logic(); // This too!
}
#[params]
fn validate_payload(payload: String) {
info!("Validating payload");
// Output includes: {"request_id": "req-123", "user_id": "user-456", ...}
}
Cross-boundary support:
tokio::spawn)When to use:
#[params(custom(...))] - Computed FieldsPurpose: Add computed fields using custom expressions.
#[params(
custom(
timestamp = std::time::SystemTime::now(),
data_size = data.len(),
is_admin = user.role == "admin",
service_version = env!("CARGO_PKG_VERSION")
)
)]
fn process_request(data: Vec<u8>, user: User) {
info!("Processing request");
// Output includes computed fields with their values
}
When to use:
Performance tip: Keep expressions lightweight as they're evaluated on every log call.
#[params(all)] - Log All ParametersPurpose: Log all function parameters as individual fields.
#[params(all)] // ⚠️ Use with caution!
fn debug_function(user_id: String, email: String, data: Vec<u8>) {
info!("Debug information");
// Output: {"user_id": "123", "email": "user@example.com", "data": [1,2,3], ...}
}
⚠️ Security Warning: This logs ALL parameters, including sensitive data!
When to use:
#[params(clone_upfront)] - Async-Safe Parameter CloningPurpose: Clone parameters before async operations to prevent ownership issues.
#[params(fields(user_id), clone_upfront)]
async fn async_operation(user_id: String, data: Vec<u8>) {
tokio::spawn(async move {
// user_id was cloned upfront, so this works without ownership issues
process_data(data).await;
});
}
When to use:
Performance note: Only use when necessary as it adds cloning overhead.
#[params(auto_capture)] - Automatic Context CapturePurpose: Automatically capture context in closures and spawned tasks.
#[params(span(batch_id), auto_capture)]
fn process_batch(batch_id: String, items: Vec<Item>) {
items.iter().for_each(|item| {
// Context automatically captured in closure
process_item(item.clone());
});
}
When to use:
You can combine multiple attributes for powerful logging strategies:
#[params(
fields(user_id, action), // Log specific parameters
custom(timestamp = now()), // Add computed fields
span(request_id), // Set up context propagation
clone_upfront // Handle async ownership
)]
async fn complex_operation(request_id: String, user_id: String, action: String, secret: String) {
info!("Complex operation started");
// Logs: user_id, action, timestamp, request_id (but NOT secret)
// Child functions inherit request_id context
}
Problem: Your fields aren't showing up in the JSON output.
Solution: Ensure your tracing subscriber is configured correctly:
// ✅ Correct setup
tracing_subscriber::fmt()
.json()
.flatten_event(true) // This is crucial!
.init();
// ❌ Wrong - fields will be nested
tracing_subscriber::fmt()
.json()
// Missing .flatten_event(true)
.init();
Problem: Child functions aren't inheriting context from parent functions.
Solution: Use span(...) for propagation, not fields(...):
// ❌ Wrong - fields() doesn't propagate context
#[params(fields(user_id))]
fn parent(user_id: String) {
child(); // Won't inherit user_id
}
// ✅ Correct - span() propagates context
#[params(span(user_id))]
fn parent(user_id: String) {
child(); // Will inherit user_id
}
Problem: Field names don't match parameter names.
// ❌ Error - field name doesn't exist
#[params(fields(nonexistent_field))]
fn my_function(user_id: String) {} // user_id ≠ nonexistent_field
// ✅ Correct - field names match parameters
#[params(fields(user_id))]
fn my_function(user_id: String) {}
#[params] and add complexity graduallyflatten_event(true) is enabledSee the workspace examples for runnable demonstrations:
cargo run --example basic_usage
cargo run --example span_propagation
cargo run --example selective_fields
cargo run --example custom_fields
cargo run --example method_support
cargo run --example all_parameters
cargo run --example auto_capture
MIT - See LICENSE for details.