Expand description
Β§log_args
A powerful procedural macro crate providing the #[params]
attribute for automatic parameter logging
and context propagation in Rust applications. Built on top of the tracing
ecosystem, it enables
truly automatic context inheritance across all boundaries including async/await, spawned tasks,
closures, and WebSocket upgrades.
Β§β¨ Key Features
Β§π― Automatic Context Inheritance
- Zero Configuration: Child functions inherit parent context with just
#[params]
- Cross-Boundary: Works across closures, async spawns, WebSocket upgrades, and thread boundaries
- Transparent: No manual context passing or management required
Β§π Performance & Safety
- Zero Runtime Overhead: All processing happens at compile-time via macro expansion
- Memory Efficient: Only specified fields are cloned and logged
- Async Safe: Proper handling of ownership in async contexts
- Thread Safe: Context propagation uses thread-local and task-local storage
Β§π§ Flexible Configuration
- Selective Logging: Choose exactly which parameters to log with
fields(...)
- Custom Fields: Add computed metadata and expressions with
custom(...)
- Span Propagation: Automatic context inheritance with
span(...)
- Nested Access: Support for deep field access like
user.profile.settings.theme
- Method Calls: Log results of method calls and expressions
Β§π Security & Privacy
- Secure by Default: Sensitive parameters excluded unless explicitly specified
- Fine-grained Control: Log only whatβs needed for debugging
- Compliance Ready: Selective logging helps meet privacy requirements
- Production Safe: Configurable logging levels and field selection
Β§π Quick Start
Add to your Cargo.toml
:
[dependencies]
log_args = "0.1.4"
log-args-runtime = { version = "0.1.2", features = ["with_context"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["json"] }
Β§Basic Usage Examples
β
use log_args::params;
use tracing::info;
// Default behavior: Only span propagation and function name logging
#[params]
fn process_request(user_id: String, data: String) {
info!("Processing request");
// Output: {"message": "Processing request", "target": "my_app::process_request"}
}
// Selective parameter logging (excludes sensitive data)
#[params(fields(user_id, action))]
fn user_action(user_id: String, action: String, password: String) {
info!("User performed action");
// Output: {"message": "User performed action", "user_id": "123", "action": "login"}
// Note: password is excluded for security
}
// Span context propagation - the killer feature!
#[params(span(request_id, user_id))]
fn handle_api_request(request_id: String, user_id: String, payload: String) {
info!("API request received");
validate_request(payload); // Child function inherits context
process_business_logic(); // This too!
}
// Child functions automatically inherit request_id and user_id
#[params]
fn validate_request(payload: String) {
info!("Validating request");
// Output: {"request_id": "req-123", "user_id": "user-456", "message": "Validating request"}
}
#[params]
fn process_business_logic() {
info!("Processing business logic");
// Output: {"request_id": "req-123", "user_id": "user-456", "message": "Processing business logic"}
}
Β§π§ Advanced Usage
Β§Custom Fields with Expressions
β
#[params(
fields(user.id, user.name),
custom(
email_count = user.emails.len(),
is_premium = user.subscription.tier == "premium",
timestamp = std::time::SystemTime::now()
)
)]
fn analyze_user(user: User, api_key: String) {
info!("Analyzing user account");
// Output: {
// "message": "Analyzing user account",
// "user_id": 42,
// "user_name": "Alice",
// "email_count": 3,
// "is_premium": true,
// "timestamp": "2024-01-01T12:00:00Z"
// }
}
Β§Async Task Processing
β
#[params(span(job_id, user_id))]
async fn process_background_job(job_id: String, user_id: String, job_data: JobData) {
info!("Background job started");
// Spawn async tasks - they inherit context automatically
let handle1 = tokio::spawn(async {
validate_job_data().await;
});
let handle2 = tokio::spawn(async {
send_notifications().await;
});
tokio::try_join!(handle1, handle2).unwrap();
info!("Background job completed");
}
#[params]
async fn validate_job_data() {
info!("Validating job data");
// Automatically includes job_id and user_id from parent context
}
Β§π§ Setup & Configuration
Β§Tracing Subscriber Setup
For structured JSON logging with context fields at the top level:
β
fn init_logging() {
tracing_subscriber::fmt()
.json()
.flatten_event(true) // Required for field flattening
.init();
}
Β§Production Configuration
β
fn init_prod_logging() {
tracing_subscriber::fmt()
.json()
.flatten_event(true)
.with_env_filter("info,my_app=debug")
.with_target(false)
.init();
}
Β§π Security Best Practices
Always use selective logging in production:
β
// β
Good - Only logs safe fields
#[params(fields(user_id, operation_type))]
fn secure_operation(user_id: String, password: String, operation_type: String) {
info!("Operation started");
// password is excluded for security
}
// β Bad - Logs everything including sensitive data
#[params(all)]
fn insecure_operation(user_id: String, password: String) {
info!("Operation started"); // This would log the password!
}
Β§π Attribute Reference
#[params]
- Default: span propagation and function name logging only#[params(all)]
- Log all parameters (use carefully in production)#[params(fields(param1, param2))]
- Log only specified parameters#[params(span(param1, param2))]
- Propagate parameters as context to child functions#[params(custom(key = expression))]
- Add computed custom fields
Β§π« Limitations
- Array indexing like
users[0].name
is not supported (useusers.first().map(|u| &u.name)
instead) - The macro redefines logging macros within function scope only
- Complex expressions may not parse correctly (simplify or use custom fields)
Β§π Examples
See the workspace examples for comprehensive demonstrations.
Attribute MacrosΒ§
- params
- A powerful procedural macro for automatic function argument logging with structured tracing.