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.

LoggingRustConfiguration-basedHigh PerformanceYAML ConfigurationFile Rotation

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(())
}