mcpField
C
configuring-opentelemetry-dotnet

configuring-opentelemetry-dotnet

github.com/dotnet開発5.8k3.4k 回インストール1 日前に更新
リポジトリ

configuring-opentelemetry-dotnet について

configuring-opentelemetry-dotnetは、ASP.NET CoreアプリケーションにOpenTelemetryで可観測性を追加する開発者向けのスキルです。トレース、メトリクス、ログの相関、カスタム計装、コンテキスト伝搬を扱います。

主な機能

  1. 01トレース、メトリクス、ログのエクスポート設定
  2. 02ログとトレースコンテキストの相関
  3. 03カスタムActivityとメトリクスの作成
  4. 04分散コンテキスト伝搬の設定
  5. 05設定検証と一般的な問題の診断

活用シーン

  1. 01ASP.NET Coreへの分散トレーシング追加
  2. 02OTLPによる可観測性データのエクスポート
  3. 03業務処理へのカスタムスパンとメトリクスの追加
  4. 04サービス間のトレースコンテキスト伝搬の調査

インストール

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

$npx skills add https://github.com/dotnet/skills --skill configuring-opentelemetry-dotnet

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

詳細

.NET での OpenTelemetry の構成

使用する場面

  • ASP.NET Core アプリケーションに分散トレーシングを追加する場合
  • OpenTelemetry エクスポーターを設定する場合(主要なプロトコルは OTLP です。Jaeger はネイティブで OTLP を受け入れます。Prometheus での OTLP 取り込みには明示的なオプトインが必要です)
  • ビジネス処理用のカスタムメトリクスやトレーススパンを作成する場合
  • サービス間での分散トレースコンテキストの伝播をトラブルシューティングする場合

使用しない場面

  • ユーザーがアプリケーションレベルのログ記録のみを必要としている場合(ILogger、Serilog を使用してください)
  • ユーザーが Application Insights SDK を直接使用している場合(API が異なります)
  • ユーザーが商用ベンダーの独自 SDK による APM を必要としている場合

入力

入力必須説明
ASP.NET Core プロジェクトはい計装するアプリケーション
オブザーバビリティバックエンドいいえエクスポート先:OTLP コレクター、Aspire ダッシュボード、Jaeger(ネイティブで OTLP を受け入れます)

ワークフロー

ステップ 1:適切なパッケージをインストールする

OpenTelemetry の NuGet パッケージは多数あります。次のパッケージだけをインストールしてください。

# Core SDK + ASP.NET Core instrumentation + logging integration
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http

# Exporter
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol  # OTLP exporter for traces, metrics, AND logs

# Optional — dev/local debugging only (do NOT include in production deployments)
# dotnet add package OpenTelemetry.Exporter.Console

OpenTelemetry だけをインストールしないでください — 適切な DI 統合には OpenTelemetry.Extensions.Hosting が必要です。

オプション:追加の自動計装パッケージ

アプリケーションで使用しているライブラリに合致するパッケージのみをインストールしてください。

dotnet add package OpenTelemetry.Instrumentation.SqlClient           # SQL Server queries
dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore  # EF Core
dotnet add package OpenTelemetry.Instrumentation.GrpcNetClient       # gRPC calls
dotnet add package OpenTelemetry.Instrumentation.Runtime             # GC, thread pool metrics
ステップ 2:Program.cs ですべてのシグナルを構成する
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using OpenTelemetry.Metrics;
using OpenTelemetry.Logs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource
        .AddService(serviceName: builder.Environment.ApplicationName))
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation(options =>
        {
            // Filter out health check endpoints from traces
            options.Filter = httpContext =>
                !httpContext.Request.Path.StartsWithSegments("/healthz");
        })
        .AddHttpClientInstrumentation(options =>
        {
            options.RecordException = true;
        })
        // Optional: add SQL instrumentation if using SqlClient directly
        // .AddSqlClientInstrumentation(options =>
        // {
        //     options.SetDbStatementForText = true;
        //     options.RecordException = true;
        // })
        // Custom activity sources (must match ActivitySource names in your code)
        .AddSource("MyApp.Orders")
        .AddSource("MyApp.Payments")
        .AddSource("MyApp.Messaging"))
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        // Optional: .AddRuntimeInstrumentation() for GC and thread pool metrics
        //   (requires OpenTelemetry.Instrumentation.Runtime package)
        // Custom meters (must match Meter names in your code)
        .AddMeter("MyApp.Metrics"))
    .WithLogging(logging =>
    {
        logging.IncludeScopes = true;
        // logging.IncludeFormattedMessage = true;  // Enable if you need the formatted message string in log exports
    })
    // Single OTLP exporter for all signals — reads OTEL_EXPORTER_OTLP_ENDPOINT
    // env var (defaults to http://localhost:4317). Override via environment variable
    // or appsettings.json configuration.
    .UseOtlpExporter();
ステップ 3:ログとトレースの相関を理解する

ステップ 2 の .WithLogging() 呼び出しにより、ILogger が OpenTelemetry と統合されます。

  • 各ログエントリには、トレースとの相関に使う TraceId と SpanId が自動的に含まれます
  • .ConfigureResource() のサービスリソースは、ログにも自動的に伝播されます
  • UseOtlpExporter() はトレースやメトリクスとともにログにも適用されます
  • 追加のパッケージや個別の SetResourceBuilder 呼び出しは必要ありません
ステップ 4:ビジネス処理用のカスタムスパン(Activity)を作成する
using System.Diagnostics;
using Microsoft.Extensions.Logging;

public class OrderService
{
    // Create an ActivitySource matching what you registered in Step 2
    private static readonly ActivitySource ActivitySource = new("MyApp.Orders");
    private readonly ILogger<OrderService> _logger;

    public OrderService(ILogger<OrderService> logger) => _logger = logger;

    public async Task<Order> ProcessOrderAsync(CreateOrderRequest request)
    {
        // Start a new span
        using var activity = ActivitySource.StartActivity("ProcessOrder");

        // Add attributes (tags) to the span
        activity?.SetTag("order.customer_id", request.CustomerId);
        activity?.SetTag("order.item_count", request.Items.Count);

        try
        {
            // Child span for validation
            using (var validationActivity = ActivitySource.StartActivity("ValidateOrder"))
            {
                await ValidateOrderAsync(request);
                validationActivity?.SetTag("validation.result", "passed");
            }

            // Child span for payment
            using (var paymentActivity = ActivitySource.StartActivity("ProcessPayment",
                ActivityKind.Client))  // Client = outgoing call
            {
                paymentActivity?.SetTag("payment.method", request.PaymentMethod);
                await ProcessPaymentAsync(request);
            }

            var order = new Order { Id = Guid.NewGuid(), CustomerId = request.CustomerId, Status = "Completed" };

            activity?.SetTag("order.status", "completed");
            activity?.SetStatus(ActivityStatusCode.Ok);

            return order;
        }
        catch (Exception ex)
        {
            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            // Log via ILogger — OpenTelemetry captures this with trace correlation.
            // Prefer logging over activity.RecordException() as OTel is deprecating
            // span events for exception recording in favor of log-based exceptions.
            _logger.LogError(ex, "Order processing failed for customer {CustomerId}", request.CustomerId);
            throw;
        }
    }
}

重要:ActivitySource の名前は、構成内の AddSource("...") と一致している必要があります。 一致しないソースは通知されずに無視されます — これがデバッグで最もよくある問題です。

ステップ 5:カスタムメトリクスを作成する

IMeterFactory(DI 経由でインジェクション)を使って Meter を作成してください。これにより、適切なライフタイム管理とテスト容易性が確保されます。

using System.Diagnostics;
using System.Diagnostics.Metrics;

public class OrderMetrics
{
    private readonly Counter<long> _ordersProcessed;
    private readonly Histogram<double> _orderProcessingDuration;
    private readonly UpDownCounter<int> _activeOrders;

    public OrderMetrics(IMeterFactory meterFactory)
    {
        // Meter name must match AddMeter("...") in configuration
        var meter = meterFactory.Create("MyApp.Metrics");

        // Counter — use for things that only go up
        _ordersProcessed = meter.CreateCounter<long>(
            "orders.processed", "orders", "Total orders successfully processed");

        // Histogram — use for measuring distributions (latency, sizes)
        _orderProcessingDuration = meter.CreateHistogram<double>(
            "orders.processing_duration", "ms", "Time to process an order");

        // UpDownCounter — use for things that go up AND down
        _activeOrders = meter.CreateUpDownCounter<int>(
            "orders.active", "orders", "Currently processing orders");
    }

    public void RecordOrderProcessed(string region, double durationMs)
    {
        // Tags enable dimensional filtering (by region, status, etc.)
        var tags = new TagList
        {
            { "region", region },
            { "order.type", "standard" }
        };

        _ordersProcessed.Add(1, tags);
        _orderProcessingDuration.Record(durationMs, tags);
    }
}

OrderMetrics を DI に登録します。

builder.Services.AddSingleton<OrderMetrics>();
ステップ 6:分散シナリオ向けにコンテキスト伝播を構成する

AddHttpClientInstrumentation() を使用すると、HTTP 呼び出しのトレースコンテキストは自動的に伝播されます。HTTP 以外のシナリオでは、次のようにします。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using OpenTelemetry.Context.Propagation;

// ActivitySource should be static — register via .AddSource("MyApp.Messaging") in Step 2
private static readonly ActivitySource MessageSource = new("MyApp.Messaging");

// Manual context propagation (e.g., across message queues)
// On the SENDING side:
var propagator = Propagators.DefaultTextMapPropagator;
var activityContext = Activity.Current?.Context ?? default;
var context = new PropagationContext(activityContext, Baggage.Current);
var carrier = new Dictionary<string, string>();

propagator.Inject(context, carrier, (dict, key, value) => dict[key] = value);
// Send carrier dictionary as message headers

// On the RECEIVING side:
var parentContext = propagator.Extract(default, carrier,
    (dict, key) => dict.TryGetValue(key, out var value) ? new[] { value } : Array.Empty<string>());

Baggage.Current = parentContext.Baggage;
using var activity = MessageSource.StartActivity("ProcessMessage",
    ActivityKind.Consumer,
    parentContext.ActivityContext);  // Links to parent trace!

検証

  • オブザーバビリティバックエンド(Jaeger、Aspire ダッシュボードなど)にトレースが表示される
  • HTTP リクエストによって、正しい動詞、URL、ステータスコードを含むスパンが自動的に作成される
  • カスタム ActivitySource 名が AddSource() の登録と一致している
  • カスタム Meter 名が AddMeter() の登録と一致している
  • ログに相関用の TraceId と SpanId が含まれている
  • ヘルスチェックエンドポイントがトレースから除外されている
  • エラースパンに例外の詳細が表示される

よくある落とし穴

落とし穴解決策
ActivitySource.StartActivity が null を返すソース名がどの AddSource() とも一致していません — 名前を完全に一致させる必要があります
エクスポーターにトレースが表示されないOTLP エンドポイントを確認してください。gRPC はポート 4317、HTTP は 4318 を使用します
HTTP クライアントのスパンがないAddHttpClientInstrumentation() が登録されていることを確認してください。IHttpClientFactory/DI と new HttpClient() の両方で動作します(ライフタイム管理には IHttpClientFactory を使用してください)
カーディナリティの高いタグユーザー ID、リクエスト ID、UUID をメトリクスタグに使用しないでください — ストレージが急増します
OTLP の gRPC と HTTP の不一致デフォルトは gRPC(ポート 4317)です。コレクターが HTTP のみを受け入れる場合は、OtlpExportProtocol.HttpProtobuf を設定してください
Meter / ActivitySource のライフサイクルActivitySource は static にしてください。適切なライフタイム管理とテスト容易性のため、Meter は new Meter() ではなく DI の IMeterFactory で作成してください

コメント