> Source: https://www.ymotongpoo.com/works/oteps/otep-4485/


# OTEP-4485: 複雑な値をサポートするための属性の拡張

## 用語集 {#glossary}

このOTEPの文脈では、次の用語を使用します。

- **単純な属性（simple attributes）** とは、プリミティブ型またはプリミティブの同種配列を持つ属性のことです。
  これらの型はあらかじめわかっており、[AnyValueのproto定義](https://github.com/open-telemetry/opentelemetry-proto/blob/42319f8b5bf330f7c3dd4a097384f9f6d5467450/opentelemetry/proto/common/v1/common.proto#L28-L40)におけるトップレベルの `string_value`、`bool`、`int64`、`double`、およびそれらの型の `ArrayValue` に対応します。
  これらは現在、[仕様](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.44.0/specification/common/README.md)において *標準（standard）* 属性と呼ばれています。

- **複雑な属性（complex attributes）** には、`AnyValue` protoでサポートされているそれ以外のすべての値が含まれます。
  たとえば、null（空）値、マップ、異種混在の配列、およびそれらとプリミティブの組み合わせなどです。
  バイト配列も複雑な属性とみなされます。
  これは、現在の *標準（standard）* 属性の定義からバイト配列が除外されているためです。

- **AnyValue** は、API、SDK、および[protoレベル](https://github.com/open-telemetry/opentelemetry-proto/blob/42319f8b5bf330f7c3dd4a097384f9f6d5467450/opentelemetry/proto/common/v1/common.proto#L28-L40)における *任意の*（単純または複雑な）属性値の型を表します。

  これは、[ログのデータモデル](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.44.0/specification/logs/data-model.md#type-any)では `any` としても知られています。

単純な属性と複雑な属性のこの区別は、仕様の用語として意図されたものではありませんが、ここでは有用です。
なぜなら、このOTEPは、*単純な* 属性と *複雑な* 属性の両方を *標準* 属性の集合に含めることを提案しているからです。

## なぜか {#why}

### なぜスパンに複雑な属性を持たせたいのか {#why-do-we-want-complex-attributes-on-spans}

スパンに複雑な属性を許可したい理由はいくつかあります。

- 新たに登場しているセマンティック規約は、スパンに複雑な属性を含めることの有用性を示しています。
  たとえば、生成AIのための[プロンプトと補完の記録](https://github.com/open-telemetry/semantic-conventions/pull/2179)や、GraphQLのための[リクエストエラーの記録](https://graphql.org/learn/response/#request-errors)が挙げられます。
- 多くのユーザーは、すでにスパンに複雑な属性をJSONエンコードして追加しています。
  複雑な属性をネイティブにサポートすることで、Collectorでの変換、より良い属性の切り詰め、バックエンドでの複雑な属性のネイティブなストレージといった将来的な可能性が広がります。
- スパンイベントの非推奨化のプロセスの中で、一部の人々がスパンイベントを使ってスパン上の複雑なデータを表現していることがわかりました。
  スパンに複雑な属性のサポートを提供することで、このデータをより自然な方法で格納できるようになります。
- イベントの安定化プロセスの中で、[スパンとイベントは概念的な双子とみなされることが多い](https://opentelemetry.io/blog/2025/opentelemetry-logging-and-you/#how-is-this-different-from-other-signals)ということが明らかになりました。
  そのため、あるものをスパンとしてモデル化するかイベントとしてモデル化するかの選択は、複雑な属性が必要かどうかによって左右されるべきではありません（ログはすでに複雑な属性をサポートしているため）。

### なぜ標準属性を拡張したいのか {#why-do-we-want-to-extend-standard-attributes}

スパンやイベントで使用できる2つ目の「拡張」属性のセットを導入する代わりに、私たちは標準属性を拡張することを提案します。

APIをまたいで複数の属性セットを持つことは、[人間工学的な課題を生み出します](https://github.com/open-telemetry/opentelemetry-specification/issues/4201)。
（[opentelemetry-java#7123](https://github.com/open-telemetry/opentelemetry-java/pull/7123)や[opentelemetry-go#6180](https://github.com/open-telemetry/opentelemetry-go/pull/6180)で示されているような）いくつかの緩和策はあるものの、標準属性を拡張することで、よりシームレスでユーザーフレンドリーなAPI体験が得られます。

### なぜこれはメジャーバージョンの繰り上げを必要としないのか {#why-doesnt-this-require-a-major-version-bump}

現在、SDKの仕様には、標準属性の集合を拡張することは[破壊的変更とみなされる](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.44.0/specification/common/README.md#standard-attribute)という条項があります。

私たちは、この条項を削除して標準属性を拡張することは、メジャーバージョンの繰り上げを必要とせずに、OpenTelemetryのエコシステム全体でスムーズに行えると考えています。

- 各言語のSDKは、後方互換性の保証を破ることなくこれを実装できます（たとえば、[Javaのもの](https://github.com/open-telemetry/opentelemetry-java/blob/main/VERSIONING.md)）。
- バックエンドは複雑な属性をサポートするための追加対応が必要になるかもしれませんが、これは新しいOTLPの機能が導入される際には常に発生することです。
  - OTLPのマイナーバージョンを繰り上げることは、この種の変更を伝えるための通常の手段としてすでに確立されています。
  - SDKは、そのOTLPバージョン以降でのみ複雑な属性を出力するように要求されます。
  - 安定版のエクスポーターは、このOTEPがマージされてから少なくとも6か月間は、ログ以外のシグナルでデフォルトで複雑な属性を出力することを禁止されます。
  - バックエンドにとって比較的簡単な実装の選択肢は、複雑な属性を単にJSONシリアライズして文字列として格納することです。

## 方法 {#how}

### API {#api}

属性の作成や追加を行う既存のAPIは、*複雑な属性* をサポートするように拡張されます。

型チェック、人間工学、パフォーマンスの観点から、`AnyValue` 型（複雑または単純な属性値を表すAPI）を公開することがRECOMMENDEDです。

「Attributes」に加えて「ExtendedAttributes」を持つといった、複数の種類の属性セットを公開することはNOT RECOMMENDEDです。

OTel APIは、スパン、ログ、プロファイル、スパンリンク、および記述的なエンティティ属性に対して複雑な属性を設定できることをMUSTとしてサポートしなければなりません。

OTel APIは、メトリクス、リソース、計装スコープ、スパンイベント、および識別用のエンティティ属性に対して複雑な属性を設定できることをMAYとしてサポートしてもよいです。

> [!NOTE]
> ここで「MUST」ではなく「MAY」が使われているのは、動的型付け言語のAPIに柔軟性を持たせるためです。
> 現時点では、これらの領域で複雑な属性を必要とする具体的なユースケースがないためです。
>
> 静的型付け言語では、おそらく複雑な属性の設定をすべての場所で一律にサポートすることを選択するでしょう。
>
> この要求レベルは、これらの領域で複雑な属性のユースケースが見つかった場合、将来的に「MAY」から「MUST」に変わる可能性があります。

複雑な属性に関するAPIドキュメントや仕様の文言には、次のような内容を含めることがSHOULDです。

> 単純な属性は可能な限り使用されるべきです（SHOULD）。
> 計装は、バックエンドが複雑な属性の個々のプロパティをインデックス化しないこと、そうしたプロパティに対するクエリや集計が非効率で複雑であること、複雑な属性を報告することがより高いパフォーマンスオーバーヘッドを伴うことを前提とすべきです（SHOULD）。

### SDK {#sdk}

OTel SDKは、スパン、ログ、プロファイル、スパンリンク、および記述的なエンティティ属性に対して複雑な属性を設定できることをMUSTとしてサポートしなければなりません。

OTel SDKは、メトリクス、エグゼンプラー、リソース、計装スコープ、スパンイベント、および識別用のエンティティ属性に対して複雑な属性を設定できることをMAYとしてサポートしてもよいです。

> [!NOTE]
> ここで「MUST」ではなく「MAY」が使われているのは、動的型付け言語のSDKに柔軟性を持たせるためです。
> 現時点では、これらの領域で複雑な属性を必要とする具体的なユースケースがないためです。
>
> 静的型付け言語では、おそらく複雑な属性の設定をすべての場所で一律にサポートすることを選択するでしょう。
>
> この要求レベルは、これらの領域で複雑な属性のユースケースが見つかった場合、将来的に「MAY」から「MUST」に変わる可能性があります。

SDKは、複雑な属性がAPIサーフェス上で許可されている場合には常に、処理の過程でそれらを読み取り、変更できることをMUSTとしてサポートしなければなりません。

#### `AnyValue` の実装に関する注意事項 {#anyvalue-implementation-notes}

APIが、トレーサー、メーター、ロガー、または時系列を識別するために属性の等価性が広く使われているメトリクス、計装スコープ、リソース、または識別用のエンティティ属性に対して `AnyValue` 属性をサポートする場合、`AnyValue` の実装は深い等価性チェックを提供しなければなりません（MUST）。

キーと値のペアのリストを1つ以上含む `AnyValue` インスタンスについては、次のとおりです。

- `AnyValue` インスタンスの等価性は、リスト内のキーと値のペアの順序に影響されてはなりません（MUST NOT）。
- 重複したキーが存在する場合の等価性の挙動は未規定です。

#### 属性の制限 {#attribute-limits}

複雑な属性の制限は、このOTEPとは別に定義されるものであり、複雑な属性が許可されているすべてのシグナルに適用されます。

これは[#4487](https://github.com/open-telemetry/opentelemetry-specification/issues/4487)で追跡されています。

### エクスポーター {#exporters}

OTLPエクスポーターは、`AnyValue` 属性をエンドポイントに渡すべきです（SHOULD）。

Prometheusのような、複雑な値をネイティブにサポートしないプロトコル向けのエクスポーターは、[属性の仕様](/specification/common/README.md#attribute)に従って、複雑な値をJSONエンコードされた文字列として表現すべきです（SHOULD）。

`AnyValue` オブジェクトをJSONにシリアライズする際には、キーと値のペアのリストをキーの辞書順にソートし、シリアライズの安定性を高める追加設定を適用することがRECOMMENDEDです。

### セマンティック規約 {#semantic-conventions}

セマンティック規約は、次のガイダンスで更新されます。

- 単純な属性は可能な限り使用されるべきです（SHOULD）。
  セマンティック規約は、バックエンドが複雑な属性の個々のプロパティをインデックス化しないこと、そうしたプロパティに対するクエリや集計が非効率で複雑であること、複雑な属性を報告することがより高いパフォーマンスオーバーヘッドを伴うことを前提とすべきです（SHOULD）。

- 文字列にシリアライズした際に大きくなりやすい複雑な属性は、*オプトイン* の属性として追加されるべきです（SHOULD）。
  *大きい* かどうかは、一般的なバックエンドの属性制限（通常4〜16KB程度）に基づいて定義されます。

- 複雑な属性は、メトリクス、リソース、計装スコープ、またはエンティティの識別用属性として使用してはなりません（MUST NOT）。

### Proto {#proto}

OTLPはすべてのシグナルで `AnyValue` 属性を使用しているため、変更は[このような](https://github.com/open-telemetry/opentelemetry-proto/blob/be5d58470429d0255ffdd49491f0815a3a63d6ef/opentelemetry/proto/trace/v1/trace.proto#L209-L213)コメントの更新と変更履歴の記録の追加に限定されます。

## トレードオフと緩和策 {#trade-offs-and-mitigations}

### バックエンドが `AnyValue` 属性をサポートしていない {#backends-dont-support-anyvalue-attributes}

複雑なデータをテレメトリーに記録できるようにするべきではあるものの、多くのバックエンドはそれをサポートしておらず、個々の複雑な属性がドロップされる結果になる可能性があります。

私たちは、次の方法でこれを緩和します。

- OTel APIの実験的な部分に新しいAPIを導入することで、サポートされていない属性型の影響を早期採用者に限定しつつ、バックエンドがサポートを追加する時間を確保します。

- 複雑な属性の使用を制限するセマンティック規約のガイダンスを整備します。

- OTTLを使って複雑な属性をドロップ、フラット化、シリアライズ、または切り詰めることができる既存のCollectorの変換プロセッサーを活用します。

### 任意のオブジェクトは危険である {#arbitrary-objects-are-dangerous}

任意のオブジェクトを属性として許可することは便利ですが、大きい、機密性が高い、可変である、シリアライズできない、あるいはその他の問題のあるデータをテレメトリーに含めてしまうリスクを高めます。

任意のオブジェクトを `AnyValue` に変換する利便性を提供するOTel SDKは、[OTLPのAnyValueへのマッピング](/specification/common/attribute-type-mapping.md#converting-to-anyvalue)に従い、サポートする型をプリミティブ、配列、標準ライブラリのコレクション、名前付きタプル、JSONオブジェクト、およびそれに類する構造体に限定すべきです（SHOULD）。

未知のオブジェクトについては文字列表現にフォールバックすることが、複雑な属性の意図しない使用のリスクを最小化するためにRECOMMENDEDです。

AnyValueへの変換に関する先行事例として、[Go](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/72cccd8065dcfd84b69f34d8cb6f349a547fedce/bridges/otelslog/convert.go#L20)、[.NET](https://github.com/open-telemetry/opentelemetry-dotnet/blob/71abd4169b4b6c672343b37c32e3337bc227ed32/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLogger.cs#L134)、[Python](https://github.com/open-telemetry/opentelemetry-python/blob/00329e07fb01d7c3e43bb513fe9be3748745c52e/opentelemetry-api/src/opentelemetry/attributes/__init__.py#L121)が挙げられます。

## プロトタイプ {#prototypes}

- [opentelemetry-python#4587](https://github.com/open-telemetry/opentelemetry-python/pull/4587)
- [opentelemetry-go#6809](https://github.com/open-telemetry/opentelemetry-go/pull/6180)

## 将来の可能性 {#future-possibilities}

### 設定可能なOTLPエクスポーターの挙動（SDKとCollectorの両方） {#configurable-otlp-exporter-behavior-both-sdk-and-collector}

複雑な属性に対するOTLPエクスポーターの挙動は、シグナルごとにカスタマイズ可能にすることができます。
これにより、複雑な属性を次のように扱えるようになります。

- そのまま渡す（デフォルト）
- JSONにシリアライズする
- ドロップする

このオプションは、バックエンドが複雑な属性型をうまく扱えないアプリケーションの回避策として役立つ可能性があります。

### 繰り返されるデータへのポインタの記録 {#record-pointer-to-repetitive-data}

構造化されたデータに対する集計は、深い等価性チェックが必要になることなどにより、パフォーマンスオーバーヘッドと追加の複雑さをもたらします。

データが繰り返されるものである場合、一度だけ記録して一意の識別子を割り当てることができます。
それ以降のテレメトリーアイテムは、データを複製する代わりにこの識別子を参照できます。

このアプローチは、パフォーマンスオーバーヘッドと送信されるデータ量を削減し、最適化として段階的に実装できます。
これは、計装APIに影響を与えるべきではなく、より重要な点として、クエリやダッシュボードを含むユーザー体験にも影響を与えるべきではありません。

## バックエンドの調査 {#backend-research}

追加の詳細については、[このgist](https://gist.github.com/lmolkova/737ebba190b206a5d60bbc075fea538b)を参照してください。

| バックエンド | 複雑な属性をうまく扱えるか | コメント |
| ------- | -------------------------------------- | -------- |
| Jaeger（OTLP） | :white_check_mark: | JSON文字列にシリアライズする |
| OTLPリモートライトを使ったPrometheus | :white_check_mark: | JSON文字列にシリアライズする |
| Grafana Tempo（OTLP） | :white_check_mark: | JSON文字列にシリアライズし、表示は可能だがこの属性を使ったクエリはできない |
| Grafana Loki（OTLP） | :white_check_mark: | フラット化する |
| Aspireダッシュボード（OTLP） | :white_check_mark: | JSON文字列にシリアライズする |
| ClickHouse（Collectorエクスポーター） | :white_check_mark: | JSON文字列にシリアライズし、JSONをパースしてクエリできる |
| Honeycomb（OTLP） | :white_check_mark: | 5階層未満で、配列やバイナリデータでなければフラット化し、それ以外はJSON文字列にする |
| Logfire（OTLP） | :white_check_mark: | JSONとして格納され、クエリでのJSONのネイティブサポートがある |
| New Relic（OTLP） | :white_check_mark: | 複雑な属性をドロップする |
| Splunk（OTLPおよびHECエクスポーター） | :white_check_mark: | ログ（HEC）ではフラット化し、トレースとメトリクス（OTLP）ではJSON文字列にシリアライズする |

> [!NOTE]
> このリストは執筆時点での挙動のみを反映しており、将来変更される可能性があります。

## 付録 {#appendix}

[属性値型の集合を拡張する #4651](https://github.com/open-telemetry/opentelemetry-specification/pull/4651) PRは、OTel APIとSDKの両方が複雑な属性をMUSTとしてサポートすることを要求することで、このOTEPの一部を実装しています。
一部の言語は、あらゆる種類のテレメトリーで複雑な属性をサポートすることを目指しています。
言語間の一貫性を保つため、私たちはすべての言語が複雑な属性に対して同じレベルのサポートを提供すべきであることに合意しました。

