はじめに
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 を利用することで、ログの生成や付加情報・出力先などを柔軟に管理できます。