C# Tips | ログ・例外・診断:ファイルログ

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

はじめに:「ファイルログ」は“あとから読み返せるブラックボックスレコーダー”

コンソールにログを出すだけだと、アプリを閉じた瞬間に全部消えます。
でも業務システムでは、数日後・数週間後に「そのとき何が起きていたか」を知りたいことが普通にあります。

そこで出てくるのが ファイルログ です。
ログをファイルに書き出しておけば、

夜中に動いていたバッチの様子
ユーザーから問い合わせがあった時間帯の動き
たまにしか起きないエラーの前後の状況

を、後からじっくり読み返せます。

ここでは、初心者向けに

ファイルログの基本イメージ
.NET でのシンプルなファイルログのやり方
ローテーション(ファイルを分割する)という考え方
実務で意識したい「書きすぎない・消しすぎない」のバランス

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


ファイルログの基本イメージ

「1 行 1 イベント」のテキストファイル

一番シンプルなファイルログは、こんなテキストファイルです。

2026-04-17 10:00:01 [Information] アプリ起動
2026-04-17 10:00:05 [Information] ログイン成功 UserId=123
2026-04-17 10:01:10 [Error] 受注登録に失敗 OrderId=456 System.Exception: ...

ポイントは、

日時
レベル(Information / Error など)
メッセージ
必要なら追加情報(ユーザーID、注文IDなど)

が、1 行にまとまっていることです。

この「1 行 1 イベント」の形にしておくと、
後から検索したり、ツールで集計したりしやすくなります。


まずは「自前で書く」超シンプルなファイルログ

File.AppendAllText を使った最低限の実装

学習用として、まずは「自分でファイルに書く」パターンを見てみます。

using System;
using System.IO;

public static class SimpleFileLogger
{
    private static readonly object _lock = new();

    public static void Log(string level, string message)
    {
        var line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}{Environment.NewLine}";
        var path = "app.log";

        lock (_lock)
        {
            File.AppendAllText(path, line);
        }
    }

    public static void Info(string message)  => Log("Information", message);
    public static void Error(string message) => Log("Error", message);
}
C#

使い方はこうです。

SimpleFileLogger.Info("アプリ起動");
SimpleFileLogger.Error("何かエラーが起きました");
C#

ここでの重要ポイントは、「複数スレッドから同時に書かれても壊れないように lock をかけている」ことです。
業務アプリでは並列処理も普通にあるので、「ファイルに書くときの排他」は必須です。

ただし、この実装はあくまで「学習用」です。
実務では、フレームワークやライブラリのロガー(ILogger など)と連携させるのが基本になります。


ASP.NET Core でのファイルログ(拡張ライブラリ利用)

ILogger をそのまま使ってファイルに出す

ASP.NET Core の標準には「ファイルログ」が直接は入っていませんが、
ILogger の仕組みを使って、ファイルに書き出すプロバイダを追加するのが一般的です。

代表的なのは Serilog や NLog などですが、ここではイメージだけ押さえます。

例えば Serilog を使う場合、Program.cs でこういう設定をします。

using Serilog;

var builder = WebApplication.CreateBuilder(args);

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.File(
        path: "logs/app-.log",
        rollingInterval: RollingInterval.Day,
        retainedFileCountLimit: 7)
    .CreateLogger();

builder.Host.UseSerilog();

var app = builder.Build();
C#

これで、アプリ内の ILogger<T> から出したログが、
logs/app-20260417.log のようなファイルに日ごとに出力されるようになります。

ここでの重要ポイントは、「アプリのコード側は ILogger を意識するだけで、“どこにどう出すか”は設定で差し替えられる」ことです。
コンソールだけに出すか、ファイルにも出すか、クラウドのログサービスにも送るか、などを後から変えられます。


ローテーション(ファイルを分割する)という考え方

1 ファイルに永遠に書き続けるのは危険

先ほどの自前実装だと、app.log 1 ファイルに延々と書き続けます。
これは学習用としてはいいのですが、実務では問題が出ます。

ファイルサイズがどんどん大きくなる
バックアップや転送が重くなる
古いログを消したいのに、1 ファイルに混ざっている

そこで出てくるのが ログローテーション です。
ざっくり言うと、

日ごとにファイルを分ける
サイズが一定を超えたら新しいファイルに切り替える
古いファイルは一定期間で削除する

といった運用です。

Serilog の例でいうと、先ほどの設定のここです。

.WriteTo.File(
    path: "logs/app-.log",
    rollingInterval: RollingInterval.Day,
    retainedFileCountLimit: 7)
C#

RollingInterval.Day
 → 日ごとにファイルを分ける(app-20260417.log など)
retainedFileCountLimit: 7
 → 7 個より古いファイルは自動で削除する

ここでの重要ポイントは、「ファイルログは“分割する前提”で設計する」ことです。
「1 個の app.log に全部入れる」は、実務ではほぼ通用しません。


ログの中身をどう設計するか

日時・レベル・メッセージ・コンテキスト

ファイルログの 1 行には、最低限これを入れておくと実務で戦えます。

日時
ログレベル
メッセージ
コンテキスト情報(ユーザーID、リクエストIDなど)

ILogger のメッセージテンプレートを使うと、自然にこれができます。

_logger.LogInformation(
    "受注登録開始 OrderId={OrderId}, UserId={UserId}",
    orderId, userId);
C#

ファイルに出ると、例えばこんな感じになります。

2026-04-17 11:00:01 [Information] 受注登録開始 OrderId=123, UserId=U001

ここでの重要ポイントは、「“後から検索したい情報”には必ず名前をつけてログに埋め込む」ことです。
OrderId や UserId を入れておくと、「この注文だけ」「このユーザーだけ」を追いかけられます。


実務でのファイルログの使いどころ

バッチ・サービス・デスクトップアプリなど「画面がない」もの

Web アプリはクラウドのログサービスに送ることも多いですが、
バッチや Windows サービス、デスクトップアプリなどでは、
「とりあえずファイルに出しておく」が今でも強い選択肢です。

夜間バッチの例をイメージしてみます。

public class NightlyJob
{
    private readonly ILogger<NightlyJob> _logger;

    public NightlyJob(ILogger<NightlyJob> logger)
    {
        _logger = logger;
    }

    public async Task RunAsync()
    {
        _logger.LogInformation("夜間バッチ開始");

        try
        {
            await Step1Async();
            await Step2Async();

            _logger.LogInformation("夜間バッチ正常終了");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "夜間バッチ異常終了");
            throw;
        }
    }
}
C#

これがファイルに残っていれば、
「何時に始まって、どこで落ちたか」が一目で分かります。

ここでの重要ポイントは、「“人が直接見に行くログ”として、ファイルログは今でも非常に強い」ということです。
特にオンプレ環境や閉じたネットワークでは、ほぼ必須です。


書きすぎない・消しすぎないのバランス

ログ量は「ディスク」と「読む人」の両方に効いてくる

ファイルログは、書けば書くほどディスクを食います。
そして、読む側からすると「ノイズが多いログ」はそれだけでつらいです。

なので、

本番では Information 以上だけファイルに出す
Debug や Trace は別の出力(コンソールだけ、開発環境だけ)にする
ローテーションで古いログは自動削除する

といったバランスが大事になります。

「とりあえず全部ファイルに出す」は、最初は安心感がありますが、
数ヶ月後に「ログが多すぎて誰も読まない」「ディスクが足りない」となりがちです。

ここでの重要ポイントは、「“本番で本当に読みたいログだけをファイルに残す”という意識を持つ」ことです。
それ以外の細かいログは、必要なときだけレベルを下げて出す、という運用に寄せていきます。


まとめ:「ファイルログユーティリティ」は“現場で一番頼りになる証拠箱”

ファイルログの本質は、

アプリの動きを
人間が後から読めるテキストとして
安全に・適量で・継続的に残しておく

ことです。

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

ファイルログは「1 行 1 イベント」で、日時・レベル・メッセージ・コンテキストを入れる
自前で書くなら lock で排他しつつ AppendAllText などで追記する
実務では ILogger とファイルプロバイダ(Serilog など)を組み合わせるのが現実的
ローテーション(日時やサイズでファイルを分割し、古いものを削除)を前提に設計する
本番で本当に読みたいログだけをファイルに出し、細かいログはレベル制御でオンオフする

ここまで腹落ちしていれば、
「とりあえず Console.WriteLine」から一歩進んで、
“現場で本当に役に立つファイルログ”を設計できるようになります。

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