> Source: https://www.ymotongpoo.com/works/otel-specs-ja/semconv/non-normative/code-generation/


# セマンティック規約ライブラリのコード生成

このリポジトリで定義されているOpenTelemetryセマンティック規約のコードは、自動生成できます。

OpenTelemetryの各言語SIGは、自分たちの言語で慣用的な形式でセマンティック規約のコードを生成し、それを単独のライブラリとして配布してもよく、配布しなくてもかまいません。

この文書では、セマンティック規約のアーティファクトを構成し、コードを生成する方法について、共通のパターンをまとめ、非規範的なガイダンスを示します。

## 安定性とバージョニング

セマンティック規約には、さまざまな安定性レベルが混在しています。
セマンティック規約ライブラリを配布する言語SIGは、セマンティック規約の安定版部分のみを含む安定版アーティファクトを配布したり、すべてのセマンティック規約を含むプレビューアーティファクトを配布したり、その言語にとって慣用的で[SemVer 2.0](https://semver.org/)の安定性保証を提供する他の組み合わせを選んだりしてよいです。

考えられる解決策には次のものがあります。

- 特定のバージョンのすべてのセマンティック規約を専用のフォルダに生成し、古いバージョンはそのまま残す方法。[opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go/tree/main/semconv/)で採用されていますが、アーティファクトのサイズが問題になる場合は不都合が生じる可能性があります。
- 不安定な部分に注釈を付けるため、言語固有の規約に従う方法。たとえば、Pythonのセマンティック規約では不安定な属性を`opentelemetry.semconv._incubating`というインポートパスに置いており、これは（Pythonのアンダースコアの慣例に従い）内部用であり変更される可能性があるとみなされます。
- 2つの異なるアーティファクトを配布する方法。1つは安定版のセマンティック規約を含み、もう1つは利用可能なすべての規約を含みます。たとえば、[Javaのsemantic-conventions](https://github.com/open-telemetry/semantic-conventions-java)は`opentelemetry-semconv`と`opentelemetry-semconv-incubating`という2つのアーティファクトで配布されています。

> [!Note]
> 同じアーティファクトの2つのバージョン（安定版とプレビュー版）を配布すると、ダイヤモンド依存関係の問題によって不都合が生じる可能性があります。
> たとえば、ユーザーアプリケーションが`semconv v1.0.0-preview`に依存し、あるライブラリが実験的な規約を含まない`semconv v1.1.0`への推移的依存を持ち込む場合、後者が解決されてしまい、アプリケーションでコンパイルエラーやランタイムエラーが発生する可能性があります。

計装ライブラリは、安定版のセマンティック規約アーティファクト（の一部）に依存するか、関連する定義を自身のコードベースにコピーすべきです。
不安定なセマンティック規約アーティファクトは、エンドユーザーのアプリケーション向けを意図しています。

### 非推奨になった規約

非推奨になった属性、メトリクス、その他の規約についても、コードを生成することが推奨されます。適切な注釈を使って非推奨であることを示してください。
規約には`stability`プロパティがあり、非推奨になった時点の安定性レベル（`development`、`alpha`、`beta`、`release_candidate`、`stable`のいずれか）を示します。また`deprecated`プロパティは非推奨の理由を記述し、ドキュメント生成に利用できます。

- 安定性に達した後に非推奨となった規約は、SemVerに従いメジャーバージョンを更新しない限り削除すべきではありません。
- 不安定なうちに非推奨となった規約についても、生成を続け、プレビューのセマンティック規約アーティファクト（の一部）に残しておくべきです。これにより、ユーザーアプリケーションでのランタイムの問題や破壊的変更を最小限に抑えられます。

プレビューのセマンティック規約アーティファクト内には、安定版の規約定義も残しておいてください。これにより、セマンティック規約が安定化した際にユーザーコードが壊れることを防げます。プレビューアーティファクト内の安定版の定義は非推奨として扱い、生成されるドキュメントでユーザーを安定版の場所へ案内してください。
たとえばJavaでは、属性`http.request.method`は安定版とプレビューの両方のアーティファクトで非推奨として定義されています（例: `io.opentelemetry.semconv.incubating.HttpIncubatingAttributes.HTTP_REQUEST_METHOD`、`io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD`）。

## セマンティック規約アーティファクトの構造

この節では、セマンティック規約アーティファクトの構成方法についての提案をまとめます。

- アーティファクト名:
  - `opentelemetry-semconv` - 安定版の規約
  - `opentelemetry-semconv-incubating` - （該当する場合）安定版・不安定版を含むすべての規約を含むプレビューアーティファクト
- 名前空間: `opentelemetry.semconv`および`opentelemetry.semconv.incubating`
- 同じアプリケーション内の異なる計装が、それぞれ従っている規約の正確なバージョンを示せるように、サポートされているすべてのSchema URLを列挙すべきです。
- 属性、メトリクス、その他の規約定義は、規約の種類とルート名前空間でグループ化すべきです。以下の例を参照してください。

```
├── SchemaUrls.code
├── attributes
│   ├── ClientAttributes.code
│   ├── HttpAttributes.code
│   └── ...
├── metrics
│   ├── HttpMetrics.code
│   └── ...
└── events
    └── ...
```

## セマンティック規約の生成

この節では、weaverによるコード生成の方法を説明します。

> [!IMPORTANT]
> すべてのコード生成は、[weaver](https://github.com/open-telemetry/weaver/blob/main/crates/weaver_forge/README.md)を使って行うべきです（SHOULD）。
> Weaverは、[1.26.0](https://github.com/open-telemetry/semantic-conventions/tree/v1.26.0)以降のセマンティック規約のバージョンをサポートしています。

コード生成は、特定バージョンのセマンティック規約にあるYAML定義に基づいています。
通常、これはいくつかの手順から構成され、一部は半自動化できます。

1. 設定内のセマンティック規約のバージョンを手動で更新する
2. サポートするバージョンの一覧に新しいSchema URLを追加する
   - 自動化されていない場合でも、少なくとも自動的にチェックすることは可能です。
3. セマンティック規約の新しいバージョンをチェックアウト（またはダウンロード）する
4. コード生成スクリプトを実行する（詳細は以下を参照）
5. 自動生成されたコードのlint違反を修正する（あれば）
6. 新しいコードのPRを対応するリポジトリに送る

手順2〜5が[Python](https://github.com/open-telemetry/opentelemetry-python/pull/4091)と[Erlang](https://github.com/open-telemetry/opentelemetry-erlang/pull/733)でどのように実装されているかの例があります。

手順4（コード生成の実行）は言語固有のカスタマイズに依存します。また、ツールの移行によって影響を受けるのもこの手順だけです。

詳細は[weaverのコード生成ドキュメント](https://github.com/open-telemetry/weaver/blob/main/crates/weaver_forge/README.md)を確認してください。

### build-toolsからの移行

[build-tools](https://github.com/open-telemetry/build-tools/blob/v0.25.0/semantic-conventions/README.md)からの移行では、Jinjaテンプレートを変更し、[weaverの設定ファイル](https://github.com/open-telemetry/weaver/blob/main/crates/weaver_forge/README.md#configuration-file---weaveryaml)を追加する必要があります。

#### Weaverの設定

以下は、すべての属性を生成するこの設定ファイルの簡略化した例です。

```yaml
params:
  excluded_namespaces: [ios, aspnetcore, signalr, android, dotnet, jvm, kestrel]

templates:
  - pattern: semantic_attributes.j2
    filter: >
      semconv_grouped_attributes({
        "exclude_root_namespace": $excluded_namespaces
      })
      | map({
          root_namespace: .root_namespace,
          attributes: .attributes,
          output: $output + "attributes/"
        })
    application_mode: each
```

言語固有のパラメータは、設定の`params`節で設定するか、コード生成スクリプトからweaverコマンドを実行する際に（build-toolsと同様に）`-DparamName=value`引数として渡すことができます。

Weaverは、（対応する節で定義された）複数のテンプレートに対するコード生成を一度に実行できます。

Jinjaを実行する前に、weaverは各テンプレートの`filter`節でセマンティック規約定義をフィルタリングまたは加工できます。
この例では、ヘルパーメソッドである`semconv_grouped_attributes`フィルターを使用しており、属性定義をルート名前空間でグループ化し、この言語に関係のない属性を除外しています。代わりのフィルターや追加のフィルターを書き、[JQ](https://jqlang.github.io/jq/manual/)を使ってセマンティック規約のデータを加工することもできます。

多くの場合、名前空間の除外と安定性フィルターを指定して`semconv_grouped_attributes`を呼び出すだけで十分であり、後処理は不要です。

`application_mode: each`は、weaverに各セマンティック規約グループごとにコード生成を実行するよう設定し、その結果としてグループごとに別のファイルにコードを生成します。すべてのグループに対して一度にテンプレートを適用する`single`という適用モードもサポートされています。

設定、データスキーマ、JQフィルターなどの詳細については、[weaverのコード生成ドキュメント](https://github.com/open-telemetry/weaver/blob/main/crates/weaver_forge/README.md)を参照してください。

#### Jinjaテンプレート

（より優れた）データ構造とヘルパーメソッドを活かすために、Jinjaテンプレートを変更する必要があります。
最初の重要な違いは、各Jinjaテンプレートが対応するファイルの名前をどのように決めるかを定義できることです。メソッド`set_file_name`で出力ファイル名を指定しない場合、Weaverはテンプレート自体の相対パスと名前を使って出力ファイルを決定します。

たとえば、`output`パラメータで指定されたサブフォルダ内で、ルート名前空間を使う例を示します。

```jinja
{% set file_name = ctx.output + (ctx.root_namespace | snake_case ) ~ "_attributes.py" -%}
{{- template.set_file_name(file_name) -}}
```

データ構造での主な変更点:

- `attributes_and_templates` -> `ctx.attributes`
- `enum_attributes` -> `ctx.attributes | select("enum")`
- `metrics` -> `ctx.metrics`
- `root_namespace` -> `ctx.root_namespace`（`semconv_grouped_attributes`や同様のフィルターを使う場合のみ利用可能）
- すべてのカスタムパラメータは`ctx`変数の下のプロパティとして提供されます。
- `attribute.fqn` -> `attribute.name`
- `attribute.type | instantiated_type`（enum値の基底の型を取得する）
- `attribute.attr_type.members` -> `attribute.type.members`（enum型のメンバーを取得する）
- `member.member_id` -> `member.id`（enumメンバーのIDを取得する）

ヘルパーメソッドでの主な変更点:

- `attr.fqn | to_const_name` -> `attr.name | screaming_snake_case`
- `attr.fqn | to_camelcase(True)` -> `attr.name | pascal_case`
- `attr.brief | to_doc_brief | indent` -> `attr.brief | comment(indent=4)`。詳細な[コメント整形の設定](https://github.com/open-telemetry/weaver/blob/main/crates/weaver_forge/README.md#comment-filter)を参照してください。
- 安定性・非推奨のチェック:
  - 1つの属性をチェックする場合は`attribute is stable`、安定版の属性をフィルタリングする場合は`attributes | select("stable")`
  - 1つの属性をチェックする場合は`attribute is deprecated`、非推奨の属性をフィルタリングする場合は`attributes | select("deprecated")`
- 属性がテンプレートかどうかのチェック: `attribute.type is template_type`
- switch文のようなロジックを簡略化する新しい方法: `key | map_text("map_name")`。マップはweaverの設定内で定義できます。セマンティック規約の属性型を言語固有の型に変換する際に非常に役立ちます。

