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