java-recipes

ホーム ロギング・例外処理 › L-01

L-01: java.util.logging 基本

Java 標準のロギング API である java.util.logging の使い方を解説します。 ログレベルの使い分け・ハンドラーの設定・例外のログ記録方法と、e.printStackTrace() を使ってはいけない理由を学びましょう。

いつ使うか

  • バッチ処理の開始・終了・処理件数を記録するとき
  • 例外が発生したときにスタックトレースをファイルに保存するとき
  • 外部ライブラリを使えない組み込みシステムやシンプルなアプリケーションでログを出力するとき
  • デバッグ時だけ詳細ログを出力し、本番では INFO 以上だけ出力するよう切り替えるとき

ログレベル一覧(重要度の高い順)

レベル数値用途
SEVERE1000システム障害・即時対応が必要なエラー
WARNING900想定外の状況だが処理は継続できる
INFO800正常な業務ログ(処理開始・終了など)
FINE500デバッグ情報(通常は出力しない)
FINER400より詳細なデバッグ情報
FINEST300最も詳細なトレース情報

サンプルコード

LoggingSample.java
import java.util.logging.*;
import java.io.*;

public class LoggingSample {

    // クラス専用のロガーを取得(クラス名をロガー名に使うのがベストプラクティス)
    private static final Logger logger = Logger.getLogger(LoggingSample.class.getName());

    // コンソールハンドラーを設定
    public static void setupLogger() {
        // デフォルトの ConsoleHandler を除去して独自設定する
        Logger rootLogger = Logger.getLogger("");
        for (Handler handler : rootLogger.getHandlers()) {
            rootLogger.removeHandler(handler);
        }

        // コンソールハンドラー(INFO 以上を出力)
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setLevel(Level.INFO);
        consoleHandler.setFormatter(new SimpleFormatter());

        logger.addHandler(consoleHandler);
        logger.setLevel(Level.ALL); // ロガー自体は ALL レベル以上を受け付ける
        logger.setUseParentHandlers(false); // 親ロガーへの伝播を停止
    }

    // 各ログレベルの使い方
    public static void demonstrateLevels() {
        logger.severe("SEVERE: システム障害・即時対応が必要なエラー");
        logger.warning("WARNING: 想定外の状況だが処理は継続できる");
        logger.info("INFO: 正常な業務ログ(処理開始・終了など)");
        logger.fine("FINE: デバッグ情報(通常は出力しない)");
        logger.finer("FINER: より詳細なデバッグ情報");
        logger.finest("FINEST: 最も詳細なトレース情報");
    }

    // 例外をログに含める正しい方法
    public static void logWithException() {
        try {
            int result = 10 / 0; // 意図的な例外
            System.out.println(result); // 到達しない
        } catch (ArithmeticException e) {
            // NG: e.printStackTrace() は使わない
            // e.printStackTrace();

            // OK: logger でスタックトレースごと記録
            logger.log(Level.SEVERE, "計算処理でエラーが発生しました", e);
        }
    }

    // メッセージの遅延評価(パフォーマンス最適化)
    public static void lazyLogging() {
        // 文字列結合は Logger.isLoggable() で事前チェック
        if (logger.isLoggable(Level.FINE)) {
            // FINE レベルが有効なときだけ文字列結合を実行
            logger.fine("詳細情報: " + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        setupLogger();

        logger.info("アプリケーション起動");
        demonstrateLevels();
        logWithException();
        lazyLogging();
        logger.info("アプリケーション終了");
    }
}

java.util.logging はデフォルトで java.util.logging.config.file システムプロパティで設定ファイルを指定できます。コード内で直接設定する場合は、ルートロガーのデフォルトハンドラーを一度削除してから追加すると重複出力を防げます。

よくあるミス・注意点

⚠️ e.printStackTrace() は使用禁止

e.printStackTrace() には3つの問題があります。①標準エラー出力(System.err)にしか出力されないためログファイルに記録されない、 ②ログレベルを設定できないため重要度が分からない、③ログフォーマットが統一されず検索・集計がしにくい。 必ず logger.log(Level.SEVERE, "メッセージ", e) を使ってください。

⚠️ FINE/FINER/FINEST ログの文字列結合は isLoggable() で事前チェック

logger.fine("値: " + obj.heavyCalc()) のように書くと、FINE レベルが無効でもメソッドが呼ばれて文字列結合が実行されます。 ホットパスでは if (logger.isLoggable(Level.FINE)) で事前チェックしてください。

⚠️ ロガー名はクラス名にする

Logger.getLogger("myLogger") のように任意の文字列でもロガーは取得できますが、ベストプラクティスは Logger.getLogger(MyClass.class.getName()) です。クラス名をロガー名にすることで、ログ出力元が一目で分かり、クラス階層に合わせてログレベルを細かく設定できます。

⚠️ 実務では SLF4J + Logback/Log4j2 が一般的

java.util.logging は外部ライブラリが使えない環境向けです。Spring Boot 等の実務プロジェクトでは SLF4J をロギングファサードとして使い、 バックエンドに Logback や Log4j2 を採用するのが一般的です。 API を SLF4J に統一することで、ロギングライブラリを後から変更しても呼び出し元のコードを修正せずに済みます。

テストする観点

  • 各ログレベルのメソッドを呼び出したとき、設定したレベル以上のみ出力されること
  • 例外オブジェクトを渡した場合、スタックトレースがログレコードに含まれること
  • ハンドラーを設定していない状態でもログ呼び出しで例外が発生しないこと(境界値)
  • isLoggable() が設定レベルより低いレベルには false を返すこと
  • ロガー名が正しく設定されていること(クラス名と一致すること)

GitHub でソースコードを見る →