C# Tips | ログ・例外・診断:スタックトレース整形

C# C#
スポンサーリンク

はじめに:スタックトレース整形は「エラーの道筋を、人間が読める形にする」技

例外が発生したとき、ex.ToString()ex.StackTrace をそのままログに出すと、
情報は多いけれど「どこを見ればいいのか分からない長文」になりがちです。

業務で本当に欲しいのは、

どのメソッドを通って
どのクラスから呼ばれて
最終的にどこで落ちたのか

を、パッと見て追える形のスタックトレースです。

そこで出てくるのが スタックトレース整形 のユーティリティです。
「生のスタックトレース」を、ログや画面に出しやすい形に整える小さな道具だと思ってください。

ここでは、初心者向けに

スタックトレースの基本構造
Exception.ToString()StackTrace の違い
不要な行を削ったり、インデントを付けたりする整形例
内部例外(InnerException)も含めてきれいに出すユーティリティ

を、例題付きでかみ砕いて説明します。


スタックトレースの基本をざっくり理解する

スタックトレースは「どのメソッドを通ってここに来たか」の履歴

例外が発生したとき、ex.ToString() を見ると、こんな感じの文字列が出ます。

System.InvalidOperationException: 何かがおかしい
   at SampleApp.Service.DoWork() in C:\src\SampleApp\Service.cs:line 25
   at SampleApp.Program.Main(String[] args) in C:\src\SampleApp\Program.cs:line 10
C#

上から順に、

例外の型とメッセージ
その例外が発生したメソッド(DoWork)
そのメソッドを呼んだメソッド(Main)

という「呼び出しの逆順」が並んでいます。

ここでの重要ポイントは、
「スタックトレースは“エラーまでの道筋”であり、行数が多くても“読むべき場所”は限られている」
ということです。


Exception.ToString と StackTrace プロパティの違い

ToString は「メッセージ+スタックトレース全部」、StackTrace は「スタック部分だけ」

Exception には、よく使うプロパティがいくつかあります。

ex.Message
例外メッセージだけ。

ex.StackTrace
スタックトレースだけ(メソッドの列)。

ex.ToString()
例外の型名+メッセージ+スタックトレース+(InnerException があればその情報も)全部。

ログに出すとき、よくやるのは ex.ToString() を丸ごと出すパターンです。

_logger.LogError(ex, "処理中にエラーが発生しました。");
C#

多くのロガーは、ex を渡すと内部で ToString() 相当を出してくれます。

一方で、「画面に出す」「メールに載せる」「Slack に貼る」など、
人間が直接読む用途では、もう少しコンパクトにしたくなることがあります。

ここでの重要ポイントは、
「用途によって、Message だけ、StackTrace だけ、ToString() 丸ごと、を使い分ける」
ということです。


スタックトレースを「読みやすく整形する」簡単な例

行ごとに分割して、インデントを付ける

ex.StackTrace は単なる長い文字列なので、
Split して行ごとに加工することができます。

例えば、「先頭にインデントを付けて、ログの中で見やすくする」ユーティリティです。

public static class StackTraceFormatter
{
    public static string IndentStackTrace(Exception ex, string indent = "    ")
    {
        if (ex.StackTrace == null)
        {
            return "(スタックトレースなし)";
        }

        var lines = ex.StackTrace
            .Split(new[] { Environment.NewLine }, StringSplitOptions.None);

        var sb = new System.Text.StringBuilder();
        foreach (var line in lines)
        {
            sb.Append(indent);
            sb.AppendLine(line);
        }

        return sb.ToString();
    }
}
C#

使い方はこうです。

catch (Exception ex)
{
    var formatted = StackTraceFormatter.IndentStackTrace(ex);
    Console.WriteLine("エラー発生:");
    Console.WriteLine(ex.Message);
    Console.WriteLine(formatted);
}
C#

ログの中で、スタックトレース部分だけがきれいにインデントされていると、
他のログ行と混ざらず、とても読みやすくなります。

ここでの重要ポイントは、
「スタックトレースは“ただの文字列”なので、行ごとに加工して見やすくできる」
という感覚を持つことです。


不要な行を削る:自分のアプリ以外のフレームワーク行を間引く

「System.」「Microsoft.」などの行をスキップする

大きなアプリでは、スタックトレースにフレームワーク内部の行が大量に含まれます。
全部残しておく価値はありますが、
「とりあえず自分のコード周りだけ見たい」ということも多いです。

簡易的に、「自分の名前空間以外の行をスキップする」整形をしてみます。

public static class StackTraceFormatter
{
    public static string FilterByNamespace(Exception ex, string includeNamespacePrefix)
    {
        if (ex.StackTrace == null)
        {
            return "(スタックトレースなし)";
        }

        var lines = ex.StackTrace
            .Split(new[] { Environment.NewLine }, StringSplitOptions.None);

        var sb = new System.Text.StringBuilder();
        foreach (var line in lines)
        {
            if (line.Contains(includeNamespacePrefix))
            {
                sb.AppendLine(line);
            }
        }

        if (sb.Length == 0)
        {
            return "(対象の名前空間を含むフレームがありません)";
        }

        return sb.ToString();
    }
}
C#

使い方はこうです。

var filtered = StackTraceFormatter.FilterByNamespace(ex, "MyApp.");
Console.WriteLine(filtered);
C#

これで、「MyApp.」を含む行だけが出力されます。

ここでの重要ポイントは、
「全部の行を捨てるのではなく、“自分のコードを含む行だけ抜き出す”という整形ができる」
ということです。
調査の第一歩としては、これだけでもかなり楽になります。


InnerException も含めて「きれいにまとめて出す」

ネストした例外を、段付きで出力する

実務では、例外ラップをしていることが多く、
InnerException がさらに InnerException を持っている、というネスト構造になります。

ex.ToString() はこれを全部まとめて出してくれますが、
「どこからどこまでがどの例外か」が分かりづらいこともあります。

そこで、「外側→内側へ」と段付きで出すユーティリティを書いてみます。

public static class ExceptionPrinter
{
    public static string FormatExceptionWithInner(Exception ex, string indent = "")
    {
        var sb = new System.Text.StringBuilder();

        sb.AppendLine($"{indent}Type   : {ex.GetType().FullName}");
        sb.AppendLine($"{indent}Message: {ex.Message}");

        if (!string.IsNullOrEmpty(ex.StackTrace))
        {
            sb.AppendLine($"{indent}StackTrace:");
            var lines = ex.StackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
            foreach (var line in lines)
            {
                sb.Append(indent);
                sb.Append("  ");
                sb.AppendLine(line);
            }
        }

        if (ex.InnerException != null)
        {
            sb.AppendLine($"{indent}InnerException:");
            sb.Append(FormatExceptionWithInner(ex.InnerException, indent + "  "));
        }

        return sb.ToString();
    }
}
C#

使い方はこうです。

catch (Exception ex)
{
    var text = ExceptionPrinter.FormatExceptionWithInner(ex);
    Console.WriteLine(text);
}
C#

出力イメージは、例えばこんな感じになります。

Type   : MyApp.Service.UserServiceException
Message: ユーザー取得に失敗しました
StackTrace:
  at MyApp.Service.UserService.GetUser(...)
  ...

InnerException:
  Type   : MyApp.Repository.UserRepositoryException
  Message: DB アクセスに失敗しました
  StackTrace:
    at MyApp.Repository.UserRepository.GetUser(...)
    ...

  InnerException:
    Type   : System.SqlClient.SqlException
    Message: 接続タイムアウト
    StackTrace:
      ...
C#

ここでの重要ポイントは、
「例外のネスト構造を“段付きのテキスト”にすると、どの層で何が起きたかが一気に見える」
ということです。
これは、障害調査のときに本当に効きます。


実務での使いどころ:ログ・メール・通知メッセージ

「ログには全部」「通知には整形版」という使い分け

スタックトレース整形ユーティリティは、次のような場面でよく使います。

ログファイル
ex をそのまま渡して、フルの情報を残す。

メール通知・Slack 通知
整形したテキストを貼り付けて、人間が読みやすい形にする。

画面の開発者向け詳細表示
ユーザーには簡単なメッセージだけ、開発者モードでは整形スタックトレースを表示。

例えば、エラー通知メールの本文を作るとき。

var body = new StringBuilder();
body.AppendLine("エラーが発生しました。");
body.AppendLine();
body.AppendLine(ExceptionPrinter.FormatExceptionWithInner(ex));

// body.ToString() をメール本文として送信
C#

ここでの重要ポイントは、
「“全部残す場所”と“人がすぐ読む場所”で、スタックトレースの出し方を変える」
という設計です。
整形ユーティリティは、その「人がすぐ読む場所」用の道具です。


まとめ:スタックトレース整形ユーティリティは“エラーの物語を読みやすくする翻訳者”

スタックトレース整形の本質は、

例外がたどってきた道筋を
人間が追いやすい形に並べ替え
必要に応じて要約・インデント・フィルタリングすること

です。

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

スタックトレースは「エラーまでの道筋」であり、行ごとに加工できるただの文字列であること。
Exception.ToString() は全部入り、StackTrace はスタック部分だけ、用途で使い分けること。
インデントやフィルタリングで、「自分のコード周りだけ」「見やすい形」に整形できること。
InnerException を段付きで出すと、「どの層で何が起きたか」が一目で分かること。
ログにはフル情報、通知や画面には整形版、という使い分けを意識すると実務で扱いやすいこと。

ここまでイメージできていれば、
「とりあえず ex.ToString() をベタッと出す」段階から一歩進んで、
“人間が読んで理解しやすいエラーログ”を設計できるようになります。

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