C# Tips | ログ・例外・診断:エラーコード管理

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

はじめに:エラーコード管理は「エラーに“番号付きの名前”を与えること」

業務システムで本気で運用を考え始めると、
「エラーが起きました」だけでは足りなくなります。

どの画面で
どの種類のエラーが
どれくらいの頻度で起きているのか

を、ログや問い合わせ対応の中で素早く共有したくなります。
そこで効いてくるのが エラーコード管理 です。

ざっくり言うと、

「エラーに“人間にも機械にも扱いやすい ID(コード)”を振って、
ログ・画面・マニュアル・問い合わせ対応で共通言語にする」

という仕組みです。

ここでは、初心者向けに

なぜエラーコードが必要なのか
C# でのエラーコード定義パターン(enum/クラス)
例外やログとの紐付け方
小さなユーティリティとしての「エラーコード管理クラス」の形

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


なぜエラーコードを管理するのか

「同じエラーを、いつも同じ名前で呼べるようにする」

例えば、ユーザーからこんな問い合わせが来たとします。

「ユーザー登録画面でエラーになりました」

これだけだと、開発側はログを漁りながら「どのエラーだろう…」と探すことになります。
でも、画面にこう出ていたらどうでしょう。

「エラーが発生しました。(エラーコード:USR-001)」

ユーザーが「USR-001」と伝えてくれれば、
開発側はログで「ErrorCode=USR-001」を検索するだけで、
該当するエラーの詳細に一気にたどり着けます。

ここでの重要ポイントは、

エラーコードは「人間同士の会話」と「ログ検索」の両方を楽にするためのラベル
同じ種類のエラーには、常に同じコードを使うことで、知識が蓄積される

ということです。


エラーコードを enum で定義する基本パターン

まずは「意味のある名前」を付けるところから

一番シンプルなやり方は、C# の enum でエラーコードを定義することです。

public enum ErrorCode
{
    None = 0,

    UserNotFound = 1001,
    UserAlreadyExists = 1002,

    InvalidInput = 2001,
    PermissionDenied = 2002,

    DatabaseError = 9001,
    ExternalServiceError = 9002
}
C#

ここでは、数値はあくまで内部的な ID で、
ログや画面には「数値+シンボリック名」または「シンボリック名だけ」を出すイメージです。

例えば、例外クラスにエラーコードを持たせます。

public class AppException : Exception
{
    public ErrorCode ErrorCode { get; }

    public AppException(ErrorCode errorCode, string message, Exception? inner = null)
        : base(message, inner)
    {
        ErrorCode = errorCode;
    }
}
C#

使う側はこうなります。

if (user == null)
{
    throw new AppException(
        ErrorCode.UserNotFound,
        $"ユーザーが見つかりません。Id={id}");
}
C#

ここでの重要ポイントは、

エラーコードを enum にすることで、コード補完が効き、タイポを防げる
「どんなエラーがあり得るか」が enum を見れば一覧できる

という点です。


エラーコードとログを紐付ける

ログに必ず「ErrorCode=XXX」を残す

エラーコード管理の真価は、ログと組み合わせたときに発揮されます。
ILogger を使って、エラーコードを必ずログに含めるようにします。

public static class ErrorLoggingExtensions
{
    public static void LogError(this ILogger logger, AppException ex, string context)
    {
        logger.LogError(
            ex,
            "ErrorCode={ErrorCode} Context={Context} Message={Message}",
            ex.ErrorCode,
            context,
            ex.Message);
    }
}
C#

使い方はこうです。

try
{
    DoSomething();
}
catch (AppException ex)
{
    _logger.LogError(ex, "ユーザー登録処理中");
    throw;
}
C#

ログには、例えばこんな行が残ります。

「ErrorCode=UserNotFound Context=ユーザー登録処理中 Message=ユーザーが見つかりません。Id=123」

ここでの重要ポイントは、

ログに「ErrorCode」というキーで必ず出す
これを軸に検索・集計できるようにする

という設計です。
ログ分析ツール(Elasticsearch、Application Insights など)と組み合わせると、
「UserNotFound がどれくらい発生しているか」といった集計も簡単になります。


エラーコードとユーザー向けメッセージを分離する

「コードは固定」「メッセージは差し替え可能」にしておく

業務システムでは、ユーザー向けメッセージを後から変更したり、多言語対応したりしたくなります。
そのとき、エラーコードとメッセージがベタ書きで結びついていると、変更が大変です。

そこで、「エラーコード → メッセージテンプレート」のマッピングを別に持つパターンがよく使われます。

public static class ErrorMessages
{
    private static readonly Dictionary<ErrorCode, string> _messages = new()
    {
        { ErrorCode.UserNotFound, "ユーザーが見つかりません。(コード:USR-001)" },
        { ErrorCode.UserAlreadyExists, "同じユーザーが既に存在します。(コード:USR-002)" },
        { ErrorCode.InvalidInput, "入力内容に誤りがあります。(コード:COM-001)" },
        { ErrorCode.PermissionDenied, "この操作を行う権限がありません。(コード:SEC-001)" },
        { ErrorCode.DatabaseError, "システム内部エラーが発生しました。(コード:SYS-DB)" },
        { ErrorCode.ExternalServiceError, "外部サービスとの通信に失敗しました。(コード:SYS-EXT)" }
    };

    public static string GetMessage(ErrorCode code)
    {
        if (_messages.TryGetValue(code, out var message))
        {
            return message;
        }

        return $"不明なエラーが発生しました。(コード:{code})";
    }
}
C#

UI 層では、こう使います。

catch (AppException ex)
{
    var message = ErrorMessages.GetMessage(ex.ErrorCode);
    ShowError(message);
}
C#

ここでの重要ポイントは、

エラーコードは変えない(ログや問い合わせのキーになる)
メッセージは別テーブルにしておき、後から差し替えや多言語化ができるようにする

という設計です。
「コードはシステムの約束」「メッセージは人間向けの説明」と割り切ると、整理しやすくなります。


エラーコード管理ユーティリティを作る

「例外生成」「ログ」「ユーザー表示」を一箇所に寄せる

エラーコード管理を本気でやるなら、
「エラーコードを使う場面」をユーティリティに寄せてしまうと、コードがすっきりします。

例えば、こんな小さなヘルパーを考えてみます。

public static class ErrorFactory
{
    public static AppException Create(ErrorCode code, string? detail = null, Exception? inner = null)
    {
        var baseMessage = ErrorMessages.GetMessage(code);
        var fullMessage = string.IsNullOrEmpty(detail)
            ? baseMessage
            : $"{baseMessage} 詳細: {detail}";

        return new AppException(code, fullMessage, inner);
    }
}
C#

使う側はこうです。

if (user == null)
{
    throw ErrorFactory.Create(
        ErrorCode.UserNotFound,
        $"Id={id}");
}
C#

ログ側は、先ほどの拡張メソッドでエラーコードを必ず出す。
UI 側は、ex.ErrorCode からメッセージを引く。

このように、「エラーコード → 例外 → ログ/画面」の流れを一本のルールにしておくと、
どこから見ても同じエラーコードで追えるようになります。

ここでの重要ポイントは、

エラーコードを「例外」「ログ」「画面表示」の共通キーにする
そのための生成・表示・ログ出力をユーティリティに寄せる

ということです。


実務での運用イメージ:問い合わせ対応と障害調査が楽になる

「エラーコードを言ってもらえれば、すぐログに飛べる」世界

最後に、エラーコード管理が効いてくる具体的な場面をイメージしてみます。

ユーザーが「エラーコード:USR-001 と出ました」と問い合わせてくる
サポート担当がマニュアルで「USR-001=ユーザー未存在」と確認する
開発者はログで「ErrorCode=UserNotFound」を検索し、該当ログをすぐ見つける
必要なら、そのエラーコードの発生回数を集計して、改善優先度を決める

この一連の流れが、「エラーコード」という共通言語でつながります。

ここでの重要ポイントは、

エラーコードは「人間の会話」と「システムのログ」を橋渡しするラベル
だからこそ、適当に増やさず、設計して管理する価値がある

ということです。


まとめ:エラーコード管理は“エラーに名前と住所を与える”設計

エラーコード管理の本質を一言で言うと、

「エラーに一意な名前(コード)と意味を与え、
ログ・画面・マニュアル・問い合わせ対応のすべてで同じ名前で呼べるようにする」

ことです。

押さえておきたいポイントを整理すると、

エラーコードは enum などで定義し、「どんなエラーがあり得るか」を一覧できるようにする。
エラーコードを持つ共通例外クラス(AppException など)を用意し、例外とコードをセットで扱う。
ログには必ず ErrorCode を出力し、検索・集計のキーにする。
ユーザー向けメッセージは「エラーコード → メッセージ」のテーブルで管理し、後から差し替え可能にする。
エラー生成・ログ・表示をユーティリティに寄せて、コード全体のルールを揃える。

ここまでイメージできていれば、
「その場その場で適当にメッセージを投げる」段階から抜けて、
“エラーが整理されていて、追いやすく、運用しやすいシステム”に一歩近づけます。

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