.NET環境でのOpenTelemetry計装 - 実装ガイド ​

.NETはマイクロサービスアーキテクチャやエンタープライズアプリケーション開発で広く使われています。Windowsエコシステムからクラウド環境まで幅広く採用されており、分散システムの可視化には、これらの.NETアプリケーションから適切にトレーシングデータを収集することが重要です。このガイドでは、C#/.NETアプリケーションにOpenTelemetryの計装を実装し、トレーシングデータを取得するための具体的な手順を紹介していきます。

.NET環境での計装が必要な理由 ​

.NETアプリケーションに計装を施すことで、HTTP/gRPCリクエスト、データベースアクセス、キューメッセージ処理といった個々の操作が、分散トレースの中でどのような経路をたどっているのかを把握できます。このデータなしには、マイクロサービス間の遅延問題やエラーの原因特定は困難になります。

特にASP.NET Coreを使用したバックエンドサービスでは、複数の依存関係(Entity Framework Core、HTTP通信、キューシステム)が関連するため、これらを統一的に可視化することで、パフォーマンスボトルネックの特定と最適化が効率的に進みます。

環境セットアップ ​

前提条件 ​

OpenTelemetry .NETは.NET 6.0以上をサポートしています。まず、実行環境がこれを満たしているか確認しましょう。

bash
dotnet --version

プロジェクトへのNuGetパッケージ追加 ​

OpenTelemetryの基本パッケージをNuGetから追加します。Visual Studioの Package Manager Console、または dotnet CLIで実行できます。

bash
dotnet add package OpenTelemetry.Api
dotnet add package OpenTelemetry.Sdk

自動計装や特定のインストルメンテーションが必要な場合は、以下のように関連パッケージを追加します。

bash
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Instrumentation.SqlClient
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol

自動計装の実装 ​

自動計装とは ​

自動計装は、アプリケーションコードに最小限の変更を加えるだけで、HttpClient、ASP.NET Coreのリクエスト処理、SQL Serverデータベースアクセスなどから、テレメトリデータを自動的に収集する仕組みです。OpenTelemetryコミュニティが提供するインストルメンテーションライブラリを使用することで、実装の手間を大幅に削減できます。

ASP.NET Coreでの自動計装セットアップ ​

ASP.NET Coreアプリケーションでは、Startup.cs または Program.cs で OpenTelemetryを設定します。.NET 6.0以降で推奨される最小限のセットアップは以下のようになります。

csharp
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithTracing(tracerProviderBuilder =>
    {
        tracerProviderBuilder
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddSqlClientInstrumentation()
            .SetResourceBuilder(ResourceBuilder.CreateDefault()
                .AddService("my-service"));
    });

var app = builder.Build();
app.UseRouting();
app.Run();

このコード例では、ASP.NET Coreのリクエスト処理、HttpClientでの外部通信、SQL Serverへのデータベースアクセスが自動的に計装されます。

個別のインストルメンテーションの活用 ​

アプリケーションの特性に応じて、不要なインストルメンテーションは除外し、必要なものだけを有効化することで、オーバーヘッドを最小化できます。主要なインストルメンテーションの種類は以下の通りです。

AspNetCoreInstrumentation:HTTP/gRPCのリクエスト・レスポンス処理を記録します。ルーティング、ミドルウェア処理の実行時間を可視化できます。

HttpClientInstrumentation:HttpClientを使用した外部APIやマイクロサービスへの通信をトレースします。リクエスト遅延の特定に有用です。

SqlClientInstrumentation:SQL Serverまたはローカルデータベースのクエリ実行をトレースします。長時間実行クエリやN+1問題の検出に役立ちます。

StackExchangeRedisInstrumentation:キャッシュシステムやセッション管理で使用されるRedisへのアクセスを記録します。

手動計装の基本 ​

自動計装では対応していない独自の処理やビジネスロジックの詳細な追跡には、手動計装が必要です。

TracerProviderと Tracerの初期化 ​

トレーシング機能を使用するには、まずTracerProviderを初期化し、Tracerを取得します。

csharp
using OpenTelemetry.Trace;
using System.Diagnostics;

var tracerProvider = new TracerProviderBuilder()
    .AddConsoleExporter()
    .Build();

var tracer = TracerProvider.Default.GetTracer("my-application");

より実践的には、依存性注入を使用して Tracer を提供することが推奨されます。

csharp
builder.Services.AddSingleton(TracerProvider.Default.GetTracer("my-service"));

Spanの作成方法 ​

Spanを作成する基本的な方法は、using文を使用することです。このパターンにより、Span内で発生した例外の処理や、自動的なSpanのクローズが行われます。

csharp
using var activity = new Activity("ProcessOrder").Start();
var orderId = ProcessOrderLogic();
activity?.SetTag("order.id", orderId);

.NETではActivityクラスがOpenTelemetryのSpanに対応しており、より簡潔に記述できます。

OpenTelemetry APIを使用したSpan作成 ​

より標準的なOpenTelemetry APIを使用する場合は以下のようになります。

csharp
using var span = tracer.StartActiveSpan("CalculateDiscount");
var discountAmount = amount * 0.1m;
span.SetAttribute("discount.amount", discountAmount);
return discountAmount;

データ送信の設定 ​

自動計装と手動計装により収集したトレーシングデータを、OpenTelemetry Collectorに送信するには、エクスポーターを設定する必要があります。OTLP(OpenTelemetry Protocol)エクスポーターを使用する場合は以下のように設定します。

bash
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol

TracerProviderにエクスポーターを追加します。

csharp
builder.Services.AddOpenTelemetry()
    .WithTracing(tracerProviderBuilder =>
    {
        tracerProviderBuilder
            .AddAspNetCoreInstrumentation()
            .AddOtlpExporter(options =>
            {
                options.Endpoint = new Uri("http://localhost:4317");
            });
    });

デフォルトではlocalhost:4317に対してトレースデータが送信されます。本番環境ではエンドポイントを環境変数から読み込む方式が一般的です。

csharp
var otlpEndpoint = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT")
    ?? "http://localhost:4317";

tracerProviderBuilder.AddOtlpExporter(options =>
{
    options.Endpoint = new Uri(otlpEndpoint);
});

ASP.NET Core統合のベストプラクティス ​

エラーハンドリング ​

ASP.NET Coreアプリケーションでは、例外をトレースに自動的に記録することが重要です。AddAspNetCoreInstrumentationのRecordExceptionオプションをtrueに設定することで、未処理の例外が自動的にSpanに記録されます。

csharp
builder.Services.AddOpenTelemetry()
    .WithTracing(tracerProviderBuilder =>
    {
        tracerProviderBuilder
            .AddAspNetCoreInstrumentation(options =>
            {
                options.RecordException = true;
            })
            // ... other configurations
    });

このオプションを有効にすると、例外の詳細情報が自動的にSpanに追加され、手動でカスタムミドルウェアを記述する必要がなくなります。

リソース情報の設定 ​

リソース情報は、トレースデータにメタデータを付与し、複数のサービスを区別するのに役立ちます。サービス名、バージョン、環境などの情報を設定することで、分析時の識別が容易になります。

csharp
.SetResourceBuilder(ResourceBuilder.CreateDefault()
    .AddService(serviceName: "my-service",
                serviceVersion: "1.0.0")
    .AddAttributes(new Dictionary<string, object>
    {
        { "environment", "production" },
        { "deployment.environment", "kubernetes" }
    }))

設定オプション ​

OpenTelemetry .NETは環境変数による設定もサポートしています。以下のような標準的な環境変数を設定することで、コードを変更することなく動作を制御できます。

OTEL_SERVICE_NAME:サービス識別名 OTEL_EXPORTER_OTLP_ENDPOINT:Collectorのエンドポイント OTEL_EXPORTER_OTLP_PROTOCOL:通信プロトコル(grpc/http/protobuf) OTEL_TRACES_SAMPLER:サンプリング戦略(always_on/always_off/traceidratio等)

コンテナ環境やKubernetesでの運用を想定した場合、これらの環境変数設定が特に有用です。

.NET固有の考慮事項 ​

ActivitySourceの使用 ​

.NETの最新バージョンでは、ActivitySourceを使用する方が推奨されます。これはOpenTelemetry APIとの統合性が高く、より標準的なアプローチです。

csharp
var source = new ActivitySource("MyCompany.MyProduct.MyLibrary");

using var activity = source.StartActivity("DoWork");
activity?.SetTag("key", "value");

非同期処理でのコンテキスト伝播 ​

ASP.NET Coreは非同期処理を多用します。.NETの標準的な非同期API(Task.Runやawaitなど)は、ExecutionContextを自動的に伝播させるため、Activity.Currentも通常は自動で引き継がれます。以下の例では、Task.Run内でもActivity.Currentが自動的に利用可能です。

csharp
using var activity = new Activity("AsyncWork").Start();
await Task.Run(() =>
{
    // ビジネスロジック
    // Activity.Currentは自動的に伝播されるため、ここでも利用可能です
});

手動でコンテキストを復元する必要があるのは、ThreadPool.UnsafeQueueUserWorkItemなど、ExecutionContextのフローが明示的に抑制される特殊なケースのみです。通常の非同期パターンでは、このような手動処理は不要です。

OpenTelemetryの実装により、.NETアプリケーションのトレーシング機能を効果的に活用できます。自動計装から始めて、段階的に手動計装を追加していくアプローチが、実務的で効率的です。