タイトル: org.apache.log4j.Logger のログ出力で printStackTrace() のエラー内容を出力する方法
SEOタイトル: log4j で printStackTrace の内容をログ出力する方法(スタックトレース完全保存)
| この記事の要点 |
|
結論: 例外は logger の第 2 引数で渡す
結論から書くと、例外オブジェクトを Logger の第 2 引数に渡すだけで、Log4j が自動的に printStackTrace() 相当のスタックトレースをログに書き出します。
import org.apache.log4j.Logger;
public class UserService {
private static final Logger logger = Logger.getLogger(UserService.class);
public void register(User u) {
try {
repository.save(u);
} catch (Exception e) {
// ★ これだけで OK。第 2 引数に例外を渡す
logger.error("ユーザー登録に失敗しました userId=" + u.getId(), e);
throw e;
}
}
}
出力例:
2026-05-17 10:23:45 ERROR UserService - ユーザー登録に失敗しました userId=123
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '123' for key 'PRIMARY'
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:109)
at com.example.UserRepository.save(UserRepository.java:42)
at com.example.UserService.register(UserService.java:18)
... 35 more
なぜ printStackTrace() ではダメなのか
e.printStackTrace() はデバッグ用の便利メソッドですが、本番運用では致命的な欠陥があります:
| 項目 | e.printStackTrace() | logger.error(msg, e) |
|---|---|---|
| 出力先 | 標準エラー (System.err) 固定 | log4j.properties で定義した Appender |
| ログレベル | 制御不能 | ERROR / WARN / INFO を選択可 |
| タイムスタンプ | なし | 自動付与 |
| クラス名・スレッド名 | なし | %c / %t で出力可 |
| ファイル分割・ローテート | 不可 | RollingFileAppender 等で可 |
| 本番サーバーでの追跡 | catalina.out に埋もれる | 専用ログファイルに集約 |
とくに Tomcat や WebLogic などのアプリサーバでは、System.err は catalina.out や stderr.log に流れますが、業務ログと分離されておらず原因追跡が困難になります。
NG パターン: 例外を toString() してしまう
よくある間違いが次のコードです:
// ❌ ダメな書き方
logger.error("エラー: " + e); // toString() しか出ない
logger.error("エラー: " + e.getMessage()); // メッセージだけ、スタックトレースなし
logger.error(e.toString()); // 同上
// ❌ さらにダメ
try {
...
} catch (Exception e) {
e.printStackTrace(); // 標準エラーに出るだけで Log4j に流れない
}
上記のコードは例外発生箇所が特定できず、運用フェーズで詰む典型例です。必ず第 2 引数で例外を渡してください。
スタックトレースを文字列で取得したい場合
「DB のエラー履歴テーブルに保存したい」「メール本文に含めたい」など、文字列としてスタックトレースを取り出したい場合は次の方法を使います。
方法 1: Apache Commons Lang3
import org.apache.commons.lang3.exception.ExceptionUtils;
try {
...
} catch (Exception e) {
String trace = ExceptionUtils.getStackTrace(e);
logger.error("エラー詳細:\n" + trace);
// DB に保存
errorLogRepository.save(new ErrorLog(e.getMessage(), trace));
}
Maven 依存:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
方法 2: 標準ライブラリの StringWriter
commons-lang3 を入れたくない場合は標準 API でも可能:
import java.io.PrintWriter;
import java.io.StringWriter;
public static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw); // ★ PrintWriter を渡せば文字列化される
return sw.toString();
}
// 使い方
catch (Exception e) {
String trace = getStackTrace(e);
logger.error("エラー:\n" + trace);
}
Log4j の設定例 (log4j.properties)
スタックトレースを綺麗に出すための定番設定:
# ルートロガー: INFO 以上をファイルとコンソールへ
log4j.rootLogger=INFO, FILE, CONSOLE
# コンソール
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m%n
# ファイル (日次ローテート)
log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender
log4j.appender.FILE.File=/var/log/app/application.log
log4j.appender.FILE.DatePattern='.'yyyy-MM-dd
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %m%n
%m%n の %m がメッセージで、第 2 引数で渡した例外のスタックトレースも自動的に %m の後ろに付加されます。
Log4j 1.x はサポート終了。Log4j 2 / SLF4J への移行
Log4j 1.x は 2015 年 8 月にサポート終了しています。新規開発では Log4j 2 または SLF4J + Logback を推奨。
// Log4j 2 (パッケージは org.apache.logging.log4j)
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
private static final Logger logger = LogManager.getLogger(UserService.class);
logger.error("失敗 userId={}", userId, e); // ★ プレースホルダ + 末尾に例外
// SLF4J + Logback (Spring Boot 標準)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
logger.error("失敗 userId={}", userId, e); // ★ 末尾の Throwable は自動でスタックトレース化
SLF4J / Log4j 2 では {} プレースホルダの最後の引数が Throwable なら自動的にスタックトレース出力される仕組みになっています。文字列連結 + より高速かつ安全です。
Log4j 2 で JSON 構造化ログ
ELK / Datadog / CloudWatch Logs で扱いやすい JSON 出力:
<!-- log4j2.xml -->
<Configuration>
<Appenders>
<Console name="JsonConsole" target="SYSTEM_OUT">
<JsonLayout compact="true" eventEol="true" stacktraceAsString="true"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="JsonConsole"/>
</Root>
</Loggers>
</Configuration>
出力例 (1 行 = 1 JSON):
{"timestamp":"2026-05-17T10:23:45.123Z","level":"ERROR","logger":"UserService","message":"失敗 userId=123","thrown":{"name":"SQLIntegrityConstraintViolationException","message":"Duplicate entry","extendedStackTrace":"..."}}
FAQ
Q: catch せず throws する場合は?
A: 上位の Filter / ControllerAdvice / try-catch 側で logger.error(msg, e) すれば OK。低レベルで catch して再 throw する場合、catch のたびにログを出すと重複するので注意。
Q: スタックトレースが「... 35 more」で省略される
A: Java の仕様で、同じフレームは省略表示されます。%ex{full} や Throwable#getStackTrace() で完全取得可能。実運用ではデフォルトの省略で問題ないことが多い。
Q: 個人情報が含まれる例外メッセージをマスクしたい
A: Log4j 2 の RegexFilter や独自 Layout で正規表現マスクを実装。パスワードを例外メッセージに入れないのが原則。
Q: log4j-over-slf4j を使っているプロジェクトは?
A: コード上は org.apache.log4j.Logger のまま動きますが、実体は SLF4J 経由で Logback に流れます。API は同じなので同じ書き方で OK。
関連項目
- Log4Shell (CVE-2021-44228) — Log4j 2.0〜2.14.1 の RCE 脆弱性。Log4j 2.17.1+ への更新必須
- SLF4J — Java ロギングのファサード。実装を切り替え可能
- Logback — SLF4J 純正実装。Spring Boot 標準
- Sentry / Rollbar — 例外を外部サービスに自動収集する選択肢