Serilog
2013年リリースの最新ロギングフレームワーク。構造化ログに特化した設計でJSON形式のログを標準サポート。豊富なシンク(出力先)、強力なフィルタリング機能、複雑なデータ型の効率的なクエリ機能を提供。
ライブラリ
Serilog
概要
Serilogは.NETアプリケーション向けの診断ロギングライブラリで、「シンプルな.NETロギングで完全な構造化イベント」を提供します。他のロギングライブラリとは異なり、構造化イベントデータを記録するために一から設計されており、シンプルなアプリケーションから複雑な分散システムまで幅広く対応。パフォーマンスへの影響がほぼゼロの非同期ロギングを特徴としています。
詳細
Serilogは2025年現在も活発に開発されている.NET界で最も人気の高いロギングライブラリの一つです。.NET 8を含む最新の.NETバージョンと完全に互換性があり、ASP.NET Core、Blazor、コンソールアプリ、デスクトップアプリなどあらゆる.NETアプリケーションで使用可能。メッセージテンプレートは.NETフォーマット文字列を拡張したシンプルなDSLで、パラメータの名前付けや値のシリアライズを支援します。
主な特徴
- 構造化ロギング: イベントデータを構造化して記録、柤軟な検索とソート機能
- 豊富なSink: ファイル、コンソール、データベース、クラウドログサーバー等への出力
- 非同期ロギング: パフォーマンスへの影響がほぼゼロの非同期処理
- リッチエンリッチメント: LogContext、スレッドID、相関ID等のコンテキスト情報追加
- 柔軽な設定: Fluent API、appsettings.json、XML設定に対応
- Seq統合: ローカル開発環境での構造化ログ解析ツール
メリット・デメリット
メラット
- .NET界で最も人気が高く最速のロギングフレームワーク
- 構造化ロギングがコア機能で、リッチなデータ型のログと効率的なクエリ機能
- シンプルなAPIと直感的なFluent設定システム
- 高性能な非同期ロギングとメッセージバッチ処理
- 豊富なエコシステム(100以上のSink、ASP.NET Core統合等)
- .NET 8を含む最新の.NETバージョンとの完全互換性
デメリット
- 初回利用時の構造化ロギングの概念理解が必要
- 大量のサードパーティSinkの選択肢が多すぎる場合がある
- プロダクション環境でのファイル・コンソールロギングは非推奨
- LogContextの不適切な使用はメモリリークの原因になる可能性
- 複雑な設定ではパフォーマンスへの影響が発生する可能性
- 他の.NETロギングライブラリとの同時使用時の競合状態
参考ページ
書き方の例
基本的なセットアップ
# 基本パッケージのインストール
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File
# ASP.NET Core統合用
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Settings.Configuration
# 追加のSink例
dotnet add package Serilog.Sinks.Seq
dotnet add package Serilog.Sinks.MSSqlServer
dotnet add package Serilog.Sinks.Elasticsearch
シンプルなロガー作成
using Serilog;
class Program
{
static void Main(string[] args)
{
// 基本的なロガー設定
using var log = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("log.txt")
.CreateLogger();
// シンプルなログ出力
log.Information("こんにちは、Serilog!");
// 構造化ログ出力
var position = new { Latitude = 35.6762, Longitude = 139.6503 };
var elapsedMs = 42;
log.Information("位置 {@Position} を {Elapsed} ms で処理しました", position, elapsedMs);
}
}
高度なロガー設定
using Serilog;
using Serilog.Events;
using Serilog.Formatting.Json;
class AdvancedLoggingExample
{
static void Main(string[] args)
{
// 高度な設定を持つグローバルロガー
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithThreadId()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
.WriteTo.File(
new JsonFormatter(),
"logs/app.json",
rollingInterval: RollingInterval.Day,
rollOnFileSizeLimit: true,
fileSizeLimitBytes: 10_000_000,
retainedFileCountLimit: 30)
.WriteTo.Seq("http://localhost:5341", apiKey: "your-api-key")
.CreateLogger();
try
{
// アプリケーションのメインロジック
Log.Information("アプリケーションが開始されました");
ProcessData();
Log.Information("アプリケーションが正常に終了しました");
}
catch (Exception ex)
{
Log.Fatal(ex, "アプリケーションで未処理例外が発生しました");
}
finally
{
await Log.CloseAndFlushAsync(); // アプリ終了前にすべてのログをフラッシュ
}
}
static void ProcessData()
{
using (LogContext.PushProperty("Operation", "DataProcessing"))
{
Log.Information("データ処理を開始します");
// データ処理のシミュレーション
var records = new[] { "Record1", "Record2", "Record3" };
foreach (var record in records)
{
using (LogContext.PushProperty("RecordId", record))
{
Log.Debug("レコード {RecordId} を処理中", record);
// 処理ロジック
Thread.Sleep(100);
Log.Information("レコード {RecordId} の処理が完了しました", record);
}
}
Log.Information("データ処理が完了しました。処理数: {RecordCount}", records.Length);
}
}
}
ASP.NET Core統合
// Program.cs (.NET 8 Web API)
using Serilog;
using Serilog.Events;
var builder = WebApplication.CreateBuilder(args);
// Serilog設定
builder.Host.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.Enrich.WithProperty("ApplicationName", "MyWebAPI")
.WriteTo.Console()
.WriteTo.File(
"logs/webapi-.log",
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.Seq("http://localhost:5341"));
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Serilogリクエストロギングミドルウェア
app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
options.GetLevel = (httpContext, elapsed, ex) => ex != null
? LogEventLevel.Error
: httpContext.Response.StatusCode > 499
? LogEventLevel.Error
: LogEventLevel.Information;
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent.FirstOrDefault());
};
});
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
// appsettings.json
{
"Serilog": {
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Seq"],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/webapi-.log",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Seq",
"Args": {
"serverUrl": "http://localhost:5341"
}
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
}
}
コントローラーでの使用例
using Microsoft.AspNetCore.Mvc;
using Serilog;
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly ILogger<UsersController> _logger;
public UsersController(ILogger<UsersController> logger)
{
_logger = logger;
}
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
using (_logger.BeginScope(new Dictionary<string, object> { ["UserId"] = id }))
{
_logger.LogInformation("ユーザー情報取得リクエスト: {UserId}", id);
try
{
// ビジネスロジックのシミュレーション
if (id <= 0)
{
_logger.LogWarning("無効なユーザーIDが指定されました: {UserId}", id);
return BadRequest("無効なユーザーIDです");
}
var user = await GetUserFromDatabase(id);
if (user == null)
{
_logger.LogWarning("ユーザーが見つかりません: {UserId}", id);
return NotFound();
}
_logger.LogInformation(
"ユーザー情報取得成功: {UserId}, 名前: {UserName}",
user.Id, user.Name);
return Ok(user);
}
catch (Exception ex)
{
_logger.LogError(ex,
"ユーザー情報取得中にエラーが発生しました: {UserId}", id);
return StatusCode(500, "内部サーバーエラー");
}
}
}
private async Task<User?> GetUserFromDatabase(int id)
{
// データベースアクセスのシミュレーション
_logger.LogDebug("データベースからユーザー情報を取得中: {UserId}", id);
await Task.Delay(50); // DBアクセスのシミュレーション
return id switch
{
1 => new User { Id = 1, Name = "田中太郎", Email = "[email protected]" },
2 => new User { Id = 2, Name = "佐藤花子", Email = "[email protected]" },
_ => null
};
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
非同期ロギングとパフォーマンス最適化
using Serilog;
using Serilog.Events;
class PerformanceOptimizedLogging
{
static void Main(string[] args)
{
// 高性能非同期ロギング設定
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Async(a => a.File(
"logs/performance.log",
rollingInterval: RollingInterval.Day,
buffered: true,
flushToDiskInterval: TimeSpan.FromSeconds(1),
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}"))
.WriteTo.Async(a => a.Console())
.CreateLogger();
// パフォーマンステスト
var stopwatch = Stopwatch.StartNew();
Parallel.For(0, 10000, i =>
{
// 条件付きログ出力(パフォーマンス最適化)
if (Log.IsEnabled(LogEventLevel.Debug))
{
Log.Debug("ループ処理: {Iteration}", i);
}
if (i % 1000 == 0)
{
Log.Information("進捗状況: {Progress}%", (double)i / 100);
}
});
stopwatch.Stop();
Log.Information("処理完了: {ElapsedMs} ms", stopwatch.ElapsedMilliseconds);
await Log.CloseAndFlushAsync();
}
}