env_logger

最もダウンロード数の多いRustロガー実装(4,100万ダウンロード)。環境変数による最小設定を特徴とし、RUST_LOG環境変数でログレベルを制御。シンプルで確実な動作により、多くのRustプロジェクトで採用されている。

ロギングライブラリRust環境変数設定シンプル標準実装

ライブラリ

env_logger

概要

env_loggerは「環境変数で設定されるlogの実装」として開発された、Rustエコシステムで最もダウンロード数の多いロガー実装(4,100万ダウンロード)です。「環境変数による最小設定」をコンセプトに、RUST_LOG環境変数でログレベルを制御するシンプルで確実な動作により、多くのRustプロジェクトで採用されています。logクレートのファサードと組み合わせることで、Rust標準的なロギング体験を提供し、初学者から上級者まで幅広く使用される事実上の標準として地位を確立しています。

詳細

env_logger 2025年版はRustロギングの事実上の標準として最高の市場シェアを維持し続けています。環境変数RUST_LOGによる直感的なログレベル制御、ケースインセンシティブな設定、モジュール単位でのきめ細かいログ制御、標準エラー出力への自動出力、色分け表示対応など、Rust開発に最適化された機能を提供。Rust Cookbookで最初に紹介されるクレートとして新規Rust開発者の学習起点となり、ライブラリ開発ではlogクレートとの組み合わせが推奨される標準的な慣例を確立しています。

主な特徴

  • 環境変数制御: RUST_LOG環境変数による簡単なログ制御
  • 最多ダウンロード: Rustロガー実装で最高のダウンロード数を記録
  • モジュール対応: クレート・モジュール単位でのきめ細かい制御
  • テストサポート: テスト環境での重複初期化対応
  • カスタマイズ: Builderパターンによる詳細設定
  • 標準準拠: Rustエコシステムの標準的な実装

メリット・デメリット

メリット

  • Rustエコシステムでの圧倒的な普及率と安定性
  • 環境変数による直感的で設定不要なログ制御
  • 初学者にとって理解しやすいシンプルな設計
  • logクレートとの完璧な統合と標準的な使用パターン
  • 豊富なコミュニティサポートと学習リソース
  • テスト環境での安全な重複初期化処理

デメリット

  • 高度なログ機能(構造化ログ、分散トレーシング)は提供せず
  • 大規模アプリケーションでは機能が不足する場合がある
  • ファイル出力やローテーション機能が基本的なレベル
  • マイクロサービス環境では他の選択肢が優先される
  • カスタマイズ性が限定的で拡張機能が少ない
  • 非同期処理に特化した最適化は提供されない

参考ページ

書き方の例

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

# 実行可能ファイル用の依存関係追加
cargo add log env_logger

# テスト用途の場合
cargo add log
cargo add --dev env_logger

# Cargo.tomlでの記述例
echo '[dependencies]' >> Cargo.toml
echo 'log = "0.4"' >> Cargo.toml
echo 'env_logger = "0.10"' >> Cargo.toml

# バージョン確認
cargo tree | grep env_logger

基本的なログ出力

use log::{info, warn, error, debug, trace};

fn main() {
    // env_logger初期化(プログラム開始時に一度だけ)
    env_logger::init();
    
    // 基本的なログ出力
    error!("エラーメッセージ");
    warn!("警告メッセージ");
    info!("情報メッセージ");
    debug!("デバッグメッセージ");
    trace!("詳細トレースメッセージ");
    
    // データの出力
    let user_id = 123;
    let user_name = "田中太郎";
    info!("ユーザー処理: ID={}, 名前={}", user_id, user_name);
    
    // 構造体の出力
    #[derive(Debug)]
    struct User {
        id: u32,
        name: String,
        email: String,
    }
    
    let user = User {
        id: 1,
        name: "佐藤花子".to_string(),
        email: "[email protected]".to_string(),
    };
    
    debug!("ユーザー詳細: {:?}", user);
    
    // 条件付きログ
    let is_debug_mode = true;
    if is_debug_mode {
        debug!("デバッグモードが有効です");
    }
    
    info!("アプリケーション開始完了");
}

// 実行例
// RUST_LOG=info cargo run
// RUST_LOG=debug cargo run

環境変数によるログ制御

# 基本的なログレベル設定
RUST_LOG=error cargo run    # エラーのみ
RUST_LOG=warn cargo run     # 警告以上
RUST_LOG=info cargo run     # 情報以上
RUST_LOG=debug cargo run    # デバッグ以上
RUST_LOG=trace cargo run    # 全てのログ

# ケースインセンシティブ対応
RUST_LOG=INFO cargo run     # info と同じ
RUST_LOG=Debug cargo run    # debug と同じ

# 複数モジュールの制御
RUST_LOG=myapp=info,tokio=warn cargo run

# クレート単位の制御
RUST_LOG=myapp=debug cargo run

# モジュール階層の制御
RUST_LOG=myapp::database=trace,myapp::auth=info cargo run

# 除外パターン
RUST_LOG=debug,tokio=off cargo run

# 全て有効
RUST_LOG=trace cargo run

# 特定クレートのみ
RUST_LOG=myapp cargo run  # デフォルトレベル

モジュール別ログ管理

// main.rs
use log::{info, error};

mod database;
mod auth;
mod api;

fn main() {
    env_logger::init();
    
    info!("アプリケーション初期化開始");
    
    // 各モジュールの関数実行
    database::connect();
    auth::login("[email protected]", "password");
    api::start_server();
    
    info!("アプリケーション起動完了");
}

// database.rs
use log::{info, debug, error};

pub fn connect() {
    debug!("データベース接続開始");
    
    match establish_connection() {
        Ok(_) => {
            info!("データベース接続成功");
        }
        Err(e) => {
            error!("データベース接続失敗: {}", e);
        }
    }
}

fn establish_connection() -> Result<(), String> {
    // 接続処理のシミュレーション
    debug!("接続パラメータ確認中...");
    std::thread::sleep(std::time::Duration::from_millis(100));
    
    // 成功ケース
    Ok(())
}

pub fn execute_query(sql: &str) -> Result<Vec<String>, String> {
    debug!("SQL実行: {}", sql);
    
    // クエリ実行のシミュレーション
    let results = vec!["結果1".to_string(), "結果2".to_string()];
    debug!("クエリ結果: {}件", results.len());
    
    Ok(results)
}

// auth.rs
use log::{info, warn, error, debug};

pub fn login(email: &str, password: &str) -> Result<String, String> {
    info!("ログイン試行: {}", email);
    
    // パスワード検証(実際の実装では暗号化されたパスワードを使用)
    if validate_credentials(email, password) {
        let session_token = generate_session_token();
        info!("ログイン成功: {}", email);
        debug!("セッショントークン生成: {}", &session_token[0..8]);
        Ok(session_token)
    } else {
        warn!("ログイン失敗: 認証情報が無効 - {}", email);
        Err("Invalid credentials".to_string())
    }
}

fn validate_credentials(email: &str, password: &str) -> bool {
    debug!("認証情報検証中: {}", email);
    // 実際の検証ロジック
    email.contains("@") && password.len() >= 8
}

fn generate_session_token() -> String {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};
    
    let mut hasher = DefaultHasher::new();
    std::time::SystemTime::now().hash(&mut hasher);
    format!("session_{:x}", hasher.finish())
}

// api.rs
use log::{info, debug, error};

pub fn start_server() {
    info!("APIサーバー開始");
    
    let port = 8080;
    debug!("ポート {}で待機中", port);
    
    // サーバー起動のシミュレーション
    match bind_server(port) {
        Ok(_) => {
            info!("APIサーバー起動完了: http://localhost:{}", port);
        }
        Err(e) => {
            error!("サーバー起動失敗: {}", e);
        }
    }
}

fn bind_server(port: u16) -> Result<(), String> {
    debug!("ポート {}のバインドを試行", port);
    // バインド処理のシミュレーション
    Ok(())
}

// 実行例と出力制御
// RUST_LOG=myapp=info cargo run  # 全モジュールのinfoレベル以上
// RUST_LOG=myapp::database=debug cargo run  # databaseモジュールのデバッグ
// RUST_LOG=myapp::auth=trace,myapp::api=info cargo run  # 個別制御

テスト環境での設定

// lib.rs または main.rs
use log::info;

pub fn add_one(num: i32) -> i32 {
    info!("add_one called with {}", num);
    num + 1
}

pub fn calculate_sum(numbers: &[i32]) -> i32 {
    info!("calculating sum of {} numbers", numbers.len());
    let sum = numbers.iter().sum();
    info!("sum calculated: {}", sum);
    sum
}

#[cfg(test)]
mod tests {
    use super::*;
    use log::info;
    
    // テスト用初期化関数
    fn init() {
        // try_init() を使用して重複初期化を防ぐ
        let _ = env_logger::builder().is_test(true).try_init();
    }
    
    #[test]
    fn test_add_one() {
        init();
        
        info!("Starting add_one test");
        let result = add_one(2);
        assert_eq!(result, 3);
        info!("add_one test completed successfully");
    }
    
    #[test]
    fn test_add_one_negative() {
        init();
        
        info!("Starting negative number test");
        let result = add_one(-5);
        assert_eq!(result, -4);
        info!("negative number test completed");
    }
    
    #[test]
    fn test_calculate_sum() {
        init();
        
        info!("Starting sum calculation test");
        let numbers = vec![1, 2, 3, 4, 5];
        let result = calculate_sum(&numbers);
        assert_eq!(result, 15);
        info!("sum calculation test completed");
    }
    
    #[test]
    fn test_empty_sum() {
        init();
        
        info!("Testing empty array sum");
        let numbers = vec![];
        let result = calculate_sum(&numbers);
        assert_eq!(result, 0);
        info!("empty array test completed");
    }
}

// テスト実行例
// RUST_LOG=info cargo test
// RUST_LOG=mylib=debug cargo test test_add_one
// RUST_LOG=trace cargo test -- --nocapture

Builder パターンによるカスタマイズ

use env_logger::{Builder, Target, WriteStyle};
use log::LevelFilter;
use std::io::Write;

fn main() {
    // カスタムビルダーの作成
    let mut builder = Builder::from_default_env();
    
    // 標準出力への出力設定
    builder.target(Target::Stdout);
    
    // ログレベルの設定
    builder.filter_level(LevelFilter::Info);
    
    // カスタムフォーマットの設定
    builder.format(|buf, record| {
        writeln!(
            buf,
            "{} [{}] - {}",
            chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
            record.level(),
            record.args()
        )
    });
    
    // 初期化
    builder.init();
    
    // テスト用ログ出力
    log::error!("エラーメッセージ");
    log::warn!("警告メッセージ");
    log::info!("情報メッセージ");
    log::debug!("デバッグメッセージ"); // filter_level(Info)により表示されない
}

// より詳細なカスタマイズ例
fn setup_detailed_logger() {
    use std::env;
    
    let mut builder = Builder::new();
    
    // 環境変数の処理
    if let Ok(rust_log) = env::var("RUST_LOG") {
        builder.parse_filters(&rust_log);
    } else {
        // デフォルトのログレベル設定
        builder.filter_level(LevelFilter::Info);
    }
    
    // 色付き出力の設定
    builder.write_style(WriteStyle::Always);
    
    // タイムスタンプ付きフォーマット
    builder.format(|buf, record| {
        let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
        writeln!(
            buf,
            "[{}] {} [{}:{}] {}",
            timestamp,
            record.level(),
            record.file().unwrap_or("unknown"),
            record.line().unwrap_or(0),
            record.args()
        )
    });
    
    // モジュール別フィルタ
    builder
        .filter_module("reqwest", LevelFilter::Warn)
        .filter_module("hyper", LevelFilter::Error)
        .filter_module("myapp::database", LevelFilter::Debug);
    
    builder.init();
}

// JSON形式でのログ出力例
fn setup_json_logger() {
    use serde_json::json;
    
    let mut builder = Builder::new();
    
    builder.format(|buf, record| {
        let log_entry = json!({
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "level": record.level().to_string(),
            "target": record.target(),
            "module": record.module_path(),
            "file": record.file(),
            "line": record.line(),
            "message": record.args().to_string()
        });
        
        writeln!(buf, "{}", log_entry)
    });
    
    if let Ok(rust_log) = std::env::var("RUST_LOG") {
        builder.parse_filters(&rust_log);
    }
    
    builder.init();
}

// 使用例
fn custom_logger_example() {
    setup_detailed_logger();
    
    log::info!("カスタムロガーでの情報ログ");
    log::error!("カスタムロガーでのエラーログ");
    log::debug!("カスタムロガーでのデバッグログ");
}

実践的なアプリケーション例

// Cargo.toml
/*
[dependencies]
log = "0.4"
env_logger = "0.10"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
*/

use log::{info, warn, error, debug};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(Debug, Clone)]
struct User {
    id: u32,
    name: String,
    email: String,
}

struct UserService {
    users: Arc<Mutex<HashMap<u32, User>>>,
}

impl UserService {
    fn new() -> Self {
        info!("UserService初期化");
        Self {
            users: Arc::new(Mutex::new(HashMap::new())),
        }
    }
    
    async fn create_user(&self, name: String, email: String) -> Result<User, String> {
        info!("ユーザー作成開始: name={}, email={}", name, email);
        
        // 入力検証
        if name.is_empty() {
            warn!("ユーザー作成失敗: 名前が空です");
            return Err("Name cannot be empty".to_string());
        }
        
        if !email.contains('@') {
            warn!("ユーザー作成失敗: 無効なメールアドレス - {}", email);
            return Err("Invalid email address".to_string());
        }
        
        let mut users = self.users.lock().await;
        
        // 重複チェック
        for (_, existing_user) in users.iter() {
            if existing_user.email == email {
                warn!("ユーザー作成失敗: メールアドレスが重複 - {}", email);
                return Err("Email already exists".to_string());
            }
        }
        
        // 新しいユーザーID生成
        let new_id = users.len() as u32 + 1;
        let user = User {
            id: new_id,
            name: name.clone(),
            email: email.clone(),
        };
        
        users.insert(new_id, user.clone());
        info!("ユーザー作成成功: ID={}, name={}", new_id, name);
        debug!("現在のユーザー数: {}", users.len());
        
        Ok(user)
    }
    
    async fn get_user(&self, id: u32) -> Option<User> {
        debug!("ユーザー取得: ID={}", id);
        let users = self.users.lock().await;
        
        match users.get(&id) {
            Some(user) => {
                debug!("ユーザー見つかりました: {}", user.name);
                Some(user.clone())
            }
            None => {
                warn!("ユーザーが見つかりません: ID={}", id);
                None
            }
        }
    }
    
    async fn list_users(&self) -> Vec<User> {
        debug!("全ユーザー一覧取得");
        let users = self.users.lock().await;
        let user_list: Vec<User> = users.values().cloned().collect();
        info!("ユーザー一覧取得完了: {}人", user_list.len());
        user_list
    }
    
    async fn delete_user(&self, id: u32) -> Result<(), String> {
        info!("ユーザー削除開始: ID={}", id);
        let mut users = self.users.lock().await;
        
        match users.remove(&id) {
            Some(user) => {
                info!("ユーザー削除成功: ID={}, name={}", id, user.name);
                debug!("残りユーザー数: {}", users.len());
                Ok(())
            }
            None => {
                warn!("削除対象ユーザーが見つかりません: ID={}", id);
                Err("User not found".to_string())
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ログ初期化
    env_logger::init();
    
    info!("アプリケーション開始");
    
    let user_service = UserService::new();
    
    // ユーザー作成テスト
    let users_to_create = vec![
        ("田中太郎", "[email protected]"),
        ("佐藤花子", "[email protected]"),
        ("鈴木一郎", "[email protected]"),
    ];
    
    for (name, email) in users_to_create {
        match user_service.create_user(name.to_string(), email.to_string()).await {
            Ok(user) => {
                info!("ユーザー作成成功: {:?}", user);
            }
            Err(e) => {
                error!("ユーザー作成エラー: {}", e);
            }
        }
    }
    
    // 重複作成テスト
    match user_service.create_user("重複太郎".to_string(), "[email protected]".to_string()).await {
        Ok(_) => {
            error!("重複チェックが失敗しました");
        }
        Err(e) => {
            info!("重複チェック正常動作: {}", e);
        }
    }
    
    // ユーザー一覧取得
    let all_users = user_service.list_users().await;
    for user in all_users {
        debug!("登録ユーザー: {:?}", user);
    }
    
    // 個別ユーザー取得
    if let Some(user) = user_service.get_user(1).await {
        info!("ユーザー取得成功: {:?}", user);
    }
    
    // 存在しないユーザー取得
    if user_service.get_user(999).await.is_none() {
        debug!("存在しないユーザーの取得テスト成功");
    }
    
    // ユーザー削除
    match user_service.delete_user(2).await {
        Ok(_) => {
            info!("ユーザー削除成功");
        }
        Err(e) => {
            error!("ユーザー削除エラー: {}", e);
        }
    }
    
    // 最終状態確認
    let final_users = user_service.list_users().await;
    info!("最終ユーザー数: {}", final_users.len());
    
    info!("アプリケーション終了");
    Ok(())
}

// 実行例とログ制御
// RUST_LOG=info cargo run
// RUST_LOG=debug cargo run
// RUST_LOG=myapp=trace cargo run
// RUST_LOG=info,tokio=warn cargo run

プロダクション環境での設定

use env_logger::{Builder, Env};
use log::LevelFilter;
use std::env;
use std::fs::OpenOptions;
use std::io::Write;

fn setup_production_logger() -> Result<(), Box<dyn std::error::Error>> {
    let mut builder = Builder::new();
    
    // 環境に応じたデフォルトレベル設定
    let default_level = match env::var("ENVIRONMENT").as_deref() {
        Ok("production") => "warn",
        Ok("staging") => "info", 
        Ok("development") => "debug",
        _ => "info",
    };
    
    // 環境変数からの設定(デフォルト値付き)
    let env = Env::default()
        .filter_or("RUST_LOG", default_level)
        .write_style_or("RUST_LOG_STYLE", "auto");
    
    builder.from_env(env);
    
    // プロダクション環境でのフォーマット設定
    if env::var("ENVIRONMENT").as_deref() == Ok("production") {
        // JSON形式のログ出力(ログ集約システム用)
        builder.format(|buf, record| {
            let log_entry = serde_json::json!({
                "timestamp": chrono::Utc::now().to_rfc3339(),
                "level": record.level().to_string(),
                "target": record.target(),
                "message": record.args().to_string(),
                "module": record.module_path(),
                "file": record.file(),
                "line": record.line()
            });
            writeln!(buf, "{}", log_entry)
        });
    } else {
        // 開発環境での読みやすいフォーマット
        builder.format(|buf, record| {
            writeln!(
                buf,
                "{} [{}] {} - {}",
                chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
                record.level(),
                record.target(),
                record.args()
            )
        });
    }
    
    builder.init();
    Ok(())
}

// エラーハンドリングとリカバリー
fn safe_logger_init() {
    match setup_production_logger() {
        Ok(_) => {
            log::info!("ロガー初期化成功");
        }
        Err(e) => {
            eprintln!("ロガー初期化失敗: {} - フォールバック初期化", e);
            // フォールバック: 基本的な初期化
            env_logger::init();
            log::warn!("フォールバックロガーで初期化されました");
        }
    }
}

// 設定検証とヘルスチェック
fn validate_logging_config() -> Result<(), String> {
    // 必要な環境変数の確認
    let required_vars = ["RUST_LOG"];
    for var in required_vars.iter() {
        if env::var(var).is_err() {
            return Err(format!("Required environment variable {} is not set", var));
        }
    }
    
    // ログレベルの妥当性チェック
    if let Ok(log_level) = env::var("RUST_LOG") {
        let valid_levels = ["error", "warn", "info", "debug", "trace"];
        if !valid_levels.iter().any(|&level| log_level.contains(level)) {
            return Err(format!("Invalid log level in RUST_LOG: {}", log_level));
        }
    }
    
    Ok(())
}

fn main() {
    // 設定検証
    if let Err(e) = validate_logging_config() {
        eprintln!("ログ設定エラー: {}", e);
        std::process::exit(1);
    }
    
    // 安全な初期化
    safe_logger_init();
    
    log::info!("プロダクション環境でのアプリケーション開始");
    
    // アプリケーションロジック
    log::debug!("デバッグ情報(プロダクションでは表示されない)");
    log::info!("重要な業務ログ");
    log::warn!("注意が必要な状況");
    log::error!("エラーが発生しました");
    
    log::info!("アプリケーション正常終了");
}

// 実行例
// ENVIRONMENT=production RUST_LOG=warn cargo run
// ENVIRONMENT=development RUST_LOG=debug cargo run
// RUST_LOG=myapp=info,tokio=error ENVIRONMENT=staging cargo run