# 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!`:
```rust
use nonblocking_logger::{info, error, debug, warning, trace};
fn main() {
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]);
}
```
## Macro-based Logging
The library provides macros that work like `println!` but for logging, with full format string support.
### Available Macros
```rust
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:
```rust
use nonblocking_logger::{log_with, info_with, error_with, debug_with, Logger};
fn main() {
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]);
}
```
### Lazy Evaluation Macros
For expensive computations, use the lazy evaluation macros:
```rust
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:
```rust
use nonblocking_logger::{log_lazy_with, info_lazy_with, debug_lazy_with, Logger};
fn main() {
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());
}
```
### Format String Support
All macros support the same format string syntax as `println!`:
```rust
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:**
```bash
# 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:**
```rust
use nonblocking_logger::{log, info, log_lazy, info_lazy, warning, error, log_with, info_with, Logger, LogLevel};
fn main() {
// 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");
}
```
## 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`:
```bash
# 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:
```bash
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(format)` - Set a single custom format for **all** levels
- `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
```rust
// 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}".to_string())
.format_for_level(LogLevel::Info, "âšī¸ {time} INFO: {message}".to_string())
.stdout();
// Or build the format string dynamically (for example, with a TLS prefix)
const LOG_PREFIX: &str = "worker-1";
let logger = Logger::new()
.time_format("%Y-%m-%d %H:%M:%S")
.format(format!("{{time}} [{{level}}][{}] {{message}}", LOG_PREFIX))
.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
```rust
use nonblocking_logger::{error, warning, info, debug, trace, log, log_lazy};
fn main() {
// 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);
}
```
## 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:
```bash
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