log4rs

Java log4jをモデルにしたRustロギングクレート。オープンソースソフトウェアで最も展開されているJavaロギングパッケージの思想をRustに移植。logクレートとの互換性を保ち、既存パラダイムを維持しながら利用可能。

ロギングRust設定ベース高性能YAML設定ファイルローテーション

ライブラリ

log4rs

概要

log4rsは、Java log4jをモデルにしたRustロギングクレートです。オープンソースソフトウェアで最も展開されているJavaロギングパッケージの思想をRustに移植し、logクレートとの互換性を保ちながら既存パラダイムを維持して利用可能。高度な設定可能性と柔軟性を特徴とし、アペンダー、エンコーダー、フィルター、ロガーの基本単位で構成される設定ベースのロギングフレームワークです。

詳細

log4rsは2025年でもJava背景を持つRust開発者による選択例が存在しますが、現代的なRust開発慣例では他の選択肢が優先される傾向にあります。設定ファイルベースの管理を好む企業環境での採用が見られる一方、メンテナンス性の観点で新規推奨度は低くなっています。しかし、豊富な機能セットと柔軟な設定能力により、複雑なログ要件が必要な場面では有効な選択肢となります。

主な特徴

  • Java log4j互換設計: Java開発者に馴染みのある概念とAPIパターン
  • 設定ベース管理: YAMLまたはプログラマティック設定による柔軸な制御
  • 自動ログローテーション: ファイルサイズや時間ベースの自動アーカイブ機能
  • アペンダーシステム: コンソール、ファイル、syslogなど多様な出力先対応
  • エンコーダー機能: カスタムフォーマットによる柔軟なログ出力形式
  • フィルター機能: ログレベルや条件による詳細な制御

メリット・デメリット

メリット

  • Java log4jの概念を理解している開発者には習得が容易
  • YAML設定ファイルによる外部化された設定管理が可能
  • 自動ログローテーション機能により運用負荷が軽減
  • 豊富な設定オプションによる詳細なカスタマイズが可能
  • 標準logクレートとの互換性により既存コードとの統合が容易
  • ファイル、コンソール、syslogなど多様な出力先への対応

デメリット

  • 現代的なRust開発慣例では他のライブラリが優先される傾向
  • 設定の複雑さにより学習コストが高く、簡単な用途には過剰
  • Java風の設計がRustらしいコードスタイルと相性が悪い場合がある
  • メンテナンス頻度が他の人気ライブラリと比較して低い
  • 新規プロジェクトでの推奨度が低く、コミュニティサポートが限定的
  • パフォーマンス面でtracingやslogなど現代的な選択肢に劣る

参考ページ

書き方の例

インストールとセットアップ

# Cargo.tomlに依存関係を追加
[dependencies]
log = "0.4"
log4rs = "1.3"

# 機能フラグの指定(YAML設定を使用する場合)
log4rs = { version = "1.3", features = ["yaml_format"] }

# 全機能を有効にする場合
log4rs = { version = "1.3", features = ["all_components", "gzip", "file", "yaml_format"] }

基本的なプログラマティック設定

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>> {
    // コンソールアペンダーの設定
    let stdout = ConsoleAppender::builder()
        .encoder(Box::new(PatternEncoder::new(
            "{d(%Y-%m-%d %H:%M:%S)} [{l}] {M}:{L} - {m}{n}"
        )))
        .build();

    // ファイルアペンダーの設定
    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")?;

    // 設定の構築
    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))?;

    // ロガーの初期化
    let _handle = log4rs::init_config(config)?;

    // ログの出力
    info!("アプリケーションが開始されました");
    debug!("デバッグ情報: 設定が正常に読み込まれました");
    warn!("警告: 一時的な設定が使用されています");
    error!("エラー: データベース接続に失敗しました");

    Ok(())
}

YAML設定ファイルによる管理

# log4rs.yaml
refresh_rate: 30 seconds

appenders:
  # コンソール出力設定
  stdout:
    kind: console
    encoder:
      pattern: "{d(%Y-%m-%d %H:%M:%S)} [{l}] {M}:{L} - {m}{n}"

  # ファイル出力設定
  requests:
    kind: file
    path: "logs/requests.log"
    encoder:
      pattern: "{d(%Y-%m-%d %H:%M:%S%.3f)} [{l}] {t} {M}:{L} - {m}{n}"

  # ローテーション付きファイル出力
  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:
  level: info
  appenders:
    - stdout
    - rolling_file

# 特定モジュールのロガー設定
loggers:
  myapp::database:
    level: debug
    appenders:
      - requests
    additive: false

  myapp::network:
    level: warn
    appenders:
      - stdout
// YAML設定ファイルを使用したmain.rs
use log::{info, warn, error, debug};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // YAML設定ファイルからの初期化
    log4rs::init_file("log4rs.yaml", Default::default())?;

    // アプリケーションログ
    info!("アプリケーションが開始されました");
    debug!("デバッグ情報が出力されます");
    warn!("警告メッセージ");
    error!("エラーが発生しました");

    // モジュール別ログの例
    database_operation();
    network_operation();

    Ok(())
}

// データベースモジュールのログ例
mod database {
    use log::{info, debug, error};

    pub fn connect() {
        debug!("データベースへの接続を開始");
        info!("データベースに正常接続しました");
    }

    pub fn query(sql: &str) {
        debug!("SQLクエリ実行: {}", sql);
        info!("クエリが正常に実行されました");
    }
}

// ネットワークモジュールのログ例
mod network {
    use log::{warn, error};

    pub fn send_request(url: &str) {
        warn!("リクエスト送信: {}", url);
        error!("ネットワークエラーが発生しました");
    }
}

fn database_operation() {
    database::connect();
    database::query("SELECT * FROM users");
}

fn network_operation() {
    network::send_request("https://api.example.com/data");
}

高度なローテーション設定

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>> {
    // サイズベースのトリガー(10MB)
    let trigger = SizeTrigger::new(10 * 1024 * 1024);

    // 固定ウィンドウローラー(最大5個のファイル)
    let roller = FixedWindowRoller::builder()
        .base(1)
        .build("logs/app.{}.log", 5)?;

    // コンパウンドポリシー
    let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));

    // ローリングファイルアペンダー
    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))?;

    // 設定
    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()?;

    // 大量のログを生成してローテーションをテスト
    for i in 0..10000 {
        log::info!("ログエントリ #{}: 大量のデータを含むログメッセージ", i);
        
        if i % 1000 == 0 {
            log::warn!("進捗: {}件のログが出力されました", i);
        }
    }

    log::info!("ログローテーションテストが完了しました");
    Ok(())
}

カスタムエンコーダーとフィルター

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形式のエンコーダー
    let json_encoder = JsonEncoder::new();

    // カスタムパターンエンコーダー
    let pattern_encoder = PatternEncoder::new(
        "[{d(%Y-%m-%d %H:%M:%S%.3f)}] {h({l})} {t} {f}:{L} - {m}{n}"
    );

    // Warnレベル以上のフィルター
    let warn_filter = ThresholdFilter::new(log::LevelFilter::Warn);

    // JSON形式ファイルアペンダー
    let json_file = FileAppender::builder()
        .encoder(Box::new(json_encoder))
        .build("logs/app.json")?;

    // フィルター付きコンソールアペンダー
    let console = ConsoleAppender::builder()
        .encoder(Box::new(pattern_encoder))
        .build();

    // エラー専用ファイルアペンダー
    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()?;

    // 様々なレベルのログ出力
    log::trace!("トレースレベル - 詳細なデバッグ情報");
    log::debug!("デバッグレベル - 開発時の情報");
    log::info!("情報レベル - 一般的な情報");
    log::warn!("警告レベル - 注意が必要な状況");
    log::error!("エラーレベル - エラーが発生");

    // クリティカルログの例
    log::error!("データベース接続エラー: 重大な問題が発生");

    Ok(())
}

実用的な統合例

use log::{info, warn, error, debug};
use serde_json::{json, Value};
use std::collections::HashMap;

// 構造化ログヘルパー
fn log_user_action(user_id: u64, action: &str, details: Value) {
    info!(
        "ユーザーアクション [user_id={}] [action={}] [details={}]",
        user_id, action, details
    );
}

// エラーログヘルパー
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!("エラー発生: {} [context: {}]", error, context_str);
}

// パフォーマンス測定ログ
struct PerformanceLogger {
    operation: String,
    start_time: std::time::Instant,
}

impl PerformanceLogger {
    fn new(operation: &str) -> Self {
        debug!("開始: {}", 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!("完了: {} [実行時間: {:?}]", self.operation, duration);
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ログ設定の初期化
    log4rs::init_file("log4rs.yaml", Default::default())?;

    info!("アプリケーション開始");

    // ユーザーアクションログ
    log_user_action(12345, "login", json!({
        "ip_address": "192.168.1.100",
        "user_agent": "Mozilla/5.0...",
        "timestamp": "2025-06-24T10:30:00Z"
    }));

    // パフォーマンス測定
    {
        let _perf = PerformanceLogger::new("データベースクエリ実行");
        std::thread::sleep(std::time::Duration::from_millis(150));
        // ここでデータベース操作を実行
    }

    // エラーログ
    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("データ更新に失敗", error_context);

    // 設定変更時のログ
    warn!("設定ファイルが変更されました。アプリケーションの再起動が推奨されます");

    info!("アプリケーション終了");
    Ok(())
}