mcpField
O
opentelemetry

opentelemetry

github.com/codewithmukeshクラウド・DevOps7933.0k 回インストール12 週間前に更新
リポジトリ

opentelemetry について

opentelemetryは、.NET開発者向けのOpenTelemetry実践ガイドです。トレース、メトリクス、ログの設定に加え、メトリクスのライフタイム、Activityのnull安全性、低カーディナリティのタグなどを説明します。

主な機能

  1. 01トレース、メトリクス、ログを一括設定
  2. 02OTLPによるテレメトリのエクスポート
  3. 03IMeterFactoryでメトリクスを作成
  4. 04ActivitySourceで独自のトレースを追加
  5. 05ローカル用Aspire Dashboardの設定

活用シーン

  1. 01.NETアプリで3種類のテレメトリを設定
  2. 02業務処理にメトリクスを追加
  3. 03独自スパンで分散処理を追跡
  4. 04ローカル開発中にテレメトリを確認

インストール

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

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

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

詳細

OpenTelemetry

基本原則

  1. 3つのシグナルを1か所で設定 — トレース、メトリクス、ログを単一の AddOpenTelemetry() 呼び出しで設定します。UseOtlpExporter() を使用すると、OTLP 互換の任意のバックエンドへ横断的にエクスポートできます。
  2. メトリクスには IMeterFactory を使用 — new で Meter インスタンスを作成しないでください。ファクトリーが DI を通じてライフタイムを管理し、リークを防ぎます。
  3. アクティビティの null 安全性 — リスナーがアタッチされていない場合、StartActivity() は null を返します。タグやイベントを設定するときは、必ず ?. を使用してください。
  4. コードより環境変数を優先 — OTEL_EXPORTER_OTLP_ENDPOINT と OTEL_SERVICE_NAME を使用すれば、コードを変更せずにデプロイ環境からテレメトリのルーティングを制御できます。
  5. メトリクスタグのカーディナリティを低く保つ — 計測器あたりのメトリクスタグの組み合わせを約 1000 未満に保ちます。ユーザー ID やリクエスト ID のようなカーディナリティの高いデータには、スパン属性またはログを使用してください。

パターン

3つのシグナルをすべて使用する完全なセットアップ
// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource
        .AddService(
            serviceName: builder.Environment.ApplicationName,
            serviceVersion: "1.0.0"))
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation()
        .AddSource("MyApp.Orders"))
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddRuntimeInstrumentation()
        .AddMeter("MyApp.Orders"))
    .WithLogging()             // no per-signal exporter here —
    .UseOtlpExporter();        // UseOtlpExporter covers all three signals

// UseOtlpExporter replaces per-signal AddOtlpExporter calls. Never combine
// the two — mixing them throws NotSupportedException (see Anti-patterns).

OTLP エンドポイントの既定値は http://localhost:4317(gRPC)です。次の方法で上書きできます。

OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
OTEL_SERVICE_NAME=MyApp.Api
IMeterFactory を使用したカスタムメトリクス

メトリクスクラスをシングルトンとして登録します。IMeterFactory は DI を通じて Meter の破棄を処理します。

public sealed class OrderMetrics
{
    private readonly Counter<int> _ordersCreated;
    private readonly Histogram<double> _orderDuration;
    private readonly UpDownCounter<int> _activeOrders;
    private readonly Gauge<double> _queueDepth;

    public OrderMetrics(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create("MyApp.Orders");

        _ordersCreated = meter.CreateCounter<int>(
            "myapp.orders.created", "{orders}", "Number of orders created");

        _orderDuration = meter.CreateHistogram<double>(
            "myapp.orders.duration", "s", "Order processing duration",
            advice: new InstrumentAdvice<double>
            {
                HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5, 10]
            });

        _activeOrders = meter.CreateUpDownCounter<int>(
            "myapp.orders.active", "{orders}", "Currently active orders");

        _queueDepth = meter.CreateGauge<double>(
            "myapp.orders.queue_depth", "{items}", "Current queue depth");
    }

    public void OrderCreated() => _ordersCreated.Add(1);
    public void RecordDuration(double seconds) => _orderDuration.Record(seconds);
    public void OrderStarted() => _activeOrders.Add(1);
    public void OrderCompleted() => _activeOrders.Add(-1);
    public void SetQueueDepth(double depth) => _queueDepth.Record(depth);
}

// Registration
builder.Services.AddSingleton<OrderMetrics>();
多次元メトリクスタグ

タグが3つ以下の場合、割り当ては発生しません。それより多い場合は TagList を使用してください。

// Allocation-free (3 or fewer tags)
_ordersCreated.Add(1,
    new KeyValuePair<string, object?>("order.type", "standard"),
    new KeyValuePair<string, object?>("payment.method", "credit_card"));

// 4+ tags — use TagList to avoid allocations
var tags = new TagList
{
    { "order.type", "standard" },
    { "payment.method", "credit_card" },
    { "region", "us-east" },
    { "priority", "high" }
};
_ordersCreated.Add(1, tags);
分散トレース用のカスタム ActivitySource
public sealed class OrderService(ILogger<OrderService> logger)
{
    private static readonly ActivitySource Source = new("MyApp.Orders");

    public async Task<Order> ProcessOrderAsync(CreateOrderRequest request, CancellationToken ct)
    {
        using var activity = Source.StartActivity("ProcessOrder", ActivityKind.Internal);
        activity?.SetTag("order.customer_id", request.CustomerId);

        try
        {
            await ValidateOrder(request, ct);
            activity?.AddEvent(new ActivityEvent("OrderValidated"));

            var order = await SaveOrder(request, ct);
            activity?.SetTag("order.id", order.Id.ToString());
            activity?.SetStatus(ActivityStatusCode.Ok);
            return order;
        }
        catch (Exception ex)
        {
            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            activity?.RecordException(ex);
            throw;
        }
    }
}

トレースビルダーで .AddSource("MyApp.Orders") を指定して、ソースを登録します。

ローカル開発用の Aspire Dashboard

Aspire によるオーケストレーションを使用せずに、スタンドアロンの Aspire Dashboard を実行します。

docker run --rm -it -p 18888:18888 -p 4317:18889 \
    mcr.microsoft.com/dotnet/aspire-dashboard:latest

次に、アプリをその Dashboard に接続します。

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317

Dashboard の UI は http://localhost:18888 にあります。

OTel を使用したソース生成ログ

最大限のパフォーマンスを得るには、[LoggerMessage] を使用します。これによりボックス化とメモリ割り当てがなくなります。

public partial class OrderService(ILogger<OrderService> logger)
{
    [LoggerMessage(Level = LogLevel.Information,
        Message = "Processing order {OrderId} for customer {CustomerId}")]
    partial void LogOrderProcessing(Guid orderId, Guid customerId);
}

OpenTelemetry のログには、Activity が現在のものである場合、TraceId と SpanId が自動的に含まれます。

アンチパターン

リクエストごとに Meter を作成しない
// BAD — new Meter per request causes memory leaks
public void HandleRequest()
{
    var meter = new Meter("MyApp");
    meter.CreateCounter<int>("requests").Add(1);
}

// GOOD — singleton via IMeterFactory
public class MyMetrics(IMeterFactory meterFactory)
{
    private readonly Counter<int> _requests =
        meterFactory.Create("MyApp").CreateCounter<int>("myapp.requests");
    public void RequestHandled() => _requests.Add(1);
}
Activity の null チェックを省略しない
// BAD — NullReferenceException when no listener is attached
using var activity = source.StartActivity("Work");
activity.SetTag("key", "value");

// GOOD — null-safe
activity?.SetTag("key", "value");
カーディナリティの高いメトリクスタグを使用しない
// BAD — unbounded cardinality causes memory explosion in collectors
_counter.Add(1, new("request.id", Guid.NewGuid().ToString()));
_counter.Add(1, new("user.id", userId));

// GOOD — low-cardinality dimensions only
_counter.Add(1, new("http.method", "GET"), new("http.status_code", 200));
UseOtlpExporter と AddOtlpExporter を併用しない
// BAD — throws NotSupportedException at runtime
builder.Services.AddOpenTelemetry()
    .UseOtlpExporter()
    .WithTracing(t => t.AddOtlpExporter());

// GOOD — use one approach
builder.Services.AddOpenTelemetry().UseOtlpExporter();
カスタムソースの登録を忘れない
// BAD — activities silently dropped (no listener registered)
var source = new ActivitySource("MyApp.Custom");
using var activity = source.StartActivity("Work"); // null!

// GOOD — register in the tracing builder
otel.WithTracing(t => t.AddSource("MyApp.Custom"));
otel.WithMetrics(m => m.AddMeter("MyApp.Custom"));

選択ガイド

シナリオ推奨事項
完全なオブザーバビリティのセットアップ3つのシグナルすべてを指定した AddOpenTelemetry() と UseOtlpExporter()
カスタムのビジネスメトリクスIMeterFactory とシングルトンのメトリクスクラス
カスタムのトレーススパンActivitySource と StartActivity()
ローカル開発用バックエンドスタンドアロンコンテナーの Aspire Dashboard
本番環境のバックエンドGrafana/Datadog などへの中継役としての OTel Collector
本番環境でのサンプリング10% の比率で OTEL_TRACES_SAMPLER=parentbased_traceidratio
高パフォーマンスのログ記録[LoggerMessage] ソースジェネレーター
メトリクスタグのカーディナリティ計測器あたり最大約 1000 の組み合わせ
環境設定OTEL_* 環境変数(appsettings.json 経由でも使用可能)

コメント