nonblocking-logger 0.1.3

A high-performance library with format string support
Documentation

Nonblocking-Logger - A High-Performance Rust Logging Library

A high-performance logging library for Rust with format string support and WebAssembly compatibility. Nonblocking-Logger provides fast, efficient logging with minimal overhead.

🚀 Key Advantages Over Other Logging Crates

High Performance

  • Fast I/O: Direct writing to targets with minimal overhead
  • Low latency: Logs are written immediately
  • Simple and reliable: Clean, straightforward implementation

Format String Support Like println!

  • Familiar API: Use info!("User {} logged in", user_id) just like println!
  • Full format specifiers: Support for {:.2}, {:?}, {:#x}, etc.
  • Lazy evaluation: Expensive computations only run when needed with debug_lazy!()

WebAssembly Ready

  • Browser console logging: Works seamlessly in WebAssembly targets
  • Same API everywhere: Write once, run on native and web platforms

Performance Optimized

  • Minimal dependencies: Only simple-datetime-rs for time formatting
  • Efficient I/O: Fast writing with minimal overhead
  • Memory efficient: String formatting with minimal allocations

Developer Experience

  • Multiple output targets: stdout, stderr, files, or any Write implementer
  • Custom time formats: Use standard datetime format strings
  • Level-specific formatting: Different formats for different log levels
  • Comprehensive examples: 10+ examples covering all use cases

Features

  • Macro-based logging with formatting - info!(), debug!(), error!() etc. with format string support
  • Date and time formatting - use standard datetime format strings for timestamps
  • Multiple output targets - write to stdout, stderr, files, or any Write implementer
  • Custom Write targets - add any Write + Send + Sync implementer as a logging target
  • Custom log string formats for different levels
  • Environment variable configuration - configure log levels via environment variables
  • Lazy evaluation - avoid expensive computations when log level is insufficient
  • WebAssembly (WASM) support - works in browsers with console logging
  • Lightweight - minimal dependencies (only simple-datetime-rs)
  • Simple API - easy to use and configure

Quick Start

The library provides macros with format string support just like println!:

use nonblocking_logger::{info, error, debug, warning, trace};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let user_id = 42;
    let status = "active";
    
    // Simple messages
    info!("Application started")?;
    error!("Something went wrong!")?;
    
    // With formatting (like println!)
    info!("User {} has status: {}", user_id, status)?;
    warning!("CPU usage is {:.1}%", 85.5)?;
    debug!("Processing data: {:?}", vec![1, 2, 3])?;
    
    Ok(())
}

Macro-based Logging

The library provides macros that work like println! but for logging, with full format string support.

Available Macros

use nonblocking_logger::{log, info, debug, warning, error, trace};

// Basic logging macros with format string support
info!("Application started")?;
error!("Something went wrong!")?;
warning!("This is a warning")?;
debug!("Debug information")?;
trace!("Very detailed trace")?;

// With formatting (like println!)
let user_id = 42;
let status = "active";
info!("User {} has status: {}", user_id, status)?;
warning!("CPU usage is {:.1}%", 85.5)?;
debug!("Processing data: {:?}", vec![1, 2, 3])?;

// Generic logging macro (always outputs, no level filtering)
use nonblocking_logger::log;
log!("Custom message with value: {}", 42)?;

Logger-Specific Macros

When you have a specific logger instance, use the *_with macros:

use nonblocking_logger::{log_with, info_with, error_with, debug_with, Logger};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let logger = Logger::new().stdout();
    
    // Use specific logger with macros
    log_with!(logger, "Processing {} items", 5)?;
    info_with!(logger, "User {} connected", 42)?;
    error_with!(logger, "Failed to process: {}", "timeout")?;
    debug_with!(logger, "Debug info: {:?}", vec![1, 2, 3])?;
    
    Ok(())
}

Lazy Evaluation Macros

For expensive computations, use the lazy evaluation macros:

use nonblocking_logger::{debug_lazy, info_lazy, warning_lazy, error_lazy, trace_lazy};

fn expensive_computation() -> String {
    // Some expensive work
    "result".to_string()
}

// Lazy evaluation - closure only runs if log level allows
debug_lazy!(|| "Expensive computation result: {}", expensive_computation())?;
info_lazy!(|| "User {}: {}", 42, expensive_computation())?;
warning_lazy!(|| "CPU usage: {:.1}%", 85.5)?;
error_lazy!(|| "Error code: {}", 500)?;
trace_lazy!(|| "Step {} completed", "validation")?;

Logger-Specific Lazy Macros

For lazy evaluation with specific logger instances:

use nonblocking_logger::{log_lazy_with, info_lazy_with, debug_lazy_with, Logger};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let logger = Logger::new().stdout();
    
    // Lazy evaluation with specific logger
    log_lazy_with!(logger, || "Expensive: {}", expensive_computation())?;
    info_lazy_with!(logger, || "User {}: {}", 42, expensive_computation())?;
    debug_lazy_with!(logger, || "Debug: {:?}", expensive_computation())?;
    
    Ok(())
}

Format String Support

All macros support the same format string syntax as println!:

let number = 42;
let float = 3.14159;
let text = "hello";

// Basic formatting
info!("Integer: {}, Float: {:.2}, Text: {}", number, float, text)?;

// Different format specifiers
info!("Hex: 0x{:x}, Binary: 0b{:b}, Octal: 0o{:o}", number, number, number)?;
info!("Scientific: {:.2e}, Percentage: {:.1}%", float * 100.0, float * 100.0)?;

// Debug formatting for complex data
let data = vec![1, 2, 3, 4, 5];
let metadata = std::collections::HashMap::from([
    ("version", "1.0.0"),
    ("environment", "development"),
]);
debug!("Data: {:?}, Metadata: {:?}", data, metadata)?;

Try the macro example:

# Run the included example
cargo run --example macro_logging

When to Use Each Method

Use Macros when:

  • You want format string support like println!
  • You're building any application
  • You want the most convenient API

Use Logger-Specific Macros (*_with) when:

  • You have a specific logger instance with custom configuration
  • You want format string support with a particular logger
  • You need different loggers for different parts of your application
  • You want to combine macro convenience with logger-specific settings

Use Function calls when:

  • You prefer function calls over macros

Use Logger API when:

  • You need custom time formats or level-specific formatting
  • You want to write to multiple targets (stdout + file, etc.)
  • You need different loggers for different parts of your application
  • You want full control over configuration

Always-Output vs Level-Filtered Logging

The library provides two types of logging methods:

Always-Output Methods (no level filtering):

  • log!(format, ...) - Always outputs regardless of logger level
  • log_lazy!(|| format, ...) - Always outputs with lazy evaluation
  • logger.log(message) - Always outputs regardless of logger level
  • logger.log_lazy(message_fn) - Always outputs with lazy evaluation

Level-Filtered Methods (respect logger level):

  • info!(format, ...), error!(format, ...), etc. - Filtered by level
  • info_lazy!(|| format, ...), error_lazy!(|| format, ...), etc. - Filtered by level
  • logger.info(message), logger.error(message), etc. - Filtered by level
  • logger.info_lazy(message_fn), logger.error_lazy(message_fn), etc. - Filtered by level

When to use each:

  • Use always-output methods when you want to ensure a message is always logged (e.g., critical system events, audit logs)
  • Use level-filtered methods for normal application logging where you want to control verbosity via log levels

Example:

use nonblocking_logger::{log, info, log_lazy, info_lazy, warning, error, log_with, info_with, Logger, LogLevel};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Global macros (use default logger)
    info!("This info message will NOT appear")?;
    log!("This message will ALWAYS appear")?;
    
    // Logger-specific macros (use custom logger)
    let custom_logger = Logger::with_level(LogLevel::Debug).stdout();
    info_with!(custom_logger, "This will appear with debug level")?;
    log_with!(custom_logger, "This will ALWAYS appear with custom logger")?;
    
    Ok(())
}

Time Format Strings

The time formatting uses standard datetime format strings for maximum flexibility:

  • %Y-%m-%d %H:%M:%S - Date and time (default): "2025-09-14 19:50:08"
  • %H:%M:%S - Time only: "19:50:08"
  • %Y-%m-%d - Date only: "2025-09-14"
  • %Y-%m-%d %H:%M:%S%.3f - With milliseconds: "2025-09-14 19:50:08.123"
  • %Y-%m-%d %H:%M:%S.%f - With fractional seconds: "2025-09-14 19:50:08.123456"
  • %Y-%m-%d %H:%M:%S %z - With timezone: "2025-09-14 19:50:08 Z"

Examples:

  • "%Y-%m-%d %H:%M:%S" → "2025-09-14 19:50:08"
  • "%H:%M:%S" → "19:50:08"
  • "%Y-%m-%d" → "2025-09-14"
  • "%Y-%m-%d %H:%M:%S%.3f" → "2025-09-14 19:50:08.123"
  • "%Y-%m-%d %H:%M:%S.%f" → "2025-09-14 19:50:08.123456"

Environment Variable Configuration

The library supports configuring log levels through environment variables, following Rust conventions:

Basic Configuration

Set the global log level using either RUST_LOG or LOG_LEVEL:

# Using RUST_LOG (Rust convention)
export RUST_LOG=debug

# Using LOG_LEVEL (fallback)
export LOG_LEVEL=info

Supported Log Levels

  • error or err - Error messages only
  • warning or warn - Warning and error messages
  • info - Informational, warning, and error messages (default)
  • debug - Debug, info, warning, and error messages
  • trace - All messages including trace

Case Insensitive

Log levels are case-insensitive:

export RUST_LOG=DEBUG    # Same as debug
export RUST_LOG=WARN     # Same as warn
export RUST_LOG=Error    # Same as error

API Reference

Global Convenience Functions

Simple string logging without formatting (also available as function calls):

Basic Logging Functions

  • error(message: &str) -> io::Result<()> - Log error message (filtered by level)
  • warning(message: &str) -> io::Result<()> - Log warning message (filtered by level)
  • info(message: &str) -> io::Result<()> - Log info message (filtered by level)
  • debug(message: &str) -> io::Result<()> - Log debug message (filtered by level)
  • trace(message: &str) -> io::Result<()> - Log trace message (filtered by level)
  • log(message: &str) -> io::Result<()> - Log message (always outputs, no level filtering)

Lazy Evaluation Functions

  • error_lazy<F>(message_fn: F) -> io::Result<()> - Log error with lazy evaluation (filtered by level)
  • warning_lazy<F>(message_fn: F) -> io::Result<()> - Log warning with lazy evaluation (filtered by level)
  • info_lazy<F>(message_fn: F) -> io::Result<()> - Log info with lazy evaluation (filtered by level)
  • debug_lazy<F>(message_fn: F) -> io::Result<()> - Log debug with lazy evaluation (filtered by level)
  • trace_lazy<F>(message_fn: F) -> io::Result<()> - Log trace with lazy evaluation (filtered by level)
  • log_lazy<F>(message_fn: F) -> io::Result<()> - Log with lazy evaluation (always outputs, no level filtering)

Initialization Functions

  • get_global_logger() -> &'static Logger - Get the global logger instance

Logger

The logger struct with fluent API for custom formatting or multiple targets.

Methods

  • new() - Create a new logger with default settings
  • with_level(level) - Create a logger with specific log level
  • from_env() - Create a logger with level from environment variables
  • time_format(format) - Set time format using datetime format string
  • no_time_prefix() - Disable time prefix
  • format_for_level(level, format) - Set custom format for specific level
  • add_target(target) - Add custom Write target (any Write + Send + Sync implementer)
  • custom(target) - Set custom Write target (replaces all existing targets)
  • stdout() - Add stdout as target
  • stderr() - Add stderr as target
  • file(path) - Add file as target
  • log(message) - Log message (always outputs, no level filtering)
  • log_lazy(message_fn) - Log with lazy evaluation (always outputs, no level filtering)
  • error(message) - Log error message (filtered by level)
  • warning(message) - Log warning message (filtered by level)
  • info(message) - Log info message (filtered by level)
  • debug(message) - Log debug message (filtered by level)
  • trace(message) - Log trace message (filtered by level)

LogLevel

Log levels in order of priority (higher levels include lower ones):

  • Error - Error messages
  • Warning - Warning messages
  • Info - Informational messages
  • Debug - Debug messages
  • Trace - Trace messages

LogMessage

A structured log message with level.

Methods

  • new(level, message) - Create new log message
  • error(message) - Create error message
  • warning(message) - Create warning message
  • info(message) - Create info message
  • debug(message) - Create debug message
  • trace(message) - Create trace message

Format String Placeholders

  • {time} - Time prefix (formatted using the time_format setting)
  • {level} - Log level (ERROR, WARN, INFO, DEBUG, TRACE)
  • {message} - The actual log message

How Time Formatting Works

The time_format() method sets the datetime format string used to format the {time} placeholder. This allows you to:

  1. Set a global time format that applies to all log levels
  2. Use different time formats in custom level-specific formats
  3. Mix time formats with custom formatting
// Global time format affects all levels
let logger = Logger::new()
    .time_format("%H:%M:%S")  // Sets time format for {time} placeholder
    .stdout();

// Custom level formats can use the {time} placeholder
let logger = Logger::new()
    .time_format("%Y-%m-%d %H:%M:%S")
    .format_for_level(LogLevel::Error, "đŸ”Ĩ {time} ERROR: {message}")
    .format_for_level(LogLevel::Info, "â„šī¸  {time} INFO: {message}")
    .stdout();

WebAssembly (WASM) Support

This library supports WebAssembly targets (wasm32-unknown-unknown) for use in web browsers. When compiled for WASM:

  • Logs are written directly to the browser's developer console
  • File logging is not available (browsers don't allow direct file access)
  • Same API as the native version for easy porting

WASM Usage

use nonblocking_logger::{error, warning, info, debug, trace, log, log_lazy};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Log messages (will appear in browser console)
    error!("This is an error")?;
    info!("This is info")?;
    warning!("This is a warning")?;
    debug!("Debug information")?;
    trace!("Trace information")?;
    
    // Always-output logging (no level filtering)
    log!("This message will always appear")?;
    log_lazy!(|| "This lazy message will always appear")?;
    
    // With formatting
    let user_id = 42;
    let status = "active";
    info!("User {} has status: {}", user_id, status)?;
    error!("Error code: {}", 500)?;
    
    Ok(())
}

Using AsyncWrite with Logging

While the library doesn't have built-in AsyncWrite support, you can easily convert AsyncWrite to Write for logging. In most cases, short log message write operations are fast and don't benefit from async I/O, making synchronous logging more efficient. See the complete example:

cargo run --example async_write_logging

This example shows how to:

  • Convert any AsyncWrite + Unpin to a sync Write trait
  • Bridge async and sync I/O seamlessly using futures::block_on
  • Create custom AsyncWrite implementations
  • Use async streams with synchronous logging infrastructure

License

MIT