概要
バッチ処理は対話的なフィードバックがないため、ログが唯一の動作確認手段になります。処理がどこまで進んだか、何件処理したか、どのレコードでエラーが発生したかを追跡できなければ、障害発生時の原因究明に時間がかかります。java.util.logging は Java 標準に含まれており、外部ライブラリなしでファイル出力やフォーマットのカスタマイズが可能です。この記事では、バッチ処理に特化したログ設計として、BatchLogger クラスの実装、ログレベルの使い分け基準、ファイルローテーション、処理件数や経過時間の記録パターンを整理します。
使いどころ
夜間バッチの実行結果を翌朝にログファイルで確認する
CSVインポートバッチで処理件数・スキップ件数・エラー件数を記録する
月次集計バッチの各ステップの経過時間を記録しボトルネックを特定する
コード例
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("=== バッチ終了 ===");
}
}Version Coverage
System.Logger が追加され、java.util.logging をバックエンドに使いつつファサードを切り替えられる。
// 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
注意点
FileHandler のパスは絶対パスにするか相対パスを明示すること。権限エラーで出力されないケースが多い
デフォルトの SimpleFormatter は XML 形式になることがある。明示的にフォーマットを指定すること
ファイルローテーションの limit と count を設定しないとログファイルが肥大化する
Level.FINE 以下はデフォルトで出力されない。Handler と Logger の両方にレベルを設定する
FAQ
SEVERE は続行不能、WARNING はリトライで回復、INFO は正常経過、FINE はデバッグ情報です。
FileHandler のコンストラクタで limit(バイト数上限)と count(世代数)を指定します。
開始時に総件数、一定件数ごとに進捗、完了時に成功・失敗・スキップ各件数と経過時間を記録します。