18.

例外を文字列で出力する方法(PHP / Java / Python のスタックトレース文字列化)

編集
この記事の要点
  • PHP / Java / Python で例外(Exception / Throwable)をログ用に文字列化する方法のまとめ
  • PHP: $e->getMessage() / $e->getTraceAsString() / (string)$e
  • Java: ExceptionUtils.getStackTrace(e) (commons-lang3) / Throwables.getStackTraceAsString(e) (Guava) / StringWriter + PrintWriter
  • Python: traceback.format_exc() / traceback.format_exception(type, value, tb)
  • ロガー使用時は例外オブジェクトを直接渡すのがベストプラクティス(logger.error("msg", e)

共通の考え方

例外を文字列化する目的は主に 3 つ:

  • ログファイルに残す: 後から原因調査するため
  • エラー通知に含める: Slack / メール / Sentry へ送信
  • API レスポンスで返す(開発時のみ): 本番では絶対にしない

どの言語でも、含めるべき情報は メッセージ + スタックトレース + 原因例外(cause chain)の 3 点セットです。

PHP の例外文字列化

getMessage();
    // → something broke

    // 2. 場所
    echo $e->getFile() . ':' . $e->getLine();
    // → /var/www/index.php:5

    // 3. スタックトレース(文字列)
    echo $e->getTraceAsString();
    /*
    #0 /var/www/index.php(10): foo()
    #1 {main}
    */

    // 4. すべて入りの最も標準的な形(__toString)
    echo (string) $e;
    /*
    RuntimeException: something broke in /var/www/index.php:5
    Stack trace:
    #0 /var/www/index.php(10): foo()
    #1 {main}
    */

    // 5. 配列のスタックトレース(要素ごとにアクセス可能)
    $trace = $e->getTrace();
    foreach ($trace as $i => $frame) {
        printf("#%d %s:%d %s%s%s()\n",
            $i,
            $frame['file'] ?? '?',
            $frame['line'] ?? 0,
            $frame['class'] ?? '',
            $frame['type'] ?? '',
            $frame['function']);
    }

    // 6. 原因例外チェーン
    $cur = $e;
    while ($cur) {
        error_log(get_class($cur) . ': ' . $cur->getMessage());
        $cur = $cur->getPrevious();
    }
}

Monolog でログ出力(PHP 標準)

pushHandler(new StreamHandler('/var/log/app.log', Logger::WARNING));

try {
    // ...
} catch (\Throwable $e) {
    // Monolog は exception を context に入れると自動展開
    $log->error('処理失敗', ['exception' => $e]);
}

// Laravel の場合
report($e);  // app/Exceptions/Handler.php の report() を呼ぶ
\Log::error('処理失敗', ['exception' => $e]);

Java の例外文字列化

import java.io.PrintWriter;
import java.io.StringWriter;

try {
    throw new RuntimeException("something broke",
        new IllegalArgumentException("inner cause"));
} catch (Exception e) {

    // 1. メッセージのみ
    System.out.println(e.getMessage());
    // → something broke

    // 2. 例外クラス + メッセージ
    System.out.println(e.toString());
    // → java.lang.RuntimeException: something broke

    // 3. スタックトレースを標準エラーへ
    e.printStackTrace();

    // 4. スタックトレースを文字列で取得(標準 API)
    StringWriter sw = new StringWriter();
    e.printStackTrace(new PrintWriter(sw));
    String stack = sw.toString();
    System.out.println(stack);
    /*
    java.lang.RuntimeException: something broke
        at com.example.Foo.bar(Foo.java:10)
        at com.example.Main.main(Main.java:5)
    Caused by: java.lang.IllegalArgumentException: inner cause
        ...
    */

    // 5. スタックトレース要素を配列で
    for (StackTraceElement el : e.getStackTrace()) {
        System.out.printf("%s.%s(%s:%d)%n",
            el.getClassName(),
            el.getMethodName(),
            el.getFileName(),
            el.getLineNumber());
    }

    // 6. 原因例外チェーンを辿る
    Throwable cur = e;
    while (cur != null) {
        System.err.println(cur.getClass().getName() + ": " + cur.getMessage());
        cur = cur.getCause();
    }
}

commons-lang3 / Guava を使う場合(ワンライナー)

// Maven: org.apache.commons:commons-lang3
import org.apache.commons.lang3.exception.ExceptionUtils;

String stack = ExceptionUtils.getStackTrace(e);
String root  = ExceptionUtils.getRootCauseMessage(e);
Throwable[] chain = ExceptionUtils.getThrowables(e);

// Maven: com.google.guava:guava
import com.google.common.base.Throwables;

String stack2 = Throwables.getStackTraceAsString(e);
Throwable root2 = Throwables.getRootCause(e);

SLF4J / Logback でログ出力

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger log = LoggerFactory.getLogger(Foo.class);

try {
    // ...
} catch (Exception e) {
    // ★ 例外オブジェクトを最後の引数で渡すと自動展開される
    log.error("処理失敗 user={}", userId, e);
}

// Spring Boot は標準で SLF4J + Logback
// application.yml で出力フォーマット調整
// logging.pattern.console: '%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n%ex'

Python の例外文字列化

import traceback
import sys
import logging

try:
    raise RuntimeError("something broke") from ValueError("inner cause")
except Exception as e:

    # 1. メッセージのみ
    print(str(e))
    # → something broke

    # 2. 型 + メッセージ
    print(repr(e))
    # → RuntimeError('something broke')

    # 3. スタックトレース文字列(最も汎用)
    stack = traceback.format_exc()
    print(stack)
    # Traceback (most recent call last):
    #   File "x.py", line 4, in 
    #     raise RuntimeError("something broke") from ValueError("inner cause")
    # RuntimeError: something broke

    # 4. 個別取得
    exc_type, exc_value, exc_tb = sys.exc_info()
    lines = traceback.format_exception(exc_type, exc_value, exc_tb)
    print(''.join(lines))

    # Python 3.10+ は例外オブジェクトだけで OK
    print(''.join(traceback.format_exception(e)))

    # 5. 標準エラー出力
    traceback.print_exc()

    # 6. 原因チェーン
    cur = e
    while cur:
        print(type(cur).__name__, str(cur))
        cur = cur.__cause__ or cur.__context__

logging モジュールで自動展開

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(name)s - %(message)s',
)
logger = logging.getLogger(__name__)

try:
    # ...
    pass
except Exception:
    # ★ exc_info=True でスタックトレース自動付与
    logger.error("処理失敗", exc_info=True)

    # logger.exception は ERROR レベル + exc_info=True と同じ
    logger.exception("処理失敗")

言語横断の比較表

用途PHPJavaPython
メッセージのみ$e->getMessage()e.getMessage()str(e)
型 + メッセージget_class($e).': '.$e->getMessage()e.toString()repr(e)
スタックトレース文字列$e->getTraceAsString()ExceptionUtils.getStackTrace(e)traceback.format_exc()
全文出力(標準形)(string) $ee.printStackTrace()traceback.print_exc()
ログ自動展開Monolog の ['exception' => $e]SLF4J log.error(msg, e)logger.exception(msg)
原因チェーン$e->getPrevious()e.getCause()e.__cause__ / __context__

セキュリティと運用の注意

  • 本番でユーザーにスタックトレースを返さない。ファイルパス / DB クエリが漏れる
  • 例外メッセージにパスワードやトークンを含めない(マスク処理を入れる)
  • ログにはリクエスト ID / ユーザー IDを必ず付与(後追い可能に)
  • 大量例外を生む箇所は Sentry / Datadog で集約・重複排除
  • PII(個人情報)が含まれる例外はマスク or 別ログへ

FAQ

Q: PHP で「Stack trace #0 {main}」しか出ない
A: 例外が投げられた直後でキャッチした場合は浅いです。debug_backtrace() を併用するか、より深い場所で投げ直す(rethrow)してください。

Q: Java で printStackTrace() がログに混ざらない
A: printStackTrace() は標準エラーに直接書き出すのでロガーの整形を経由しません。必ず log.error("msg", e) を使ってください。

Q: Python の例外チェーンが見づらい
A: raise X from Y で意図的にチェーンを付けると The above exception was the direct cause of the following exception という綺麗な形式で出ます。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. 変数
  3. 演算子
  4. 標準ライブラリ
  5. 外部ライブラリ
  6. 制御構文
  7. リスト(配列)
  8. タプル
  9. セット
  10. 辞書(dict)
  11. クラスとメソッド
  12. 継承の概念と必要性
  13. 継承の構文
  14. コンストラクタ
  15. cookieの値の設定と取得
  16. 例外処理
  17. 例外を文字列で出力する方法
  18. httpリクエスト(curl)をする方法
  19. Responseオブジェクトの中身の確認
  20. 変数が空かどうか判定する方法
  21. タイムゾーンの設定と現在日時の取得と文字列化
  22. シングルクォーテーションとダブルクォーテーションの違い