Serilog

2013年リリースの最新ロギングフレームワーク。構造化ログに特化した設計でJSON形式のログを標準サポート。豊富なシンク(出力先)、強力なフィルタリング機能、複雑なデータ型の効率的なクエリ機能を提供。

ロギング.NETC#構造化ログASP.NET Coreデバッグ

ライブラリ

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();
    }
}