セマンティック規約の書き方
この記事は英語の原文を日本語に翻訳したものです。原文: https://opentelemetry.io/docs/specs/semconv/how-to-write-conventions/
翻訳元: open-telemetry/semantic-conventions v1.44.0(コミット e10a930)
セマンティック規約の書き方
ステータス: Development
この文書では、新しい領域のセマンティック規約を定義する、または既存の規約に大きな変更を加える際の要件・推奨事項・ベストプラクティスについて説明します。
新しい規約の定義
- 新しい規約には、コードオーナーのグループが必須です(MUST)。詳細はプロジェクト管理を参照してください。
- 新しい規約はYAMLファイルで定義すべきです(SHOULD)。詳細はセマンティック規約のYAMLモデルを参照してください。
- 新しい規約は
developmentの安定性レベルで定義すべきです(SHOULD)。 - 新しい規約は、テレメトリーシグナルの定義(スパン、メトリクス、イベント、リソース、プロファイル)を含めるべきであり(SHOULD)、新しい属性定義を含めてもかまいません(MAY)。
ベストプラクティス
[!NOTE]
この節には非規範的なガイダンスが含まれています。
規約の主要な用途を定義することでセマンティック規約の作成にどう取り組むかについては、T-Shaped Signalsを参照してください。
プロトタイピング
提案する規約は、1つ以上の計装でプロトタイプ化することが強く推奨されます。それにより次のことができます。
- 提案するテレメトリーと属性を収集することの実現可能性を検証し、その情報が入手可能で、かつ妥当なオーバーヘッドで収集できることを確認する。
- 提案する用語が、規約の対象となる多様なライブラリや技術の全体にわたって適用できることを確認する。
- 計装の実装者に対して、いつ・どのように属性を収集し、テレメトリーを記録すべきかについて実用的なガイダンスを提供する。
- 新規または更新されたテレメトリーが他の計装レイヤーとどのように統合されるかを評価し、ギャップや重複、エンドユーザー体験を改善する機会を特定する。
属性の定義
可能な限り既存の属性を再利用してください。類似の領域については既存の規約を確認し、汎用の属性も確認してください。 セマンティック規約の作成者は、異なる名前空間の属性を使うことが推奨されます。
新しい属性の追加を検討するのは、次のすべてが当てはまる場合です。
- テレメトリーを強化することで、エンドユーザーに明確な利点をもたらす。
- セマンティック規約においてスパン、メトリクス、イベント、リソース、その他のテレメトリーシグナルを定義する際に、その属性を使う明確な計画がある。
- 計装がこれらの属性をどのように使うかについて、明確な計画がある。
セマンティック規約のメンテナーは、利点や使用事例がまだ明確でない場合、新しい属性の追加を拒否することがあります。
新しい属性を定義する際は次のようにします。
命名ガイダンスに従う。
その属性が何を表すかを明確に説明する、記述的な
briefとnoteの節を用意する。その属性が外部で文書化されている一般的な概念を表す場合は、関連するリンクを含める。例えば、RFCや他の標準で定義されている概念には常にリンクします。
その属性の値にPII(個人を特定できる情報)や他の機密情報が含まれる可能性がある場合は、
noteでその点を明示的に述べます。次のような警告を含めます。
- id: user.full_name ... note: | ... > [!WARNING] > > This attribute contains sensitive (PII) information.
適切な属性の型を使用する。
- 値が妥当な範囲の(オープンまたはクローズドな)取りうる値の集合を持つ場合は、enumとして定義します。
- 値がタイムスタンプの場合は、ISO 8601形式の文字列として記録します。
- プリミティブ型の配列には配列型を使い、配列を単一の文字列として記録することは避けます。
- 配列は、すべての要素が同じ型で同じ概念を表す、均質なものであるべきです。例えば次のとおりです。
- 緯度と経度は、それぞれ異なる概念を表すため、単一の配列にまとめるのではなく、別々の属性(
geo.latとgeo.lon)として定義すべきです。
- 緯度と経度は、それぞれ異なる概念を表すため、単一の配列にまとめるのではなく、別々の属性(
- 動的な名前を持つ属性を定義するには、テンプレート型を使用します(名前のうち動的であるべき部分は最後のセグメントのみです)。これは、HTTPヘッダーのようなユーザー定義のキーバリューペアを取得する際に有用です。
- 複雑な値は、可能な限りフラットな属性の集合として表現します。
複雑または構造化された属性(標準属性の集合に列挙されていないもの)は、イベントとスパン(
)でのみ参照できる可能性があります。
セマンティック規約の作成者は、バックエンドが複雑な属性の個々のプロパティをインデックス化しないこと、そうしたプロパティに対するクエリや集計が非効率で複雑であること、複雑な属性を報告するとパフォーマンスオーバーヘッドが高くなることを前提とすべきです。
新しい属性は
developmentの安定性で定義する。現実的な例を提供する。
1KBを超える文字列や1,000要素を超える配列など、潜在的に無制限になりうる値を持つ属性は定義しないようにします。そのような値は、代わりにログまたはイベントの本体に記録すべきです。
属性の適用範囲と、それが将来どのように発展しうるかを検討してください。
狭い用途のために属性を定義する際は、より広い用途になりうる可能性を考慮します。 例えば、あるシステム固有の属性を作成する場合、同じ領域の他のシステムが将来同様の属性を必要とするかどうかを評価します。
同様に、成功・失敗を示す単純なブールフラグを定義するのではなく、
foo.status_code属性のように追加の詳細を含められる、より拡張性のあるアプローチを検討します。複数の領域やシステムにわたって適用可能な広い属性を定義する際は、業界における既存の標準や広く受け入れられているベストプラクティスを確認します。 確立された標準に基づかない汎用的な属性の作成は避けます。
[!NOTE]
データベースやクラウドプロバイダーなど、複数の実装やシステムが存在する領域の規約を定義する場合、汎用的すぎず、かつ汎用性が不足しないバランスを見つけるには時間がかかることがあります。
まずは実験的な規約から始め、それが多様なプロバイダー・システム・ライブラリにどのように適用されるかを文書化し、計装をプロトタイプ化してください。
エンドユーザー体験を、指針となる主要な原則とすべきです。
その属性がその領域の汎用的なメトリクスで使われることが想定される場合は、共通属性の導入を検討します。
例えば、多くのメッセージングシステムには、キューやトピックのような概念があります。 キューやトピックの名前は、レイテンシーやスループットのメトリクスにとって重要であり、メッセージフローをデバッグ・可視化するためのスパンにおいても同様に重要です。 これは、任意の種類のメッセージング宛先を表す汎用属性が必要であることを示しています。
その属性が狭い範囲のシナリオでのみ有用であるか、特定のシステムのメトリクス・スパン・イベントに固有のものである場合は、おそらく汎用化する必要はありません。
enum属性メンバーの定義
enum属性は、一般的に次の3つの主要なカテゴリーに分類されます。
完全なenumは、取りうるすべての値を文書化します。例えばcpu.modeは、既知のすべてのCPUモードを対象とします。system.cpu.timeのようなメトリクスは、すべてのモードが定義されていることに依存します。作成者は既知のすべての値を事前に文書化すべきですが、新しいOSやCPUアーキテクチャをサポートするために後から値が追加されることもあります。
error.typeのようなオープンなenumは、規約や計装が独自の適用可能な値を定義できるようにします。
システム識別子enumは、システム、プロジェクト、プロバイダー、製品、プロトコルを指定します。例えばdb.system.nameには、mongodbやmysqlといったデータベース名が含まれます。
システム識別子enumは、テレメトリーシグナルを区別するのに役立ちます。MongoDBとMySQLはどちらも一般的なデータベース規約に従いますが、db.collection.nameのような属性に異なる値を設定し、システム固有の属性も持ちます。それぞれのシステムには、独自のスパン定義と文書があります。
MongoDBとMySQLを例として参照し、命名のガイダンスについてはシステム固有の命名を確認してください。
[!IMPORTANT] システム識別子enumは、あらゆるシステム・コンポーネント・技術を列挙する必要はありません。
enum属性を使うOpenTelemetryの計装は、安定版のアーティファクトをリリースする前にその値を文書化すべきであり(SHOULD)、未安定なアーティファクトやフィーチャーフラグの背後では、未文書化の値をサポートしてもかまいません(MAY)。
新しいシステム識別子を定義するのは、その規約がそのシステムにどのように適用されるかを合わせて文書化する場合に限ります。例えば新しいdb.system.nameの値を追加する際は、そのデータベースに対して汎用属性がどのように機能するかを示す文書とスパン定義を作成してください。
スパンの定義
スパンは、トレース内の特定の操作の個々の実行を表します。
スパンを定義すべき場合は次のとおりです。
- 対応する操作が、オブザーバビリティの要件にとって重要である。
- その操作に継続時間がある。
例えば、1つ以上のネットワーク呼び出しを伴う操作についてはスパンを定義します。
[!NOTE]
既知の例外: messagingの
createスパンは、ローカル呼び出しに対して定義されています。これは、メッセージのバッチを発行する際に、各メッセージが一意のコンテキストを持ち、個別にエンドツーエンドでトレースできるようにするために必要です。
スパンを定義すべきでない場合は次のとおりです。
- 時点的な発生事象の場合は、代わりにイベントを使います。
- シリアライズやデシリアライズのような、プロセス外呼び出しを伴わない短い操作の場合。
- 非常に似た操作を捉える既存のスパン定義がある場合。例えば、DBクライアントのスパンは、ORMやDBドライバーの視点からのDBクエリ実行を表します。両方のレイヤーに計装を入れることはできますが、重複を減らすために内側のレイヤーは抑制される場合があります。
[!IMPORTANT]
スパン定義には、次のものを一緒に用意するのが一般的な慣行です。
- 同じ操作の継続時間を計測するメトリクス。
- その操作が正常に完了できなかった場合の例外を記録するイベント。
例えば、
http.client.request.durationメトリクスとhttp.client.request.exceptionイベントは、対応するHTTPクライアントのスパンと一緒に記録されます。
スパン定義では、その表す操作、命名パターン、スパンのステータスを設定する際の考慮事項、スパン種別、適用可能な属性の一覧を説明すべきです。
どのような操作を表すスパンか
操作の範囲と境界を定義してください。
- スパンがいつ開始し、いつ終了するか。
- そのスパンがクライアント呼び出しを表す場合、論理呼び出し(APIの呼び出し元から見たもの)と物理呼び出し(試行単位)のどちらを捉えているかを指定します。
- 異なる操作には異なるスパンを定義します。例えば、スパンの種別が異なる場合や、属性の集合が大きく異なる場合です。 例えば、HTTPクライアントとHTTPサーバーのスパンは2つの独立した定義です。メッセージングの発行と受信も異なるスパン種別です。
命名パターン
スパン名は低カーディナリティでなければならず(MUST)、その操作に対して妥当なグルーピングを提供すべきです(SHOULD)。詳細はスパン名のガイドラインを参照してください。
スパン名は通常
{action} {target}というパターンに従います。例えばsend orders_queueです。スパン名には、スパン属性として利用可能な情報のみを含めるべきです(SHOULD)。つまり、
{action}と{target}は通常、属性としても利用可能であり、その操作を記述するメトリクスでも使われます。静的なテキストはスパン名に含めるべきではありませんが(SHOULD NOT)、フォールバックとしては使用できます。 例えば、HTTPサーバーのスパン名では、
HTTP GET /orders/{id}ではなくGET /orders/{id}を使います。スパン名で使われる属性の一部が利用できない場合や、エッジケースで問題になりうる場合(例えば高カーディナリティになる場合)に備えて、フォールバック値を提供してください。
スパン名が長くなりすぎる可能性がある場合は、制限と切り詰め戦略を定義してください(例えば、DBの規約では255文字の制限を定義しています)。
ステータス
その操作にとって何がエラーに該当するかを定義してください。
特別な考慮事項がなければ、エラーの記録の文書を参照してください。
条件によっては、エラーか非エラーかを明確に分類できないことがあります(キャンセルやHTTP 404など)。厳密な要件を課すことは避け、計装が追加のコンテキストを活用して、より正確なステータスを提供できるようにしてください。
種別
すべてのスパン定義には、特定のスパン種別を含めなければなりません(MUST)。1つのスパン定義は、1つのスパン種別のみを言及できます。
属性
その特定の操作にとって重要な詳細のみを取得してください。親操作や子操作には、それぞれ独自のスパンがあります。
例えば、オブジェクトストアへのファイルアップロード呼び出しを記録する場合、エンドポイント、操作名(アップロードファイルなど)、コレクション、オブジェクト識別子を含めます。強い理由がない限り、その背後にあるHTTP/gRPCリクエストの詳細は含めません。
明確な価値をもたらす属性のみを含めてください。これにより、テレメトリーの量とパフォーマンスオーバーヘッドを低く抑えられます。利用可能なすべての詳細を取得しようとしないでください。判断に迷う場合は、追加の属性を参照しないでください。フィードバックに基づいて段階的に追加できます。
そのスパンを有用にするために必要な追加のプロパティを定義してください。
error.type属性を含めます。記述する操作に、ドメイン固有のエラーコードが通常存在する場合は、それも別の属性として含めます。どのエラーコードがエラーに該当するかを文書化します。クライアントのスパンには
server.addressとserver.portを含めます。ネットワーク呼び出しを表すスパンには、適用可能な
network.*属性を含めます。実行されている操作を記述する、何らかの操作名を含めます。
例えばHTTPの場合は
http.request.method、RPCの場合はrpc.method、メッセージングの場合はmessaging.operation.name、GenAIの場合はgen_ai.operation.nameです。 この属性は通常、スパン名の{action}として機能し、同じ領域内の複数のスパン定義で使われることがあります。DBコレクション、メッセージングキュー、GenAIモデル、オブジェクトストアのコレクションといった操作対象、入力パラメータ、スパンに記録すべき結果のプロパティなど、他の重要な特性を特定します。
属性を参照する際は次のようにします。
- その属性がhead samplingに関連するかどうかを指定します。そのような属性は、サンプラーに渡されるように、開始時に提供されるべきです(SHOULD)。通常、これらは低カーディナリティで取得しやすい属性です。
- 要求レベルを指定します。絶対に不可欠(かつ常に利用可能)な属性のみを
requiredにできます。機密情報を含む可能性がある属性、取得コストが高い属性、冗長な属性はopt-inにすべきです。 - その操作に合わせて属性定義を調整するために、briefとnoteを更新します。
メトリクスの定義
TBD(未定)
エンティティの定義
エンティティモデリングガイドに従ってください。
イベントの定義
イベントのセマンティック規約に従ってください。
既存の規約の安定化
- すべての規約は、安定版として宣言される前にYAMLで定義されていなければなりません(MUST)。
- 計装で使われていない規約は、安定版として宣言してはなりません(MUST NOT)。
TODO:
- 移行計画
移行計画
TODO(未定)