mcpField
S
serilog

serilog

github.com/codewithmukesh開発7913.0k 回インストール12 週間前に更新
リポジトリ

serilog について

Serilogの設定とログ記録パターンを扱う.NET開発向けスキルです。構造化ログや起動・リクエストイベントの記録方法と、よくある問題の回避策を紹介します。

主な機能

  1. 01ロガーの2段階初期化
  2. 02構造化メッセージテンプレート
  3. 03appsettings.jsonによる設定
  4. 04リクエストログミドルウェア
  5. 05LogContextのスコーププロパティ
  6. 06OTLPへのイベント出力

活用シーン

  1. 01アプリ起動中のエラー記録
  2. 02名前付きプロパティによるログ検索
  3. 03ASP.NET Coreリクエストの要約
  4. 04イベントのOTLPバックエンドへの出力

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill serilog

インストール後、Claude Code で/serilogを入力して読み込まれたか確認します。

詳細

Serilog

基本原則

  1. 2段階の初期化 — 起動時にブートストラップロガーを作成し、DIの準備ができたら完全なロガーに置き換えます。これにより、そうしなければ失われる起動時のエラーを捕捉できます。
  2. UseSerilog()よりAddSerilog() — builder.Host.UseSerilog()ではなく、モダンなAPIであるbuilder.Services.AddSerilog()を使用します。ReadFrom.Services(services)を通じてDIサービスと統合できます。
  3. 文字列補間ではなくメッセージテンプレート — {PropertyName}構文を使うと、クエリ可能な構造化データが作成されます。文字列補間($"...")では構造が失われ、ログレベルが無効でもメモリが割り当てられます。
  4. appsettings.jsonで設定 — ログレベル、シンク、オーバーライドは設定に記述しておけば、再デプロイせずに環境ごとに変更できます。

パターン

2段階ブートストラップの設定
using Serilog;

// Stage 1: Bootstrap logger — captures startup errors before DI
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .CreateBootstrapLogger();

try
{
    Log.Information("Starting application");

    var builder = WebApplication.CreateBuilder(args);

    // Stage 2: Full logger with DI and configuration
    builder.Services.AddSerilog((services, lc) => lc
        .ReadFrom.Configuration(builder.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext()
        .Enrich.WithMachineName()
        .Enrich.WithEnvironmentName()
        .Enrich.WithProperty("Application", "MyApp.Api"));

    var app = builder.Build();

    app.UseSerilogRequestLogging(options =>
    {
        options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
        {
            diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
            diagnosticContext.Set("UserAgent",
                httpContext.Request.Headers.UserAgent.ToString());
        };
    });

    app.Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
    await Log.CloseAndFlushAsync();
}
appsettings.jsonの設定
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.AspNetCore": "Warning",
        "Microsoft.EntityFrameworkCore": "Warning",
        "Microsoft.Hosting.Lifetime": "Information",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/app-.log",
          "rollingInterval": "Day",
          "retainedFileCountLimit": 30,
          "fileSizeLimitBytes": 104857600
        }
      },
      {
        "Name": "Seq",
        "Args": { "serverUrl": "http://localhost:5341" }
      }
    ],
    "Enrich": ["FromLogContext", "WithMachineName", "WithEnvironmentName"],
    "Destructure": [
      { "Name": "ToMaximumDepth", "Args": { "maximumDestructuringDepth": 4 } },
      { "Name": "ToMaximumStringLength", "Args": { "maximumStringLength": 1024 } },
      { "Name": "ToMaximumCollectionCount", "Args": { "maximumCollectionCount": 10 } }
    ]
  }
}

オーバーライドセクションでは、SourceContextと照合する名前空間プレフィックスを使用します。より具体的なプレフィックスが優先されます。

リクエストロギングミドルウェア

ASP.NET Coreがリクエストごとに出力する複数のログイベントを、単一の要約イベントに置き換えます。

app.UseSerilogRequestLogging(options =>
{
    options.MessageTemplate =
        "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";

    options.GetLevel = (httpContext, elapsed, ex) => ex is not null
        ? LogEventLevel.Error
        : httpContext.Response.StatusCode >= 500
            ? LogEventLevel.Error
            : LogEventLevel.Information;

    options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
    {
        diagnosticContext.Set("UserId",
            httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous");
    };
});
構造化ロギングとデストラクチャリング
// Named properties — creates queryable structured data
logger.LogInformation("Order {OrderId} placed by {CustomerId} for {Total:C}",
    orderId, customerId, total);

// @ operator preserves object structure as properties
logger.LogInformation("Processing {@SensorInput}", sensorInput);
// Output: Processing {"Latitude": 25, "Longitude": 134}

// $ operator forces ToString()
logger.LogInformation("Received {$Data}", new[] { 1, 2, 3 });
// Output: Received "System.Int32[]"
LogContextによるスコープ付きプロパティ
using (LogContext.PushProperty("CorrelationId", correlationId))
using (LogContext.PushProperty("TenantId", tenantId))
{
    logger.LogInformation("Processing order {OrderId}", orderId);
    // CorrelationId and TenantId attached to ALL log events in this scope
}

ロガーの設定で.Enrich.FromLogContext()が必要です。

OpenTelemetryシンク(OTLPエクスポート)

OpenTelemetry SDKを使わずに、Serilogイベントを任意のOTLPバックエンドへ直接エクスポートします。

.WriteTo.OpenTelemetry(options =>
{
    options.Endpoint = "http://localhost:4317";
    options.Protocol = OtlpProtocol.Grpc;
    options.ResourceAttributes = new Dictionary<string, object>
    {
        ["service.name"] = "MyApp.Api",
        ["deployment.environment"] = "production"
    };
})
フィルタリング用のSerilog.Expressions

Serilog.Expressionsパッケージが必要です。

// Exclude health check noise
.Filter.ByExcluding("RequestPath like '/health%'")

// Route errors to a separate file
.WriteTo.Conditional("@l = 'Error'",
    wt => wt.File("logs/errors-.log", rollingInterval: RollingInterval.Day))
高頻度処理向けの[LoggerMessage]ソースジェネレーター

Microsoft.Extensions.Logging.Abstractionsに組み込まれており、コンパイル時に生成されます。ログレベルが無効な場合、メモリ割り当てはゼロです。

public static partial class OrderLogs
{
    [LoggerMessage(Level = LogLevel.Information,
        Message = "Order {OrderId} created for {CustomerId}")]
    public static partial void OrderCreated(this ILogger logger, Guid orderId, Guid customerId);
}

// Usage
logger.OrderCreated(order.Id, order.CustomerId);

アンチパターン

文字列補間を使わない
// BAD — breaks structured logging, allocates even when level is disabled
logger.LogInformation($"Order {orderId} created for {customerId}");

// GOOD — message template with named parameters
logger.LogInformation("Order {OrderId} created for {CustomerId}", orderId, customerId);
CloseAndFlushを省略しない
// BAD — async sinks (Seq, OTLP, Elasticsearch) lose buffered events
app.Run();

// GOOD — wrap in try/finally
try { app.Run(); }
catch (Exception ex) { Log.Fatal(ex, "Unhandled exception"); }
finally { await Log.CloseAndFlushAsync(); }
機密データをログに記録しない
// BAD — passwords and tokens in logs
logger.LogInformation("Login: {Email} with password {Password}", email, password);

// GOOD — never log secrets, passwords, tokens, or PII
logger.LogInformation("Login: {Email}", email);
制限なしでデストラクチャリングしない
// BAD — large object graphs cause memory issues and massive log entries
logger.LogInformation("Request: {@Request}", httpContext.Request);

// GOOD — configure destructuring limits
.Destructure.ToMaximumDepth(4)
.Destructure.ToMaximumStringLength(1024)
.Destructure.ToMaximumCollectionCount(10)

// BETTER — destructure to specific properties
.Destructure.ByTransforming<HttpRequest>(r => new { r.Method, r.Path })
非推奨のElasticsearchシンクを使わない
// BAD — the Serilog.Sinks.Elasticsearch PACKAGE is deprecated
// <PackageReference Include="Serilog.Sinks.Elasticsearch" />
.WriteTo.Elasticsearch("http://localhost:9200")

// GOOD — same method name, but from the official Elastic.Serilog.Sinks
// package, which writes ECS-formatted documents to data streams
// <PackageReference Include="Elastic.Serilog.Sinks" />
.WriteTo.Elasticsearch([new Uri("https://elastic.example.com:9200")], opts =>
    opts.DataStream = new DataStreamName("logs", "myapp"))

選択ガイド

シナリオ推奨
アプリケーションのロギングAddSerilog()とappsettings.jsonを使ったSerilog
ログの保存(開発環境)Seq(無料のシングルユーザー版)またはAspire Dashboard
ログの保存(本番環境)Seq、Elasticsearch(Elasticシンク)、またはOTLPバックエンド
リクエストのロギングUseSerilogRequestLogging()(リクエストごとのノイズを置き換えます)
スコープ付きプロパティミドルウェアでLogContext.PushProperty()を使用
ログのフィルタリング式ベースのフィルタリングにはSerilog.Expressionsを使用
高パフォーマンスが求められる処理[LoggerMessage]ソースジェネレーター
監査証跡AuditTo(同期処理、例外は伝播します)
環境ごとのログレベルappsettingsで名前空間ごとにMinimumLevel.Overrideを設定
OpenTelemetryとの統合Serilog.Sinks.OpenTelemetry(SDKへの依存なし)

コメント