概要

バッチ処理は対話的なフィードバックがないため、ログが唯一の動作確認手段になります。処理がどこまで進んだか、何件処理したか、どのレコードでエラーが発生したかを追跡できなければ、障害発生時の原因究明に時間がかかります。java.util.logging は Java 標準に含まれており、外部ライブラリなしでファイル出力やフォーマットのカスタマイズが可能です。この記事では、バッチ処理に特化したログ設計として、BatchLogger クラスの実装、ログレベルの使い分け基準、ファイルローテーション、処理件数や経過時間の記録パターンを整理します。

使いどころ

夜間バッチの実行結果を翌朝にログファイルで確認する

CSVインポートバッチで処理件数・スキップ件数・エラー件数を記録する

月次集計バッチの各ステップの経過時間を記録しボトルネックを特定する

コード例

BatchLogger — バッチ向けログ出力クラス
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class BatchLogger {
    private final Logger logger;

    public BatchLogger(String name, String logFilePath) throws IOException {
        this.logger = Logger.getLogger(name);
        this.logger.setUseParentHandlers(false);
        FileHandler fh = new FileHandler(logFilePath, 5_000_000, 3, true);
        fh.setFormatter(new SimpleFormatter() {
            @Override public String format(LogRecord r) {
                return String.format("[%1$tF %1$tT.%1$tL] [%2$-7s] %3$s%n",
                    r.getMillis(), r.getLevel(), r.getMessage());
            }
        });
        fh.setLevel(Level.ALL);
        logger.addHandler(fh);
        logger.setLevel(Level.ALL);
    }

    public void info(String msg) { logger.info(msg); }
    public void warn(String msg) { logger.warning(msg); }
    public void error(String msg, Throwable t) { logger.log(Level.SEVERE, msg, t); }

    public void logProgress(int processed, int total, long startMillis) {
        long elapsed = System.currentTimeMillis() - startMillis;
        double pct = total > 0 ? (processed * 100.0 / total) : 0;
        logger.info(String.format("進捗: %d/%d (%.1f%%) — %dms", processed, total, pct, elapsed));
    }

    public void logSummary(int success, int error, int skip, long startMillis) {
        long elapsed = System.currentTimeMillis() - startMillis;
        logger.info(String.format("完了: 成功=%d, エラー=%d, スキップ=%d, %dms",
            success, error, skip, elapsed));
    }

    public static void main(String[] args) throws IOException {
        BatchLogger log = new BatchLogger("CsvBatch", "./logs/batch.log");
        long start = System.currentTimeMillis();
        log.info("=== バッチ開始 ===");
        for (int i = 1; i <= 5000; i++) {
            if (i % 1000 == 0) log.logProgress(i, 5000, start);
        }
        log.logSummary(4999, 1, 0, start);
        log.info("=== バッチ終了 ===");
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

System.Logger が追加され、java.util.logging をバックエンドに使いつつファサードを切り替えられる。

Java 17
// Java 17: var で簡潔に
var formatter = new SimpleFormatter() {
    @Override
    public String format(java.util.logging.LogRecord record) {
        return String.format("[%1$tF %1$tT] %2$s: %3$s%n",
            record.getMillis(), record.getLevel(), record.getMessage());
    }
};

Library Comparison

java.util.logging(標準API)外部ライブラリなしで軽量バッチのログを出力したい場合。設定が冗長で SLF4J + Logback よりパフォーマンスが劣る。
SLF4J + Logbackプロジェクト全体で統一したロギングファサードが必要な場合。3JAR追加。logback.xml の学習コストがある。
Log4j 2非同期ロギングが必要な高スループットバッチの場合。過去の脆弱性によりバージョン管理に注意が必要。

注意点

FileHandler のパスは絶対パスにするか相対パスを明示すること。権限エラーで出力されないケースが多い

デフォルトの SimpleFormatter は XML 形式になることがある。明示的にフォーマットを指定すること

ファイルローテーションの limit と count を設定しないとログファイルが肥大化する

Level.FINE 以下はデフォルトで出力されない。Handler と Logger の両方にレベルを設定する

FAQ

ログレベルの使い分け基準は。

SEVERE は続行不能、WARNING はリトライで回復、INFO は正常経過、FINE はデバッグ情報です。

ファイルローテーションの設定方法は。

FileHandler のコンストラクタで limit(バイト数上限)と count(世代数)を指定します。

処理件数はどのタイミングで記録しますか。

開始時に総件数、一定件数ごとに進捗、完了時に成功・失敗・スキップ各件数と経過時間を記録します。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます