CodeGym /コース /C# SELF /構造化ロギングと Serilog

構造化ロギングと Serilog

C# SELF
レベル 64 , レッスン 1
使用可能

1. はじめに

普通のテキストログがあなたの日記でメモが "今日ご飯を食べた" みたいなものだとすると、構造化ロギングは各エントリをフィールド付きのカードに変えます: {日付: ..., イベント: "食べた", カロリー: 500, 料理: "焼き肉"}。つまり、その日記を読むだけでなく、月ごとのカロリーのグラフを作ったり、料理でフィルタしたり、いつ遅く食べたかを調べられるってことです。

なぜプレーンテキストだけでは不十分なのか?

プレーンテキストだと楽なんだけど... ある時点から辛くなります。ログからエラーの統計を集めたり、EC サイトの売上を集計したり、あるユーザーの操作履歴を ID(例えば 42)で辿ったりするのは、全部がただの長いテキストだと大変です。構造化ロギングがあればログに分析やAIを繋げられる。異常検知したり、ダッシュボード作ったり、自動で問題に対処したりできます。

利点

  • メッセージだけでなく、それに紐づくデータ(フィールド/プロパティ)もログできる。
  • ログは自動的に解析できる:集計、フィルタ、レポート作成が容易。
  • JSON のような標準フォーマットを使えるので、機械でのパースが簡単。

Serilog:何でこれを使うの?

Serilog(公式サイト: serilog.net, ドキュメント: github.com/serilog/serilog/wiki)は .NET 用の人気のある構造化ロギングライブラリです。Microsoft.Extensions.Logging とよく統合でき、ファイル、コンソール、Seq、ElasticSearch、Grafana、Azure など多数の出力先をサポートし、パフォーマンスへの影響は小さく設定もシンプルです。

Serilog は「普通のログ」と何が違うの?

  • 構造: ログはフィールドを持つオブジェクトになり、ID 42 のユーザーのエラーだけを抽出する、みたいなことができる。
  • フォーマット: テキストだけでなく JSONXML を出力でき、後処理が楽。
  • 柔軟性: たくさんの既製の sink パッケージがあって、どこにでも送れる。

Serilog のログエントリの構造

コードを書く前に、まず構造化ログの例を見てみましょう。

{
  "Timestamp": "2024-06-22T10:23:45.123Z",
  "Level": "Information",
  "MessageTemplate": "ユーザー {UserId} がログインしました",
  "Properties": {
    "UserId": 42,
    "IpAddress": "127.0.0.1"
  }
}

これだけで解析ツールは「ユーザー #42 と IP アドレスが関係している」と分かります。

2. C# プロジェクトでの Serilog のインストールと基本設定

ステップ1. NuGet パッケージのインストール

Rider/Visual Studio の NuGet Package Manager で以下を入れてください:

  • Serilog
  • Serilog.Sinks.Console(コンソール出力)
  • Serilog.Extensions.LoggingMicrosoft.Extensions.Logging と統合するため)

コマンドラインからは:

dotnet add package Serilog
dotnet add package Serilog.Sinks.Console

ステップ2. 最低限の設定

Program.cs に設定を追加して最初のログを出してみましょう。

using System;
using Serilog;

namespace MySuperApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1. Serilog の基本設定: コンソール出力
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Console()
                .CreateLogger();

            // 2. 構造化ロギングの例
            int userId = 42;
            string ip = "127.0.0.1";
            Log.Information("ユーザー {UserId} が IP {IpAddress} でログインしました", userId, ip);

            Log.CloseAndFlush();
        }
    }
}

コンソールには大体こんな感じで出ます:

[10:30:16 INF] ユーザー 42 が IP 127.0.0.1 でログインしました

出力先を JSON ファイルや Seq、ほかのシステムに向けるのは簡単です。

3. ログのフォーマット: Message Template

Serilog では文字列連結の代わりにテンプレート構文を使います:

Log.Information("ファイルに対する操作 {Operation}: {FileName}", "削除", "test.txt");

これは単なる見た目の良さだけじゃなくて、ログに OperationFileName といったフィールドが入るので、後でフィルタや集計に使えます。

string.Format と何が違うの?

string.Format("Операция {0} над файлом {1}", operation, fileName) はプレースホルダー {0}, {1} を置き換えるだけです。Serilog はフィールドを分離して扱うので、解析に向いています。

柔軟な設定: レベル、フィルター、複数の sink

Serilog は同時に複数の場所にログを書けます。

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

これでコンソールにもファイル(日別ローテーション)にもログが出ます。

4. 例

コンソールの「メモ帳」アプリを作るとします。ユーザーのエントリを作成できて、その操作を構造化ログとして残します。

using System;
using Serilog;

namespace NotesApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Information()
                .WriteTo.Console()
                .WriteTo.File("notes-log.json", rollingInterval: RollingInterval.Day, 
                              formatter: new Serilog.Formatting.Json.JsonFormatter())
                .CreateLogger();

            Console.WriteLine("ユーザー名を入力して:");
            string userName = Console.ReadLine();

            Log.Information("ユーザー {UserName} が NotesApp を起動しました", userName);

            while (true)
            {
                Console.WriteLine("メモの内容を入力してください(終了 と入力すると終了):");
                string note = Console.ReadLine();

                if (note == "終了")
                {
                    Log.Information("ユーザー {UserName} が終了しました", userName);
                    break;
                }

                Log.Information("ユーザー {UserName} がメモを作成しました: {NoteText}", userName, note);
            }

            Log.CloseAndFlush();
        }
    }
}

補足:
ログは誰がアプリを起動し、何を入力し、いつ終了したかを記録します。ファイル notes-log.json の各エントリは JSON オブジェクトで、解析が簡単です。

5. 便利なポイント

構造化ロギングのベストプラクティス

  • 本番環境で Debug/Trace を乱用しない — InformationWarning を適切に使う。
  • 文字列連結ではなく、テンプレートで名前付きパラメータを使う。
  • 機密データ(パスワード、トークン、キー)は絶対にログに出さない。
  • エラーだけでなく、重要なビジネスイベントもログする。
  • ログのローテーションとクリーンアップを設定してディスク不足を防ぐ。

可視化と解析: Seq, Kibana, Application Insights

Serilog は多くの sink をサポートしていて、ログの送り先を柔軟に選べます。

Sink 概要 どこで使われるか
Console そのままコンソールに出力 開発、テスト
File ローカルやネットワーク上のファイルに出力 小規模プロジェクト、dev
Seq フィルタやダッシュボード付きの Web インターフェース エンタープライズ、分析
ElasticSearch 強力な保存と解析のためのシステム 大規模な組織
Azure Application Insights クラウド監視とテレメトリ Azure を使ったサービス

Seq (datalust.co/seq) は開発や社内利用で人気のあるソリューションです:フィールド検索が便利で、ダッシュボード作るのが速いです。

テーブル表示や可視化

以下は構造化ログでよく記録する項目の簡単な表です:

ログするもの Serilog での表現 値の例
ユーザー ID
{UserId}
123
アクション
{Action}
"削除"
エラー
Log.Error(ex, "モジュールでエラーが発生しました: {Module}")
"登録モジュール"
操作時間 {Elapsed:0.000} 1.234
ファイル名
{FileName}
"report.pdf"

面白い機能と追加の可能性: Serilog

  • Enrichers: 各ログにプロパティを追加できる(例えば .Enrich.WithMachineName())。
  • 相関ログ: チェーンを辿れるように RequestId を入れる。
  • appsettings.json での設定: 本番運用時に便利。
{
  "Serilog": {
    "MinimumLevel": "Debug",
    "WriteTo": [
      { "Name": "Console" },
      { "Name": "File", "Args": { "path": "log.txt" } }
    ]
  }
}

高度な sink:Slack、Telegram、メールにログを飛ばすことも可能ですが、エラーごとに大量にメールが飛ばないよう注意してください。

6. 実践: Microsoft.Extensions.Logging との統合

.NET では特定のライブラリに依存しないために標準のインターフェイス ILogger を使うことが多いです。Serilog はプロバイダとして接続できます。

ステップ1. パッケージのインストール

dotnet add package Serilog.Extensions.Logging

ステップ2. 設定

using Microsoft.Extensions.Logging;
using Serilog;

// ...

// 通常通り Serilog を設定:
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

// で、Microsoft.Extensions.Logging を使う
var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddSerilog();
});

ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("テストメッセージ: {TestValue}", 123);

// 忘れずにクローズ:
Log.CloseAndFlush();

補足:
これでコード中の ILogger<T> はプロバイダに依存しません — 必要なら NLogLog4Net に切り替えられます。

7. Serilog を使うときのよくあるミス

ログの過剰出力: 何でもかんでも出すと重要な情報が埋もれます。

例外をテキストだけでログする: 例外オブジェクトを渡すオーバーロードを使いましょう — そうするとエラーの構造がログに残ります。

try
{
    // some code
}
catch (Exception ex)
{
    Log.Error(ex, "リクエストの実行中にエラーが発生しました");
}

設定の乱用: 設定を雑に増やしてしまうと管理が難しくなります — 必要な sink とレベルだけを追加しましょう。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION