log_args 0.1.2

A simple procedural macro to log function arguments using the tracing crate.
Documentation

log_args

Crates.io Docs.rs License Build Status

A procedural macro for structured logging of function arguments using the tracing crate, with special support for async contexts.

This crate provides a procedural macro attribute #[params] that can be applied to functions to automatically log their arguments. It seamlessly integrates with the tracing ecosystem for structured logging, offering enhanced flexibility for both synchronous and asynchronous functions.


๐Ÿš€ Overview

  • Macro name: #[params]
  • Purpose: Automatically logs function arguments and custom fields using the tracing macros (info!, debug!, etc.)
  • Works with: Both synchronous and asynchronous functions, with special support for async contexts
  • Flexible logging: Control exactly what fields are logged, including nested struct fields
  • Async-friendly: Special clone_upfront option for handling ownership in async blocks

โœจ Features

  • Log all function arguments by default
  • Select specific arguments to log using fields(...)
  • Log nested fields of struct arguments (e.g., user.id)
  • Add custom key-value pairs to the log output using custom(...)
  • Async support with special clone_upfront option for ownership in async contexts
  • Supports both synchronous and asynchronous functions
  • All logging is done through the tracing macros (info!, debug!, warn!, error!, trace!)
  • No level attribute: Use the desired tracing macro directly in your function body

๐Ÿ“ฆ Installation

Add log_args to your Cargo.toml:

[dependencies]
log_args = "0.1" # Replace with the latest version from crates.io
tracing = "0.1"

You'll also need a compatible tracing subscriber to process and display logs. For example:

// Initialize a simple console subscriber
tracing_subscriber::fmt().init();

๐Ÿ”ง Basic Usage

Log All Arguments

By default, #[params] logs all arguments of a function.

use log_args::params;
use tracing::info;

#[derive(Debug)]
struct User { id: u32, name: String }

#[params]
fn process_user(user: User, task_id: i32) {
    info!("Processing task");
}

// Log output will be similar to:
// INFO process_user: Processing task user=User { id: 42, name: "Alice" } task_id=100

Log Specific Fields

You can specify which arguments or fields to log using the fields(...) attribute:

use log_args::params;
use tracing::info;

#[derive(Debug)]
struct User { id: u32, name: String }

#[params(fields(user.id, user.name))]
fn process_user(user: User, task_id: i32) {
    info!("Processing task");
}

// Log output will be similar to:
// INFO process_user: Processing task user.id=42 user.name="Alice"

Add Custom Key-Value Pairs

Use custom(...) to add static key-value pairs to your logs:

use log_args::params;
use tracing::info;

#[derive(Debug)]
struct User { id: u32, name: String }

#[params(custom(service_name = "user-service", version = "1.0"))]
fn process_user(user: User, task_id: i32) {
    info!("Processing task");
}

// Log output will be similar to:
// INFO process_user: Processing task user=User { id: 42, name: "Alice" } task_id=100 service_name="user-service" version="1.0"

Combine Multiple Options

You can combine fields and custom options:

use log_args::params;
use tracing::info;

#[derive(Debug)]
struct User { id: u32, name: String }

#[params(fields(user.id), custom(service_name = "user-service"))]
fn process_user(user: User, task_id: i32) {
    info!("Processing task");
}

// Log output will be similar to:
// INFO process_user: Processing task user.id=42 service_name="user-service"

๐Ÿ”„ Advanced Usage: Async Functions

The #[params] macro works seamlessly with async functions:

use log_args::params;
use tracing::info;
use tokio::time::sleep;
use std::time::Duration;

#[derive(Debug)]
struct User { id: u32, name: String }

#[params]
async fn process_user_async(user: User, task_id: i32) {
    info!("Starting async task");
    sleep(Duration::from_millis(100)).await;
    info!("Completed async task");
}

Using clone_upfront for Async Contexts

When working with async code, especially when moving values into async move blocks or tokio::spawn, you might encounter ownership issues. The clone_upfront option addresses this by ensuring fields can be safely used throughout your async function:

use log_args::params;
use tracing::info;

#[derive(Debug, Clone)]
struct Client { id: String, name: String }

#[params(clone_upfront, fields(client.id, client.name))]
async fn process_client(client: Client) {
    info!("Starting client processing");
    
    // Move client into an async block
    let task = tokio::spawn(async move {
        // Use client here without ownership issues
        info!("Processing client in spawned task");
        client
    });
    
    // Logs still work even though client was moved
    // because values were cloned upfront
    info!("Waiting for client processing to complete");
}

The clone_upfront option is particularly useful when:

  • You need to move values into async move blocks
  • You're using tokio::spawn or similar functions
  • You want to log values even after they've been moved

๐Ÿ› ๏ธ Feature Reference

Attribute Options

Option Description Example
fields(...) Specify which fields to log #[params(fields(user.id, count))]
custom(...) Add custom key-value pairs #[params(custom(version = "1.0"))]
clone_upfront Clone fields for safe use in async contexts #[params(clone_upfront)]

Logging Options

The #[params] macro redefines the following tracing macros within the function body:

  • info!
  • warn!
  • error!
  • debug!
  • trace!

Use these macros as you normally would - the function arguments will be automatically included in the output.


๐Ÿ“‹ Examples

Check out the examples directory for more detailed usage patterns:

  • examples/basic.rs: Basic usage with all arguments
  • examples/selected_fields.rs: Logging specific fields
  • examples/custom_fields.rs: Adding custom key-value pairs
  • examples/async_function.rs: Usage with async functions
  • examples/async_clone_upfront.rs: Using clone_upfront with async functions
  • examples/subfields.rs: Logging nested struct fields

๐Ÿ” How It Works

The #[params] macro:

  1. Analyzes the function signature to find available arguments
  2. Processes attribute options like fields(...) and custom(...)
  3. Redefines tracing macros within the function scope to automatically include the specified fields
  4. With clone_upfront, ensures values are safely cloned to prevent ownership issues in async contexts

The macro does not add overhead beyond the normal cost of logging and cloning when needed.


โš ๏ธ Limitations

  • The #[params] macro redefines tracing macros within function scope, which may generate unused macro warnings if not all redefined macros are used (these are suppressed internally)
  • When using clone_upfront, fields must implement Clone
  • Deeply nested or complex field expressions may not be properly captured
  • Currently, directly using a struct in a field expression (e.g., fields(user) instead of fields(user.id)) may not work as expected; use individual fields instead

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


๐Ÿงต Example: End-to-End

use log_args::log_args;
use tracing_subscriber;

#[derive(Debug)]
struct User {
    id: u32,
    name: String,
}

#[log_args(fields(user.id, user.name), custom("service" = "auth"))]
fn login(user: User) {
    info!("Login started");
    warn!("Invalid password");
}

Logs:

INFO login: user_id=42 user_name="Alice" service="auth" Login started
WARN login: user_id=42 user_name="Alice" service="auth" Invalid password

โŒ Incorrect usage:

#[params]
fn foo() {
    tracing::debug!("debug message"); // will NOT be enriched
}

Always import the macros you use:

use tracing::{debug, info, warn, error};

๐Ÿ”ฎ Future Enhancements

  • #[log_args(span = true)]: Optional span-based logging for subfunction support
  • #[log_args(log_return)]: Auto-log return values
  • Integration with opentelemetry and structured span hierarchy

โœ… License

MIT or Apache 2.0 โ€” your choice.


๐Ÿ™Œ Contributions

PRs, issues, and feedback are welcome. Letโ€™s make logging in Rust ergonomic and powerful, together.


๐Ÿ“ซ Contact

Maintained by [MKJS Tech](mailto:mkjsm57@gmail.com) โ€ข Feel free to reach out via mail or GitHub Issues.