Start Debugging

.NET 11 で Serilog から OpenTelemetry ロギングへ移行する

.NET 11 アプリを Serilog から OpenTelemetry ロギングへ移行するためのステップバイステップガイド。低リスクな Serilog.Sinks.OpenTelemetry ブリッジ、Microsoft.Extensions.Logging への全面的な切り替え、何が壊れるか、検証方法、ロールバックの方法を解説します。

チームがトレースとメトリクスについて OpenTelemetry に標準化している場合、仲間外れになりがちなのはたいていロギングで、いまだに Serilog とファイルまたは Seq シンクを通っているものです。このガイドでは、.NET 11 アプリ (OpenTelemetry .NET SDK 1.15.x、Serilog 4.x) をその分断された構成から外し、OpenTelemetry ロギングへ移行します。ルートは 2 つあります。Serilog を残してシンクだけを差し替える一晩で済むブリッジと、Serilog を完全に取り除く Microsoft.Extensions.Logging への全面的な切り替えです。ブリッジは 1 コミットで元に戻せ、ほとんど何も壊しません。全面的な移行は実際のコードベースでは 1 日か 2 日かかり、すべての BeginScope とメッセージテンプレートに手を入れることになります。これに価値があるのは、Serilog 依存の除去と 1 つのロギング API への統一が、あったら嬉しい程度ではなく実際の目標である場合だけです。

そもそもなぜロギングを OpenTelemetry へ移すのか

まだ OpenTelemetry のトレースを組み込んでいない場合は、まずそれを行ってください。.NET 11 と無料バックエンドで OpenTelemetry を使う では、このガイドが既に整っていることを前提としているエクスポーターとバックエンドのセットアップを解説しています。

何が壊れるか

領域変更深刻度
シンク構成ファイル/コンソール/Seq シンクが OTLP エクスポーターまたは Serilog.Sinks.OpenTelemetry に置き換わるhigh (full) / low (bridge)
Log.Logger static + CreateBootstrapLogger()全面移行で削除される。2 段階の起動時ロギングがなくなるhigh (full only)
LogContext.PushProperty エンリッチャーILogger.BeginScopeIncludeScopes = true に置き換わるmedium (full only)
デストラクチャリング演算子 {@Order}Microsoft.Extensions.Logging に同等物はない。スカラーフィールドをログ出力するか、明示的にシリアライズするmedium (full only)
UseSerilogRequestLogging()ASP.NET Core OTel インストルメンテーションまたは AddHttpLogging に置き換わるmedium (full only)
MinimumLevel 設定ブロックappsettings.jsonLogging:LogLevel セクションへ移動するlow (full only)
重大度の名前Serilog の Verbose は OTel の Trace に対応する。Information はそのままlow

「ブリッジ」の列が重要です。ルート A を取るなら、最初の行だけが該当し、深刻度は low です。それ以外はすべて全面移行に関わる問題です。

事前チェックリスト

移行手順

1. パッケージのバージョンを固定する

ルートを決め、それに合うパッケージをインストールします。どちらのルートも同じ SDK ラインを対象にします。

<!-- .NET 11, both routes -->
<!-- Route A (bridge): keep Serilog, swap the sink -->
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.2.0" />

<!-- Route B (full): Microsoft.Extensions.Logging + OpenTelemetry -->
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />

検証: コードを変更する前に dotnet restore が成功し、dotnet build がクリーンであることを確認します。

2a. ルート A — Serilog のシンクを OTLP に差し替える

これは低リスクな道です。既存のエンリッチャー、メッセージテンプレート、LogContext 呼び出しはすべて残します。シンク構成だけを置き換えます。

// .NET 11, C# 14, Serilog 4.x, Serilog.Sinks.OpenTelemetry 4.2.0
using Serilog;
using Serilog.Sinks.OpenTelemetry;

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .WriteTo.OpenTelemetry(options =>
    {
        options.Endpoint = "http://localhost:4318/v1/logs";
        options.Protocol = OtlpProtocol.HttpProtobuf;
        options.ResourceAttributes = new Dictionary<string, object>
        {
            ["service.name"] = "orders-api",
            ["service.version"] = "2.4.0"
        };
    })
    .CreateLogger();

シンクは Activity.Current を読み取り、すべてのログレコードに TraceIdSpanId を自動的に注入します (IncludedData.TraceIdFieldSpanIdField はデフォルトでオン)。そのため、追加のエンリッチャーなしでシグナルをまたいだ相関が機能します。_logger.LogInformation("Placed order {OrderId}", id) のテンプレートはそのまま流れていきます。

検証: アプリを起動し、リクエストを 1 つ投げ、そのログ行がリクエストのトレースと同じ TraceId で OTLP バックエンドに表示されることを確認します。

2b. ルート B — Microsoft.Extensions.Logging へ切り替える

Program.cs から UseSerilog() / AddSerilog()Log.Logger のブートストラップを取り除きます。代わりに OpenTelemetry を組み込みのロギングビルダーに組み込みます。

// .NET 11, C# 14, OpenTelemetry .NET SDK 1.15.3
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddOpenTelemetry(options =>
{
    options.IncludeScopes = true;            // keep BeginScope properties
    options.IncludeFormattedMessage = true;  // populate the log body
    options.ParseStateValues = true;         // capture structured attributes
    options.SetResourceBuilder(
        ResourceBuilder.CreateDefault()
            .AddService("orders-api", serviceVersion: "2.4.0"));
    options.AddOtlpExporter(o =>
    {
        o.Endpoint = new Uri("http://localhost:4318");
    });
});

var app = builder.Build();

アプリが既にトレースとメトリクスのために builder.Services.AddOpenTelemetry() を呼び出している場合は、3 つのシグナルすべてが 1 つのリソースとエクスポーターを共有するよう、統一された形式を優先します。

// .NET 11, OpenTelemetry .NET SDK 1.15.3
builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("orders-api", serviceVersion: "2.4.0"))
    .WithTracing(t => t.AddAspNetCoreInstrumentation())
    .WithMetrics(m => m.AddAspNetCoreInstrumentation())
    .UseOtlpExporter(); // one call: configures OTLP for traces, metrics, AND logs

// Logs still need the provider on the logging builder:
builder.Logging.AddOpenTelemetry(o =>
{
    o.IncludeScopes = true;
    o.IncludeFormattedMessage = true;
    o.ParseStateValues = true;
});

UseOtlpExporter() (SDK 1.8 で追加) は、すべてのシグナルに対して OTLP エクスポーターを一度に登録するため、エンドポイントを 3 回繰り返す必要はありません。

検証: dotnet run を実行し、ILogger の行が正しい service.name と内容の入った本文とともにバックエンドに届くことを確認します。

3. エンリッチャーをスコープに変換する (ルート B のみ)

Serilog の LogContext.PushProperty("UserId", id) には Microsoft.Extensions.Logging に同等物がありません。ILogger.BeginScope を使えば、IncludeScopes = true を設定しているため、プロパティが OTLP レコードに流れ込みます。

// Before (Serilog)
using (LogContext.PushProperty("UserId", userId))
{
    _logger.LogInformation("Loaded cart");
}

// After (.NET 11, Microsoft.Extensions.Logging)
using (_logger.BeginScope(new Dictionary<string, object> { ["UserId"] = userId }))
{
    _logger.LogInformation("Loaded cart");
}

検証: 出力されたログレコードが UserId を属性として持つことを確認します。欠けている場合は IncludeScopes = true を忘れています。

4. リクエストロギングを置き換える (ルート B のみ)

app.UseSerilogRequestLogging() はリクエストごとに 1 行の要約ログを生成していました。OpenTelemetry では、ASP.NET Core インストルメンテーションが既にリクエストごとに HTTP サーバースパンを出力しており、これが「このリクエストで何が起きたか」を表すより良いプリミティブです。それでもログ行が欲しい場合は、HTTP ロギングを追加します。

// .NET 11
builder.Services.AddHttpLogging(o => { });
// ...
app.UseHttpLogging();

検証: 各リクエストが TraceId で相関付けられた HTTP サーバースパン (とオプションの HTTP ログエントリ) を生成することを確認します。

5. レベル設定を appsettings に移す

Serilog の MinimumLevel ブロックは標準の Logging:LogLevel セクションに置き換わり、OpenTelemetry プロバイダーは他と同様にこれを尊重します。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

検証: カテゴリを Warning に設定し、その Information の行がバックエンドに表示されなくなることを確認します。

検証チェックリスト

どちらのルートのあとでも、古いパッケージを削除する前にこれを実行します。

ロールバックプラン

ルート A は 1 コミットで元に戻せます。 WriteTo.OpenTelemetry(...) ブロックを元の WriteTo.Seq(...) / WriteTo.File(...) 構成に戻し、Serilog.Sinks.OpenTelemetry パッケージを削除します。それ以外は何も変えていないので、リスク面はありません。

ルート B は元に戻せますが簡単ではありません。 新しいパイプラインが本番で 1 リリースサイクル稼働するまで、Serilog パッケージはインストールしたまま、古い Program.cs のブートストラップは git 履歴に残しておきます。差し戻す必要があれば、UseSerilog()Log.Logger のブートストラップを復元し、BeginScope の呼び出しを LogContext.PushProperty に戻します。切り替えはコードベース全体のスコープとリクエストロギングに手を入れるため、差し戻しは 1 行のトグルではなく、それ自体を小さな移行として扱ってください。本番で検証が通るまで、.csproj から Serilog パッケージを削除しないでください。

私たちがはまった落とし穴

バックエンドでログ本文が空になる。 IncludeFormattedMessage がデフォルトの false のままだと、OTLP レコードは構造化された属性を伴って送られますがレンダリングされたメッセージは含まれず、一部のバックエンドでは空行が表示されます。これをオンにしてください。あわせて ParseStateValues = true も設定し、名前付きプレースホルダー ({OrderId}) がフォーマット済み文字列の中だけでなく属性としても届くようにします。

スコーププロパティが消える。 IncludeScopes はデフォルトで false です。すべての BeginScope の値や、ASP.NET Core がリクエストスコープに入れたものは、これを有効にするまで破棄されます。これは「移行でログコンテキストの半分を失った」という報告の中で最もよくあるものです。

{@Object} デストラクチャリングがない。 Serilog の _logger.LogInformation("Got {@Order}", order) はオブジェクト全体をシリアライズしていました。Microsoft.Extensions.Logging@ をリテラルテキストとして扱います。実際にクエリするスカラーフィールドをログ出力するか、System.Text.Json で明示的にシリアライズしてください。オブジェクト全体をダンプすると属性のカーディナリティも爆発し、一部のバックエンドはそれに課金します。

2 段階のブートストラップロギングを失う。 Serilog の CreateBootstrapLogger() は、ホストがビルドされる前に起きる障害を捕捉していました。OpenTelemetry プロバイダーは builder.Build() のあとにしか存在しないため、ごく初期の起動時例外はコンソールにしか出力されません。起動初期の可観測性が重要なら、その期間用に最小限のコンソールロガーを残してください。

重大度マッピングの意外な点。 Serilog の Verbose は OTel の Trace になり、FatalCritical になります。重大度の名前で下流のフィルタリングやアラートを行っている場合は、それらのルールを更新してください。DebugInformationWarningError は 1 対 1 で対応します。

どのみちロギングを整えるなら、そもそも構造化データをどう出力するかを見直す価値があります。.NET 11 で Serilog と Seq による構造化ロギングをセットアップする では、ILogger にきれいに引き継げるメッセージテンプレートのパターンを解説しており、ASP.NET Core 11 のネイティブ OpenTelemetry トレーシング では、統一されたパイプラインに移れば追加のインストルメンテーションパッケージが不要になる理由を説明しています。バックグラウンドジョブやホストされるサービスについては、Hangfire なしでバックグラウンドジョブを監視する がリクエストパスの外でも同じ相関が機能することを示しており、Aspire 13.2.4 OpenTelemetry baggage アドバイザリ は OTel パッケージにパッチを当て続けることを思い出させてくれます。

Serilog の除去が本当の目標でない限り、ブリッジを選んでください。それは今夜にも OTLP ログとトレース相関をもたらしてくれますし、スコープとリクエストロギングをきちんと変換する静かなスプリントが取れたときに、後から全面的な切り替えを行えます。

ソース

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る