Java Tips | 基本ユーティリティ:スタックトレース文字列化

Java Java
スポンサーリンク

スタックトレース文字列化は「エラーの足跡をテキストとして残す」技

例外が起きたとき、Java は「スタックトレース」という“足跡”を持っています。
これは「どのメソッドからどのメソッドへ呼ばれて、最終的にどこでエラーになったか」を示す、とても重要な情報です。

普段はコンソールに e.printStackTrace() で出力されますが、業務システムでは「ログに文字列として残したい」「外部サービスに送信したい」「画面に整形して表示したい」といったニーズが出てきます。
そのときに必要になるのが「スタックトレースを文字列化するユーティリティ」です。


まずは基本:printStackTrace を String に変える

StringWriter + PrintWriter を使う定番パターン

Java でスタックトレースを文字列にする、一番オーソドックスな方法はこれです。

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

public final class StackTraces {

    private StackTraces() {}

    public static String toString(Throwable t) {
        if (t == null) {
            return "";
        }
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);
        pw.flush();
        return sw.toString();
    }
}
Java

使い方はとてもシンプルです。

try {
    doSomething();
} catch (Exception e) {
    String stack = StackTraces.toString(e);
    System.out.println(stack);
}
Java

ここで押さえておきたいポイントは二つです。

一つ目は、「Throwable#printStackTrace(PrintWriter) を使うと、出力先を自由に差し替えられる」ということです。
通常は標準エラー出力に出ますが、StringWriter をかませることで「文字列として受け取る」ことができます。

二つ目は、「Throwable を受け取るようにしている」ことです。
Exception だけでなく、Error も含めて扱えるようにしておくと、ユーティリティとして汎用性が高くなります。


ログフレームワークと組み合わせるときの使いどころ

ログフレームワークは「例外を渡すだけ」でスタックトレースを出してくれる

SLF4JLogback, Log4j2 などのログフレームワークは、例外オブジェクトを渡すだけでスタックトレースを出力してくれます。

log.error("エラーが発生しました", e);
Java

この場合、わざわざ文字列化する必要はありません。
ログファイルには、メッセージと一緒にスタックトレースがきれいに出力されます。

つまり、「ログに出すだけなら、スタックトレース文字列化ユーティリティは不要」です。
ここを勘違いして、何でもかんでも toString(e) してログに流すと、逆に見づらくなることもあります。

それでも「文字列化」が必要になる場面

それでもユーティリティが活きるのは、例えばこんな場面です。

監視サービス(SaaS)に JSON でエラー情報を送るとき、スタックトレースを文字列として詰め込みたい。
DB のエラーテーブルに「スタックトレース全文」を文字列として保存したい。
画面の「詳細表示」ボタンを押したときに、スタックトレースをテキストエリアに表示したい。

こういう「ログ以外の場所」にスタックトレースを載せたいときに、StackTraces.toString(e) のようなユーティリティが効いてきます。


実務で使えるスタックトレース文字列化ユーティリティの工夫

最大長を決めて「長すぎるスタックトレース」を切る

スタックトレースは、場合によってはとても長くなります。
それをそのまま DB に入れたり、外部サービスに送ったりすると、サイズ制限に引っかかったり、ログがノイズだらけになったりします。

そこで、「最大長を決めて切り詰める」ユーティリティを用意しておくと実務的です。

public static String toStringLimited(Throwable t, int maxLength) {
    String full = toString(t);
    if (full.length() <= maxLength) {
        return full;
    }
    return full.substring(0, maxLength) + "\n... (truncated)";
}
Java

使い方はこうです。

String stack = StackTraces.toStringLimited(e, 4000);
Java

ここで深掘りしたいのは、「保存先・送信先の制約を意識して、スタックトレースを“扱いやすいサイズ”に整える」という発想です。
「全部残したい」気持ちは分かりますが、現実には「4KB まで」「1 万文字まで」といった制約があることが多いので、ユーティリティ側で制御しておくと安全です。

先頭数行だけを抜き出すユーティリティ

「とりあえず原因の場所だけ知りたい」というときは、先頭数行だけを抜き出すのも有効です。

public static String head(Throwable t, int lines) {
    String full = toString(t);
    String[] split = full.split("\\R"); // 改行で分割
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < Math.min(lines, split.length); i++) {
        sb.append(split[i]).append(System.lineSeparator());
    }
    if (split.length > lines) {
        sb.append("... (").append(split.length - lines).append(" lines more)");
    }
    return sb.toString();
}
Java

例えばこう使います。

String shortStack = StackTraces.head(e, 5);
Java

これは、Slack 通知やメール通知など、「全文はうるさいけれど、入口と原因だけ知りたい」場面でとても便利です。


セキュリティとプライバシーの観点も忘れない

スタックトレースは「内部構造の情報の塊」

スタックトレースには、クラス名・メソッド名・パッケージ構成・ファイル名・行番号など、システム内部の情報が大量に含まれています。
これをそのまま外部のユーザーに見せると、「内部構造の暴露」になり、セキュリティリスクになることがあります。

例えば、Web アプリでエラーが起きたときに、画面にスタックトレース全文を表示するのは典型的なアンチパターンです。
攻撃者にとっては、「どんなフレームワークを使っているか」「どのライブラリのどのバージョンか」といったヒントになります。

「どこまで誰に見せるか」をユーティリティ側で意識する

スタックトレース文字列化ユーティリティは、「誰に見せるか」を意識して使い分けるべきです。

開発者向けのログ・監視サービス・内部用管理画面:全文または制限付き全文を使う。
一般ユーザー向けの画面:スタックトレースは見せず、「問い合わせ ID」や「簡単なメッセージ」だけを表示する。

ユーティリティ自体は単に文字列を返すだけですが、「どこでどう使うか」のルールをチームで決めておくと、安全な運用につながります。


例外ラップと組み合わせたときのスタックトレースの見え方

ラップしても cause をつなげておけば情報は失われない

前回話した「例外ラップ」と組み合わせるとき、new AppException("...", e) のように cause を渡していれば、
スタックトレース文字列化をしても、ラップされた階層ごとの情報がすべて含まれます。

try {
    doIo();
} catch (IOException e) {
    throw new AppException("I/O エラー", e);
}
Java

この AppExceptionStackTraces.toString(e) すると、

AppException: I/O エラー
at …
Caused by: java.io.IOException: …
at …

という形で、「どこでラップされたか」「元の例外は何か」が全部見えます。
つまり、「ラップしても cause をつなげておけば、スタックトレース文字列化で情報はちゃんと追える」ということです。


まとめ:スタックトレース文字列化で初心者が身につけるべき感覚

スタックトレース文字列化は、「例外の足跡を、ログや外部サービスに載せられる形に変換する」ための小さなユーティリティです。

押さえておきたいポイントは次の通りです。

StringWriterPrintWriterThrowable#printStackTrace で、スタックトレースを文字列にできる。
ログフレームワークに渡すだけなら文字列化は不要で、「ログ以外の場所」に載せたいときにユーティリティが活きる。
最大長や先頭行数を制限するメソッドを用意しておくと、DB や通知先の制約に対応しやすい。
スタックトレースは内部情報の塊なので、「誰にどこまで見せるか」を意識して使う。
例外ラップと組み合わせるときは、cause をつなげておけば、文字列化しても情報は失われない。

タイトルとURLをコピーしました