opentelemetry-net-instrumentation について
.NET の OpenTelemetry 計装を追加、レビュー、調整するための Claude Code スキルです。.NET 標準の診断 API を重視し、ライブラリの計装とアプリケーション側の収集・エクスポート設定を区別します。
主な機能
- 01トレース、メトリクス、ログの指針
- 02ActivitySource と Meter のパターン
- 03SDK、エクスポーター、サンプリングの設定
- 04耐障害性とセマンティック規約の確認
活用シーン
- 01OTel パッケージなしで .NET ライブラリを計装
- 02アプリケーションのテレメトリー出力設定
- 03スパンのエラーと属性のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill opentelemetry-net-instrumentationインストール後、Claude Code で/opentelemetry-net-instrumentationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metrics-and-instruments-reference.mdsdk-resources-and-logs-reference.mdtraces-and-propagation-reference.md詳細
OpenTelemetry .NET インストルメンテーションスキル
使用する場面
- .NET コードに OpenTelemetry インストルメンテーション(トレース、メトリクス、ログ)を追加するとき
- ActivitySources、Meters、または ILogger の使用を作成または変更するとき
- OpenTelemetry SDK、リソース、エクスポーター、またはサンプリングを設定するとき
- テレメトリの実装が仕様に準拠しているかレビューするとき
- インストルメンテーションのパフォーマンスを最適化するとき
- パブリック API の一部となるテレメトリ API を設計するとき
- サービス境界をまたぐコンテキスト伝搬を実装するとき
アーキテクチャ:.NET は他のプラットフォームとは異なります
重要:.NET の OpenTelemetry 実装は、他のプラットフォームとは根本的に異なります。.NET では、トレース、メトリクス、ロギングの API がフレームワーク自体に組み込まれています。 つまり、OTel は個別のインストルメンテーション API を提供しません。組み込みの .NET API を使用し、収集/エクスポート層として機能します。
.NET に組み込まれている 3 つの API(主要:依存関係ゼロ)
| シグナル | .NET フレームワーク API | 名前空間 |
|---|---|---|
| トレース | ActivitySource / Activity | System.Diagnostics |
| メトリクス | Meter / Counter<T> / Histogram<T> / etc. | System.Diagnostics.Metrics |
| ロギング | ILogger<T> | Microsoft.Extensions.Logging |
これらは、ライブラリの作成者がインストルメンテーションに使用すべき主要かつ唯一の APIです。 .NET ランタイムに含まれているため、NuGet パッケージは不要です。
OTel の収集/エクスポート層(二次的:アプリケーションのルートのみ)
OTel の NuGet パッケージは収集およびエクスポート層であり、アプリケーションのコンポジションルートにのみ追加します (ライブラリには追加しません)。
| パッケージ | 目的 | 追加する場面 |
|---|---|---|
OpenTelemetry.Extensions.Hosting | ASP.NET Core/汎用ホスト向けの DI 統合 | アプリケーションのみ |
OpenTelemetry.Exporter.Console | コンソールエクスポーター(開発/テスト用) | アプリケーションのみ |
OpenTelemetry.Exporter.OpenTelemetryProtocol | OTLP エクスポーター(本番用) | アプリケーションのみ |
OpenTelemetry.Exporter.Prometheus* | Prometheus メトリクスエンドポイント | アプリケーションのみ |
OpenTelemetry.Instrumentation.AspNetCore | ASP.NET Core リクエストの自動インストルメンテーション | アプリケーションのみ |
OpenTelemetry.Instrumentation.Http | HttpClient 呼び出しの自動インストルメンテーション | アプリケーションのみ |
OpenTelemetry.Instrumentation.SqlClient | SQL 呼び出しの自動インストルメンテーション | アプリケーションのみ |
パッケージ選択ガイド
OpenTelemetry の NuGet パッケージを追加する前に、必ずユーザーとトレードオフについて話し合ってください。
「これから OTel の NuGet パッケージを追加しようとしています。テレメトリを可観測性バックエンド(Jaeger、Prometheus、OTLP コレクター)に エクスポートする必要があるアプリケーションでしょうか? ライブラリを作成しているのであれば、必要な OTel パッケージはおそらくゼロです。
System.Diagnostics.ActivitySource/System.Diagnostics.Metrics.Meterを使用し、 エクスポートパイプラインは利用側のアプリケーションで設定してください。続行しますか?」
ライブラリの作成者:何も追加しないでください。System.Diagnostics.* と ILogger のみを使用します。
利用側のアプリケーションで SDK とエクスポーターを接続します。
アプリケーションの作成者:OpenTelemetry.Extensions.Hosting に加え、必要なエクスポーターとインストルメンテーションライブラリを追加します。設定方法の全体については、sdk-resources-and-logs-reference.md を参照してください。
ライブラリに OpenTelemetry.Api を決して追加しないでください。System.Diagnostics.* が API そのものです。
SDK の設定、リソース構成、エクスポーター、サンプリング、ログ統合については、sdk-resources-and-logs-reference.md を参照してください。
基本原則
まずは堅牢性
重要:診断/トレース/メトリクスのロジックで発生した例外が、アプリケーションの処理に影響することがあってはなりません。
- Activity インスタンスが null の場合があると想定します。Activity 拡張メソッドでは
activity?.ExtensionMethod()を使用し、それ以外では必ず null の Activity 参照に対処してください。 - すべてのインストルメンテーションコードに、適切な null チェックを入れてください。
API サーフェスに関する注意
- 出力されるテレメトリはすべて、パブリック API サーフェスの一部になります
- 変更には破壊的変更に関するガイドラインが適用されます
- テレメトリはデフォルトで出力してください(ユーザーは OpenTelemetry 拡張機能を通じて収集をオプトインします)
- 例外:カーディナリティの高いメトリクスのディメンションには、明示的なオプトインが必要な場合があります
標準への準拠
- 分散トレーシングの計装に関する Microsoft のベストプラクティスに従ってください
- OpenTelemetry のセマンティック規約に従ってください
- 属性値では、string、boolean、double(IEEE 754)、int64、byte 配列、およびこれらのプリミティブ型の同種配列がサポートされます。OTel AnyValue 仕様に従い、null/空の値も有効で意味を持つため、エクスポーターに保存して渡す必要があります。
- 属性のキーは null ではなく、空でない文字列である必要があります
トレース/スパン(Activity)
ActivitySource の設定
// ✅ CORRECT: Use ActivitySource, not DiagnosticSource
public class MyFeature
{
// Primary ActivitySource - name typically matches the component or NuGet package name
private static readonly ActivitySource ActivitySource = new("MyApp.MyComponent", "1.0.0");
// Specialized ActivitySource for opt-in scenarios
private static readonly ActivitySource DetailedActivitySource = new("MyApp.MyComponent.Detailed", "1.0.0");
}
ルール:
- 各コンポーネントでは、主要なアクティビティ用のプライマリ
ActivitySourceを定義します - 名前は通常、コンポーネントまたは NuGet パッケージ名と一致させます(例:
"MyCompany.MyLibrary") - ActivitySource のバージョンには SemVer を使用します
- 特殊なシナリオやオプトインのシナリオには、個別の
ActivitySourceを作成します。MyCompany.MyLibraryやMyCompany.MyLibrary.Detailedのような階層的なソース名を使用すると、利用側のアプリケーションはAddSource(...)を通じて必要なソースだけをサブスクライブでき、バックエンドはインストルメンテーションスコープでフィルターできます
Activity の作成
// ✅ Check HasListeners, null-check, then guard expensive work behind IsAllDataRequested
if (ActivitySource.HasListeners())
{
using var activity = ActivitySource.StartActivity("ProcessItem", ActivityKind.Internal);
if (activity != null && activity.IsAllDataRequested)
{
activity.DisplayName = "Processing order #12345";
activity.SetTag("app.item_id", itemId);
activity.SetTag("app.item_type", itemType);
}
}
// ❌ WRONG: Don't start activities in fire-and-forget tasks where the
// using scope ends before the async work completes (AsyncLocal context is lost)
async Task HelperAsync()
{
using var activity = ActivitySource.StartActivity("Helper");
_ = Task.Run(() => DoWorkAsync()); // ❌ activity disposed before task completes
}
ルール:
- 作成前に
ActivitySource.HasListeners()を確認します(割り当てゼロの高速パス) - 作成後は必ず Activity が null でないことを確認します(リスナーによってフィルターされたり、サンプリング対象外になったりする場合があります)
- 非同期ヘルパーメソッド内で Activity を開始しないでください(
Activity.CurrentはAsyncLocalを使用します) - コストの高いタグ計算は
activity.IsAllDataRequestedでガードします - W3C TraceContext を使用します。.NET Core 3.0 以降/.NET 5 以降ではデフォルトで使用されます。古い TFM や .NET Framework アプリでは、起動時に
Activity.DefaultIdFormat = ActivityIdFormat.W3Cを設定し、Activity.ForceDefaultIdFormat = trueを使用して階層型の親を上書きできます。
Activity の命名
// ✅ Unique operation name, friendly display name (null-check before accessing)
using var activity = ActivitySource.StartActivity(
name: "ProcessItem", // Unique, identifies class of spans
kind: ActivityKind.Internal
);
if (activity != null)
activity.DisplayName = "Processing order #12345"; // User-friendly, can be specific
// ❌ WRONG: Don't include runtime data in operation name
using var badActivity = ActivitySource.StartActivity($"Process_{itemId}"); // ❌
ルール:
- 各スパン型には、一意の
OperationNameを指定します(統計的に重要なスパンのクラスを識別します) - オペレーション名に実行時データを含めてはいけません(コンパイル時/構成時の情報のみを使用します)
- 個別の内容には、人が読みやすい
DisplayNameを使用します - OpenTelemetry のスパン命名規約に従ってください
SpanKind の選択
分散トレーシングにおけるスパンの役割を明確にするため、適切な ActivityKind を選択します。
ActivityKind | OTel SpanKind | 使用する場面 |
|---|---|---|
Internal | INTERNAL | デフォルト — リモート境界を越えないプロセス内のオペレーション |
Server | SERVER | 受信したリクエスト/レスポンス呼び出しの処理(HTTP サーバー、gRPC サーバー、RPC サーバー) |
Client | CLIENT | 発信するリクエスト/レスポンス呼び出しの実行(HTTP クライアント、データベースクライアント、RPC 呼び出し) |
Producer | PRODUCER | 後続処理のエンキュー/公開(メッセージキューへの公開、イベントの発行、ジョブのエンキュー) |
Consumer | CONSUMER | 後続処理のデキュー/処理(メッセージキューからの受信、イベント処理、ジョブのデキュー) |
ルール:
- 1 つのスパンで複数の目的を担うべきではありません
SpanContextをリクエストに注入する前に、発信側のスパンを作成します。先に注入すると、親のコンテキストが伝播してしまい、発信側のスパンがぶら下がった状態になります(下流の呼び出しとの接続がありません)。- SpanKind の詳細なガイダンスと例については、traces-and-propagation-reference.mdを参照してください
スパン属性(タグ)
// ✅ Application code: use your own namespace
activity?.SetTag("myapp.order_id", orderId);
activity?.SetTag("myapp.payment.status", "confirmed");
// ✅ Manual infrastructure instrumentation: use semantic conventions
// activity?.SetTag("db.system.name", "postgresql"); // custom database client
// activity?.SetTag("http.request.method", "GET"); // custom HTTP transport
// Values can be strings, numbers, booleans, or homogeneous arrays
activity?.SetTag("app.item_count", 42);
activity?.SetTag("app.related_ids", new int[] { 1, 2, 3 });
// ❌ WRONG: PascalCase, hyphen delimiter, plural, or unrelated namespace
activity?.SetTag("MyApp.OrderId", orderId); // ❌ Wrong case
activity?.SetTag("myapp.order-id", orderId); // ❌ Wrong delimiter
ルール:
- コンポーネントに対応する名前空間プレフィックスを使用します:
myapp.*、myapp.db.* - すべて小文字にし、区切り文字にはアンダースコア(
_)を使用し、単数形にします - 属性値には、文字列、ブール値、double、int64、バイト配列、同種の配列を使用できます(AnyValue の仕様に基づき、null と空の値も有効です)
- ビジネス/ドメイン属性:独自の名前空間(
myapp.*)を使用します。 - 手動で計装する HTTP、データベース、メッセージング、RPC の概念:セマンティック規約を使用します。自動計装によってすでに出力されている属性を重複させないでください。カスタム属性のプレフィックスに OTel の名前空間を使用しないでください。
アクティビティのステータスとエラー
try
{
await ProcessItemAsync(); // ✅ success: leave status Unset, do not call SetStatus(Ok) — see rules below
}
catch (Exception ex)
{
if (activity != null)
{
activity.SetStatus(ActivityStatusCode.Error, ex.Message); // modern API
activity.SetTag("error.type", ex.GetType().FullName);
}
throw;
}
ルール(エラーの記録およびステータス設定 API の仕様に基づく):
- 成功時はスパンのステータスを Unset のままにします。
SetStatus(ActivityStatusCode.Ok)は呼び出さないでください。 Okは計装ライブラリではなく、アプリケーションコードで使用します。トレース API の仕様では、「計装ライブラリは、明示的に設定されていない限り、ステータスコードをOkに設定するべきではありません(中略)。アプリケーション開発者と運用担当者は、ステータスコードをOkに設定できます」とされています。通常は、ライブラリが報告したErrorを、実際の失敗ではないと判断して上書きする場合(例:頻発する 404 を抑制する場合)に使用します。一度設定すると、Okが最終ステータスとなり、それ以降の呼び出しは無視されます。」- 失敗時:
ActivityStatusCode.Errorを設定するべきです。error.typeタグを設定するべきです。ステータスの説明には例外メッセージを設定するべきです。 SetStatusを使用します。従来のotel.status_code/otel.status_descriptionタグは不要になりました。- 再試行または処理によって操作が正常に完了したエラーは記録しないでください。
Errorステータスとerror.typeは、回復した操作ではなく、失敗した操作を示すものです。
例外の記録 — Span イベントの送信を続け、ログはオプトインで追加する
exceptions-spans — Activity.AddEvent(new ActivityEvent("exception", ...)) の背後にある規約 — は、exceptions-logs を優先するため Deprecated ステータスを持っていますが、.NET エコシステムはまだ追随していません。仕様の OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN オプトインを実装している OpenTelemetry.* .NET パッケージは存在せず(OpenTelemetry.Api 1.17.0 で確認済み)、OpenTelemetry.Instrumentation.AspNetCore 1.17.0 自体も依然として Activity.AddException、すなわち Span イベントを介して例外を記録しています。つまり、一般的な .NET ユーザーが今日セットアップしているエクスポーター、バックエンド、ダッシュボードは、ログベースの例外ではなく、例外 Span イベントを読み取るように構築されています。Span イベントをデフォルトとして実装し続けてください — 基盤となる仕様ドキュメントが Deprecated とマークされているからといってそれらを削除すると、今日のツール群を使い続けている人々の例外の可視性がサイレントに損なわれます。より新しいログベースのパスは、Span イベントから移行する計装について仕様自身の移行ガイダンスが説明しているとおりに、オプトインとしてその上に重ねてください:
// Mirrors the spec's OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN values: unset/anything else → spans only (today's default), "logs/dup" → both, "logs" → logs only.
private static readonly string? ExceptionSignalOptIn = Environment.GetEnvironmentVariable("OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN");
private static readonly bool EmitSpanEvents = ExceptionSignalOptIn != "logs";
private static readonly bool EmitLogs = ExceptionSignalOptIn is "logs" or "logs/dup";
try
{
await ProcessItemAsync();
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.SetTag("error.type", ex.GetType().FullName);
if (EmitSpanEvents) // ✅ default — what current .NET tooling and dashboards actually consume today
{
activity?.AddEvent(new ActivityEvent("exception", tags: new ActivityTagsCollection
{
["exception.type"] = ex.GetType().FullName,
["exception.message"] = ex.Message,
["exception.stacktrace"] = ex.ToString()
}));
}
if (EmitLogs) // opt-in — the spec's forward direction; pass the exception instance while `activity` is still Activity.Current so the SDK derives trace_id/span_id and exception.type/message/stacktrace from it
{
logger.LogError(ex, "Item processing failed");
}
throw;
}
ルール:
- Span イベントに
exception.escapedを設定しないでください — これは完全に非推奨です:「ハンドルされ、Span のスコープから脱出しない例外を記録することはもはや推奨されません。」 OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN(logs/logs/dup)をサポートし、準備ができた利用者がログまたは二重送信をオプトインできるようにしてください。ただしデフォルトは Span のみとします — これは仕様自身の移行ガイダンスであり、任意の便利機能ではなく、まさに誰も準備ができる前に一方のシグナルを他方より優先して選ばなくて済むように存在しています。- ログを送信する場合(オプトインまたは二重送信)、
exceptions-logsに従って重大度を選択してください:ハンドルされない例外(特にSERVER/CONSUMERSpan 上)にはERROR、呼び出し元によってハンドルされることが想定される例外(特にCLIENT/PRODUCERSpan 上)にはWARN、実際の問題を示さない例外(例:クライアント側でキャンセルされたリクエスト)にはDEBUG、例外がアプリケーションのシャットダウンを引き起こす場合にのみFATAL。 - ログ呼び出しは、必ずそれが関連付けられる Span がまだカレントである間に行われなければなりません(MUST) — 仕様は「同じ操作に対して Span も記録する計装によって発行される例外イベントは、対応する Span コンテキストに関連付けられなければならない」と要求しています。
usingアクティビティスコープが終了した後に、切り離されたエラー報告コールバックからログを記録すると、サイレントに誤った Span に関連付けられるか、まったく関連付けられません。以下の アクティビティへのアクセス を参照してください。 logsのみへの移行は、安定したメジャーバージョンで少なくとも 6 か月間二重送信を維持し(仕様自身の最小期間)、かつ実際の利用者がログベースの例外を読み取っていることを確認した後にのみ行ってください — 固定されたタイムラインだけに基づいて行わないでください。
完全なパターンについては traces-and-propagation-reference を参照してください。コアにログ依存関係を持たないライブラリが、正しい Span がアクティブな間に別の OTel 統合パッケージが例外の詳細をキャプチャできるよう、どのようにコールバックを公開できるかも含まれています。
アクティビティへのアクセス
var current = Activity.Current; // ❌ may be a user-created ambient span
using var ownedActivity = ActivitySource.StartActivity("MyOperation"); // ✅ captured reference
ownedActivity?.SetTag("myapp.key", value);
ルール:自分が所有する Span に対して Activity.Current に依存しないでください。ユーザーコードが AsyncLocal を介してそれを置き換える可能性があります。キャプチャした Activity は存続中のみ渡すか保存してください。伝播の同一性には ActivityContext を保存してください。
Span リンク
リンクは、因果関係があるものの直接の親子関係にはない他の Span に Span を接続します — バッチ処理、scatter/gather、トレース境界の横断などです。
var links = new List<ActivityLink>
{
new(activityContext1),
new(activityContext2),
};
var activity = ActivitySource.StartActivity(
ActivityKind.Internal, name: "batch-process", links: links);
バッチ処理、scatter/gather、トレース境界の横断を含む完全なリンクパターンについては traces-and-propagation-reference.md を参照してください。
コンテキストの伝搬
分散トレースでは、W3C の traceparent ヘッダーを使用して、プロセス境界(HTTP 呼び出し、メッセージキューなど)を越えてトレースコンテキストを伝搬させる必要があります。.NET では、これを DistributedContextPropagator が処理します。OTel SDK では、既定で W3C TraceContext の伝搬が設定されます。
伝搬パターン、カスタムプロパゲーター、標準以外のトランスポートでの手動の注入/抽出については、traces-and-propagation-reference.md を参照してください。
メトリクス
Meter と Metrics クラスのセットアップ
public sealed class OrderProcessingMetrics : IDisposable
{
private readonly Meter meter = new("MyApp.OrderProcessing", "1.0.0");
private readonly Histogram<double> processingDuration =
meter.CreateHistogram<double>("myapp.order.processing.duration", unit: "s");
private readonly Counter<long> itemsProcessed =
meter.CreateCounter<long>("myapp.order.processing.count", unit: "{order}");
public void Dispose() => meter.Dispose();
}
命名規則(OTel のセマンティック規約に従ってください):
- 単数形の名前を使用し、階層化します:
myapp.order.processing.duration - 単位(s、ms、{item}、{connection})を定義し、技術的な接尾辞(
_counter、_histogram)は避けます - 採用が実証されるまでは、1.0.0 未満のバージョンから始めます
計測器タイプの概要
.NET には 7 種類のメトリクス計測器があります。測定対象に合ったものを選んでください。
| 計測器 | .NET API | 動作 | 一般的な用途 |
|---|---|---|---|
| Counter | CreateCounter<T> | 単調増加 | リクエスト数、エラー数 |
| UpDownCounter | CreateUpDownCounter<T> | 増減 | キューサイズ、アクティブな接続数 |
| Histogram | CreateHistogram<T> | 値の分布 | 処理時間、レスポンスサイズ |
| Gauge | CreateGauge<T> (.NET 9+) | 同期的な瞬時値 | 現在の測定値の記録 |
| ObservableCounter | CreateObservableCounter<T> | 非同期コールバック、単調増加 | 定期的にポーリングされる合計値 |
| ObservableGauge | CreateObservableGauge<T> | 非同期コールバック、非単調 | CPU/メモリ使用量 |
| ObservableUpDownCounter | CreateObservableUpDownCounter<T> | 非同期コールバック、双方向 | 優先度別のアクティブなタスク数 |
作成/記録の例やオブザーバブルコールバックのパターンについては、metrics-and-instruments-reference.md を参照してください。
メトリクス記録メソッドの命名
// ✅ Action/outcome-based naming, separate methods per outcome
public void OrderProcessingSucceeded(string orderType, TimeSpan duration) { /* Record */ }
public void OrderProcessingFailed(string orderType, Exception ex, TimeSpan duration) { /* Record */ }
public void ConnectionOpened() => connectionsOpen.Add(1);
public void ConnectionClosed() => connectionsOpen.Add(-1);
// ❌ WRONG: Name after metric, confusing signature
public void RecordOrderProcessingDuration(...) { } // ❌ don't name after metric
public void RecordError(bool succeeded, Exception? ex) { } // ❌ confusing signature
規則:
- メトリクス名(
RecordXxx)ではなく、アクション/結果に基づいて命名します(OrderProcessingSucceeded) - 結果ごとにメソッドを分けます(ブール値フラグと省略可能な例外の組み合わせは避けます)
- 状態変化にはイベントベースの命名を使用します:
ConnectionOpened()、ItemQueued()
メトリクスのディメンション
// ✅ Low-cardinality, predefined dimensions
processingDuration.Record(duration.TotalSeconds,
new KeyValuePair<string, object?>("myapp.order_type", orderType), // bounded set
new KeyValuePair<string, object?>("outcome", "success")); // bounded set
// ❌ High-cardinality: unbounded values cause cardinality explosion
failureCount.Add(1, new KeyValuePair<string, object?>("order_id", orderId)); // ❌ unbounded
規則:
- ディメンションは事前に定義し、カーディナリティを低く抑える必要があります(アイテムの種類、キュー名、結果)
- 上限のない値は避けます(値が一意であるたびに新しい時系列行が作成され、カーディナリティが爆発的に増加します)
- カーディナリティの高いディメンションは、オプトイン設定にする必要があります
- コンポーネント間で一貫した名前を使用します:
myapp.regionはどこでも同じ意味になります - トレースとの相関には、エグザンプラーを有効にできます(ディメンション経由ではありません)
パフォーマンス要件
計装は既定で低コストでなければなりません。オーバーヘッドを最小限に抑えるため、以下の規則に従ってください。
アロケーションゼロの高速パス
// ✅ CORRECT: Guard with cheap checks
if (ActivitySource.HasListeners())
{
using var activity = ActivitySource.StartActivity("Operation");
// ... expensive work
}
// ✅ CORRECT: Use TagList (struct) for metrics
var tags = new TagList
{
{ "myapp.order_type", orderType },
{ "outcome", "success" }
};
counter.Add(1, tags);
計測時間
// ✅ Timestamp math (no allocation)
var startTime = Stopwatch.GetTimestamp();
try { await ProcessAsync(); }
finally { var duration = Stopwatch.GetElapsedTime(startTime); metrics.OrderProcessingSucceeded(orderType, duration); }
// ❌ Allocates: Stopwatch.StartNew() or IDisposable timing wrappers
隠れたアロケーションを避ける
// ❌ Allocates: string interpolation without IsAllDataRequested guard
activity?.SetTag("item", $"Processing {itemId}"); // ❌
// ✅ Guard expensive work behind IsAllDataRequested
if (activity?.IsAllDataRequested == true)
activity.SetTag("item", $"Processing {itemId}");
規則:
Stopwatch.StartNew()は使用しません(Stopwatch.GetTimestamp()/GetElapsedTimeを使用します)- 配列/辞書よりも
TagList(構造体)を優先します - ガードなしで、ホットパスに LINQ、文字列補間、非同期ステートマシンを使用しません
テスト要件
スパンのテスト
[Test]
public async Task Should_create_processing_span_with_correct_parent()
{
// Arrange
using var parent = new Activity("Parent").Start();
// Act
await handler.Handle(item);
// Assert
var processingSpan = recordedActivities.Single(a => a.OperationName == "ProcessItem");
Assert.AreEqual(parent.Id, processingSpan.ParentId);
Assert.AreEqual("myapp.item_type", processingSpan.Tags.First().Key);
}
[Test]
public void Should_not_introduce_breaking_changes_to_span_names()
{
// Ensures string values in span names are under test
Assert.AreEqual("ProcessItem", MyFeature.SpanName);
}
規則:
- どのスパンにアクティビティが接続されるかをテストします
- 文字列値(スパン名、タグ名)をテストし、破壊的変更を防ぎます
- テレメトリはパブリック API の一部であることを忘れないでください
バージョニング
- テレメトリのバージョニングはパッケージのバージョニングから切り離します
- SemVer のセマンティクスを使用します
- トレースとメトリクスにはそれぞれ別のバージョンを使用します(独立して進化します)
- 採用/有用性が実証されるまでは、1.0.0 未満のバージョンから始めます
private static readonly ActivitySource ActivitySource = new("MyApp.MyComponent", "0.9.0");
private readonly Meter meter = new("MyApp.MyComponent", "0.8.0");
ログ
.NET のログは、組み込みの ILogger API を通じて OpenTelemetry と統合されます。OTel SDK は、ログの収集、処理、エクスポートを行うために、ロギングビルダーに AddOpenTelemetry() を提供します。ログレコードは、TraceId/SpanId を介してトレースと自動的に関連付けられます。
相関、機密情報のマスキング、構造化ログ、重大度によるフィルタリングなど、ログ統合の全パターンについては、sdk-resources-and-logs-reference.md を参照してください。
リファレンスファイル
- traces-and-propagation-reference.md:SpanKind の詳細と例、Span Links(一括処理、散布/収集、トレース境界)、コンテキスト伝搬(W3C traceparent、DistributedContextPropagator、カスタムプロパゲーター)、Baggage、および最新の例外記録パターン全体。
- metrics-and-instruments-reference.md:7 種類すべてのメトリックインストゥルメントと、作成/記録コードおよび使用場面のガイダンス、オブザーバブルインストゥルメントのコールバックパターン、ディメンションの詳細、エグゼンプラー、集約のデフォルト、単位。
- sdk-resources-and-logs-reference.md:SDK の初期化(ASP.NET Core + Console)、リソースの設定(ResourceBuilder、AddService、AddDetector、カスタム検出器)、エクスポーター(OTLP、Console、Jaeger、Zipkin、Prometheus)、インストゥルメンテーションライブラリ、サンプリング(組み込み、カスタム、環境変数、ヘッドベース/テールベース)、ログ統合(ILogger、相関、機密情報のマスキング、構造化ログ)、環境変数による設定。