Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

C# Serilog で人間向けのログと構造化ログの両方を出力する

0
Posted at

はじめに

Serilog で人間向けのログと構造化ログの両方を出力する備忘録です。

改訂履歴

  • 2026/09/10 : 初版公開。

本文

1. 環境

  • .NET 8.0
  • Serilog 4.4.0
  • Serilog.Enrichers.Process 3.0.0
  • Serilog.Enrichers.Thread 4.0.0
  • Serilog.Expressions 5.0.0
  • Serilog.Sinks.File 7.0.0
  • Visual Studio Community 2022

2. 実装例

Serilog は .NET のロギングライブラリです。log4net や NLog よりも構造化ログに強く、近年ではデファクトスタンダードの一つとして採用されている印象です。

SerilogManager.cs
using System.Reflection;
using Serilog;
using Serilog.Formatting.Json;
using Serilog.Templates;

namespace SerilogExample;

/// <summary>
/// <see cref="Serilog"/> の初期化と終了処理を提供します。
/// </summary>
public static class SerilogManager
{
    /// <summary>
    /// ログファイルを保持する件数の上限値です。
    /// </summary>
    private static readonly int s_retainedFileCountLimit = 90;

    /// <summary>
    /// 初期化処理を実行します。
    /// </summary>
    public static void Initialize()
    {
        // ログディレクトリのパスを取得する。
        string logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
        string jsonLogDirectory = Path.Combine(logDirectory, "json");
        string textLogDirectory = Path.Combine(logDirectory, "txt");

        // ログディレクトリが存在しない場合、作成する。
        Directory.CreateDirectory(jsonLogDirectory);
        Directory.CreateDirectory(textLogDirectory);

        // アセンブリ名を取得する。
        string assemblyName = Assembly.GetEntryAssembly()?.GetName().Name ?? "log";

        // Serilog の初期設定を実行する。
        Log.Logger = new LoggerConfiguration()
            .Enrich.FromLogContext() // ログコンテキストの情報を追加できるようにする。
            .Enrich.WithProcessId()  // プロセス ID を追加できるようにする。
            .Enrich.WithThreadId()   // スレッド ID を追加できるようにする。
            .MinimumLevel.Debug()    // 最小ログレベルを Debug に設定する。

            // 人間向けログの出力設定。
            .WriteTo.File(
                formatter: new ExpressionTemplate(
                    "{@t:yyyy-MM-dd HH:mm:ss.fff} " + // タイムスタンプ。
                    "[{@l:u3}] " + // ログレベル。
                    "(ProcessId:{ProcessId}) " + // プロセス ID。
                    "(ThreadId:{ThreadId}) " + // スレッド ID。
                    "{#if RequestId is not null}(RequestId:{RequestId}) {#end}" + // RequestId が存在する場合は出力する。
                    "{@m}\n" + // メッセージ。
                    "{@x}"), // 例外情報。
                path: Path.Combine(textLogDirectory, $"{assemblyName}_.txt"),
                rollingInterval: RollingInterval.Day,
                retainedFileCountLimit: s_retainedFileCountLimit)

            // 構造化ログの出力設定。
            .WriteTo.File(
                formatter: new JsonFormatter(),
                path: Path.Combine(jsonLogDirectory, $"{assemblyName}_.json"),
                rollingInterval: RollingInterval.Day,
                retainedFileCountLimit: s_retainedFileCountLimit)

            // ロガーの生成。
            .CreateLogger();
    }

    /// <summary>
    /// 終了処理を実行します。
    /// </summary>
    public static void Shutdown()
    {
        Log.CloseAndFlush();
    }
}

3. 動作確認

Program.cs に動作確認用のコードを書きました。

Program.cs
using Serilog;
using Serilog.Context;

namespace SerilogExample;

/// <summary>
/// アプリケーションのエントリポイントを定義します。
/// </summary>
internal static class Program
{
    /// <summary>
    /// アプリケーションのエントリポイントです。
    /// </summary>
    [STAThread]
    static void Main()
    {
        SerilogManager.Initialize();

        try
        {
            Log.Information("アプリケーションを開始しました。");

            Parallel.For(0, 10, i =>
            {
                Log.Information("スレッド {Index} からログを出力します。", i);
            });

            using (LogContext.PushProperty("RequestId", "0123456789"))
            {
                Log.Debug("Debug ログの動作確認です。");
                Log.Warning("Warning ログの動作確認です。");
            }

            try
            {
                throw new InvalidOperationException("動作確認用の例外です。");
            }
            catch (Exception ex)
            {
                Log.Error(ex, "例外ログの動作確認です。");
            }
        }
        finally
        {
            SerilogManager.Shutdown();
        }
    }
}

良い感じに出力されてますね。

SerilogExample_20260910.txt
2026-09-10 13:43:58.545 [INF] (ProcessId:8232) (ThreadId:1) アプリケーションを開始しました。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:11) スレッド 2 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:1) スレッド 0 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:5) スレッド 3 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:7) スレッド 4 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:12) スレッド 1 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:13) スレッド 5 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:14) スレッド 6 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:15) スレッド 7 からログを出力します。
2026-09-10 13:43:58.672 [INF] (ProcessId:8232) (ThreadId:16) スレッド 8 からログを出力します。
2026-09-10 13:43:58.677 [INF] (ProcessId:8232) (ThreadId:17) スレッド 9 からログを出力します。
2026-09-10 13:43:58.695 [DBG] (ProcessId:8232) (ThreadId:1) (RequestId:0123456789) Debug ログの動作確認です。
2026-09-10 13:43:58.696 [WRN] (ProcessId:8232) (ThreadId:1) (RequestId:0123456789) Warning ログの動作確認です。
2026-09-10 13:43:58.710 [ERR] (ProcessId:8232) (ThreadId:1) 例外ログの動作確認です。
System.InvalidOperationException: 動作確認用の例外です。
   at SerilogExample.Program.Main() in C:\src\net8\Program.cs:line 36
SerilogExample_20260910.json
{"Timestamp":"2026-09-10T13:43:58.5458936+09:00","Level":"Information","MessageTemplate":"アプリケーションを開始しました。","Properties":{"ProcessId":8232,"ThreadId":1}}
{"Timestamp":"2026-09-10T13:43:58.6722081+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":2,"ProcessId":8232,"ThreadId":11}}
{"Timestamp":"2026-09-10T13:43:58.6721629+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":0,"ProcessId":8232,"ThreadId":1}}
{"Timestamp":"2026-09-10T13:43:58.6721908+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":3,"ProcessId":8232,"ThreadId":5}}
{"Timestamp":"2026-09-10T13:43:58.6722245+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":4,"ProcessId":8232,"ThreadId":7}}
{"Timestamp":"2026-09-10T13:43:58.6721303+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":1,"ProcessId":8232,"ThreadId":12}}
{"Timestamp":"2026-09-10T13:43:58.6720914+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":5,"ProcessId":8232,"ThreadId":13}}
{"Timestamp":"2026-09-10T13:43:58.6720588+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":6,"ProcessId":8232,"ThreadId":14}}
{"Timestamp":"2026-09-10T13:43:58.6721085+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":7,"ProcessId":8232,"ThreadId":15}}
{"Timestamp":"2026-09-10T13:43:58.6727767+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":8,"ProcessId":8232,"ThreadId":16}}
{"Timestamp":"2026-09-10T13:43:58.6777547+09:00","Level":"Information","MessageTemplate":"スレッド {Index} からログを出力します。","Properties":{"Index":9,"ProcessId":8232,"ThreadId":17}}
{"Timestamp":"2026-09-10T13:43:58.6951135+09:00","Level":"Debug","MessageTemplate":"Debug ログの動作確認です。","Properties":{"RequestId":"0123456789","ProcessId":8232,"ThreadId":1}}
{"Timestamp":"2026-09-10T13:43:58.6960827+09:00","Level":"Warning","MessageTemplate":"Warning ログの動作確認です。","Properties":{"RequestId":"0123456789","ProcessId":8232,"ThreadId":1}}
{"Timestamp":"2026-09-10T13:43:58.7108341+09:00","Level":"Error","MessageTemplate":"例外ログの動作確認です。","Exception":"System.InvalidOperationException: 動作確認用の例外です。\r\n   at SerilogExample.Program.Main() in C:\\src\\net8\\Program.cs:line 36","Properties":{"ProcessId":8232,"ThreadId":1}}

おわりに

DB にログテーブルを作成して保存する古典的な方法もありますが、Serilog を利用することで、ログの生成や付加情報・出力先などを柔軟に管理できます。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?