log4rs
Rust logging crate modeled after Java log4j. Ports philosophy of Java logging package most deployed in open source software to Rust. Maintains compatibility with log crate, usable while preserving existing paradigms.
Library
log4rs
Overview
log4rs is a Rust logging crate modeled after Java log4j. It ports the philosophy of the Java logging package most deployed in open source software to Rust, maintaining compatibility with the log crate while preserving existing paradigms. This configuration-based logging framework is characterized by high configurability and flexibility, composed of basic units: appenders, encoders, filters, and loggers.
Details
While log4rs still sees selection examples by Rust developers with Java background in 2025, other choices tend to be prioritized in modern Rust development practices. Adoption is seen in corporate environments preferring configuration file-based management, but new recommendation is low from maintainability perspective. However, with its rich feature set and flexible configuration capabilities, it remains an effective choice for scenarios requiring complex logging requirements.
Key Features
- Java log4j Compatible Design: Concepts and API patterns familiar to Java developers
- Configuration-based Management: Flexible control through YAML or programmatic configuration
- Automatic Log Rotation: Automatic archiving based on file size or time
- Appender System: Support for various output destinations like console, file, syslog
- Encoder Functionality: Flexible log output formats through custom formatting
- Filter Functionality: Detailed control through log levels and conditions
Pros and Cons
Pros
- Easy to learn for developers familiar with Java log4j concepts
- External configuration management possible through YAML configuration files
- Reduced operational burden through automatic log rotation functionality
- Detailed customization possible through rich configuration options
- Easy integration with existing code through compatibility with standard log crate
- Support for various output destinations including file, console, syslog
Cons
- Other libraries tend to be prioritized in modern Rust development practices
- High learning cost due to configuration complexity, excessive for simple uses
- Java-style design may not align well with Rust-like coding style
- Lower maintenance frequency compared to other popular libraries
- Low recommendation for new projects with limited community support
- Performance inferior to modern choices like tracing and slog
Reference Pages
Usage Examples
Installation and Setup
# Add dependencies to Cargo.toml
[dependencies]
log = "0.4"
log4rs = "1.3"
# Specify feature flags (when using YAML configuration)
log4rs = { version = "1.3", features = ["yaml_format"] }
# Enable all features
log4rs = { version = "1.3", features = ["all_components", "gzip", "file", "yaml_format"] }
Basic Programmatic Configuration
use log::{info, warn, error, debug};
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Logger, Root};
use log4rs::init_config;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Console appender configuration
let stdout = ConsoleAppender::builder()
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S)} [{l}] {M}:{L} - {m}{n}"
)))
.build();
// File appender configuration
let logfile = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S%.3f)} [{l}] {t} {M}:{L} - {m}{n}"
)))
.build("logs/app.log")?;
// Build configuration
let config = Config::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("logfile", Box::new(logfile)))
.logger(Logger::builder()
.appender("logfile")
.additive(false)
.build("myapp::database", log::LevelFilter::Info))
.build(Root::builder()
.appender("stdout")
.appender("logfile")
.build(log::LevelFilter::Debug))?;
// Initialize logger
let _handle = log4rs::init_config(config)?;
// Output logs
info!("Application started");
debug!("Debug info: Configuration loaded successfully");
warn!("Warning: Temporary configuration is being used");
error!("Error: Database connection failed");
Ok(())
}
Management through YAML Configuration File
# log4rs.yaml
refresh_rate: 30 seconds
appenders:
# Console output configuration
stdout:
kind: console
encoder:
pattern: "{d(%Y-%m-%d %H:%M:%S)} [{l}] {M}:{L} - {m}{n}"
# File output configuration
requests:
kind: file
path: "logs/requests.log"
encoder:
pattern: "{d(%Y-%m-%d %H:%M:%S%.3f)} [{l}] {t} {M}:{L} - {m}{n}"
# File output with rotation
rolling_file:
kind: rolling_file
path: "logs/app.log"
policy:
kind: compound
trigger:
kind: size
limit: 10mb
roller:
kind: fixed_window
pattern: "logs/app.{}.log"
base: 1
count: 5
encoder:
pattern: "{d(%Y-%m-%d %H:%M:%S%.3f)} [{l}] {t} {M}:{L} - {m}{n}"
# Root logger configuration
root:
level: info
appenders:
- stdout
- rolling_file
# Specific module logger configuration
loggers:
myapp::database:
level: debug
appenders:
- requests
additive: false
myapp::network:
level: warn
appenders:
- stdout
// main.rs using YAML configuration file
use log::{info, warn, error, debug};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize from YAML configuration file
log4rs::init_file("log4rs.yaml", Default::default())?;
// Application logs
info!("Application started");
debug!("Debug information will be output");
warn!("Warning message");
error!("An error occurred");
// Module-specific log examples
database_operation();
network_operation();
Ok(())
}
// Database module log example
mod database {
use log::{info, debug, error};
pub fn connect() {
debug!("Starting database connection");
info!("Successfully connected to database");
}
pub fn query(sql: &str) {
debug!("Executing SQL query: {}", sql);
info!("Query executed successfully");
}
}
// Network module log example
mod network {
use log::{warn, error};
pub fn send_request(url: &str) {
warn!("Sending request: {}", url);
error!("Network error occurred");
}
}
fn database_operation() {
database::connect();
database::query("SELECT * FROM users");
}
fn network_operation() {
network::send_request("https://api.example.com/data");
}
Advanced Rotation Configuration
use log4rs::append::rolling_file::RollingFileAppender;
use log4rs::append::rolling_file::policy::compound::CompoundPolicy;
use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger;
use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Root};
fn setup_rotating_logger() -> Result<(), Box<dyn std::error::Error>> {
// Size-based trigger (10MB)
let trigger = SizeTrigger::new(10 * 1024 * 1024);
// Fixed window roller (maximum 5 files)
let roller = FixedWindowRoller::builder()
.base(1)
.build("logs/app.{}.log", 5)?;
// Compound policy
let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
// Rolling file appender
let rolling_appender = RollingFileAppender::builder()
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S%.3f)} [{l}] {t} {f}:{L} - {m}{n}"
)))
.build("logs/app.log", Box::new(policy))?;
// Configuration
let config = Config::builder()
.appender(
Appender::builder()
.build("rolling_file", Box::new(rolling_appender))
)
.build(Root::builder()
.appender("rolling_file")
.build(log::LevelFilter::Info))?;
log4rs::init_config(config)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
setup_rotating_logger()?;
// Generate large amount of logs to test rotation
for i in 0..10000 {
log::info!("Log entry #{}: Log message containing large amount of data", i);
if i % 1000 == 0 {
log::warn!("Progress: {} logs have been output", i);
}
}
log::info!("Log rotation test completed");
Ok(())
}
Custom Encoder and Filter
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::encode::json::JsonEncoder;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::filter::threshold::ThresholdFilter;
use log4rs::config::{Appender, Config, Logger, Root};
fn setup_custom_logger() -> Result<(), Box<dyn std::error::Error>> {
// JSON format encoder
let json_encoder = JsonEncoder::new();
// Custom pattern encoder
let pattern_encoder = PatternEncoder::new(
"[{d(%Y-%m-%d %H:%M:%S%.3f)}] {h({l})} {t} {f}:{L} - {m}{n}"
);
// Warn level and above filter
let warn_filter = ThresholdFilter::new(log::LevelFilter::Warn);
// JSON format file appender
let json_file = FileAppender::builder()
.encoder(Box::new(json_encoder))
.build("logs/app.json")?;
// Console appender with filter
let console = ConsoleAppender::builder()
.encoder(Box::new(pattern_encoder))
.build();
// Error-only file appender
let error_file = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S)} [ERROR] {M}:{L} - {m}{n}"
)))
.build("logs/errors.log")?;
let config = Config::builder()
.appender(
Appender::builder()
.filter(Box::new(warn_filter))
.build("console", Box::new(console))
)
.appender(Appender::builder().build("json_file", Box::new(json_file)))
.appender(Appender::builder().build("error_file", Box::new(error_file)))
.logger(Logger::builder()
.appender("error_file")
.additive(false)
.build("myapp::critical", log::LevelFilter::Error))
.build(Root::builder()
.appender("console")
.appender("json_file")
.build(log::LevelFilter::Debug))?;
log4rs::init_config(config)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
setup_custom_logger()?;
// Output various levels of logs
log::trace!("Trace level - Detailed debug information");
log::debug!("Debug level - Development information");
log::info!("Info level - General information");
log::warn!("Warn level - Situation requiring attention");
log::error!("Error level - Error occurred");
// Critical log example
log::error!("Database connection error: Critical issue occurred");
Ok(())
}
Practical Integration Example
use log::{info, warn, error, debug};
use serde_json::{json, Value};
use std::collections::HashMap;
// Structured log helper
fn log_user_action(user_id: u64, action: &str, details: Value) {
info!(
"User action [user_id={}] [action={}] [details={}]",
user_id, action, details
);
}
// Error log helper
fn log_error_with_context(error: &str, context: HashMap<&str, &str>) {
let context_str = context.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(" ");
error!("Error occurred: {} [context: {}]", error, context_str);
}
// Performance measurement log
struct PerformanceLogger {
operation: String,
start_time: std::time::Instant,
}
impl PerformanceLogger {
fn new(operation: &str) -> Self {
debug!("Starting: {}", operation);
Self {
operation: operation.to_string(),
start_time: std::time::Instant::now(),
}
}
}
impl Drop for PerformanceLogger {
fn drop(&mut self) {
let duration = self.start_time.elapsed();
info!("Completed: {} [duration: {:?}]", self.operation, duration);
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize log configuration
log4rs::init_file("log4rs.yaml", Default::default())?;
info!("Application starting");
// User action log
log_user_action(12345, "login", json!({
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"timestamp": "2025-06-24T10:30:00Z"
}));
// Performance measurement
{
let _perf = PerformanceLogger::new("Database query execution");
std::thread::sleep(std::time::Duration::from_millis(150));
// Execute database operations here
}
// Error log
let mut error_context = HashMap::new();
error_context.insert("user_id", "12345");
error_context.insert("operation", "update_profile");
error_context.insert("table", "users");
log_error_with_context("Data update failed", error_context);
// Configuration change log
warn!("Configuration file has been changed. Application restart is recommended");
info!("Application terminating");
Ok(())
}