Crate log_args

Source
Expand description

§log-args: Procedural Macro for Logging Function Arguments with Async Support

This crate provides the #[params] attribute macro to automatically log function arguments using the tracing crate, with special support for handling ownership in asynchronous contexts.

§Features

  • Log all function arguments automatically or select specific ones
  • Log nested fields of struct arguments (e.g., user.id)
  • Add custom key-value pairs to log output
  • Compatible with both synchronous and asynchronous functions
  • Special clone_upfront option for handling ownership in async move blocks and spawned tasks
  • No tracing spans or span-related features used - just simple structured logging

§Basic Usage

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

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

// Log all arguments
#[params]
fn process_user(user: User, count: usize) {
    info!("Processing user data");
    // Logs: INFO process_user: Processing user data user=User { id: 42, name: "Alice" } count=5
}

// Log only specific fields
#[params(fields(user.id, count))]
fn validate_user(user: User, count: usize) {
    info!("Validating user");
    // Logs: INFO validate_user: Validating user user.id=42 count=5
}

// Add custom values
#[params(custom(service = "auth", version = "1.0"))]
fn authenticate(user: User) {
    info!("Authentication attempt");
    // Logs: INFO authenticate: Authentication attempt user=User { id: 42, name: "Alice" } service="auth" version="1.0"
}

§Advanced: Async Support with clone_upfront

When working with asynchronous 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
        // ...
        client
    });
     
    // Logs still work even though client was moved
    // because values were cloned upfront
    info!("Waiting for client processing to complete");
}

§Macro Attributes

  • fields(...): Log only the specified arguments or fields (e.g., fields(user.id, count)). This is useful for reducing log verbosity or accessing nested fields of struct arguments.

  • custom(...): Add custom key-value pairs to the log output (e.g., custom(service = "auth")). This allows adding static context to your logs that isn’t directly from function arguments.

  • clone_upfront: Clone fields upfront to avoid ownership issues in async contexts. This is particularly useful when working with async move blocks or spawned tasks where the original variables are moved.

§How It Works

The #[params] macro redefines the tracing macros (info!, warn!, error!, debug!, trace!) within the function body to automatically include the specified function arguments or fields in the log output. For clone_upfront, it generates code that safely handles ownership by cloning values as needed to ensure they’re available throughout the function execution.

When using with async functions, especially those containing tokio::spawn with async move blocks, be careful about ownership and lifetimes. There are three patterns for handling this:

§Pattern 1: Using with owned parameters (default behavior)

#[params(fields(session.user_id, session.session_id))]
async fn process_session(session: Session) {
    info!("Processing session"); // Fields are cloned here
     
    // Do some processing
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
     
    info!("Session processing completed"); // Fields are cloned here too
}

§Pattern 2: Using the clone_upfront option (for async functions)

This pattern clones all fields at the beginning of the function, which helps with logging in async functions. Note that for tokio::spawn with async move blocks, you’ll still need to clone the fields again before the async block:

#[params(clone_upfront, fields(self.client_id, self.company_id))]
async fn run(self, socket: WebSocket) {
    info!("Starting connection"); // Uses cloned fields
     
    // Clone variables are created at the beginning of the function
    // so they're available for all tracing macros in the main function body
     
    // For tokio::spawn, you need to clone again for the async block
    let task_client_id = self.client_id.clone();
    let task_company_id = self.company_id.clone();
     
    let task = tokio::spawn(async move {
        // Use the task-specific clones inside the async block
        info!(client_id = ?task_client_id, company_id = ?task_company_id, "Worker task started");
    });
     
    info!("Connection handler completed"); // Uses the original cloned fields
}

§Pattern 3: Manual logging with references and async blocks

When you need more control, you can manually clone and log fields:

async fn handle_connection(client_id: String, company_id: String) {
    // Log manually at the beginning
    info!(client_id = ?client_id, company_id = ?company_id, "Starting connection");

    // Clone what we need for the async block
    let task_client_id = client_id.clone();

    // Spawn the async task with its own cloned data
    let task = tokio::spawn(async move {
        info!(client_id = ?task_client_id, "Worker task started");
    });

    // This log can still use the original client_id
    info!(client_id = ?client_id, "Connection handler completed");
}

See the examples directory for more detailed usage patterns.

§Limitations

  • Only works with the tracing crate macros (info!, debug!, warn!, error!, trace!).
  • Does not support span creation or level selection via macro input.
  • All arguments must implement Debug for structured logging.
  • For async code with tokio::spawn, use the clone_upfront option to avoid ownership issues.
  • Generates warnings about unused macro definitions (these are expected and can be ignored).

See the examples directory for more detailed usage patterns.

Attribute Macros§

params
A procedural macro attribute for automatically logging function arguments.