モチベーション
Go言語でできたアプリケーションにOpenTelemetryの手動計装を行う際、Semantic Conventionsに定められたAttributeを付与するため、OpenTelemetry Go SDKのsemconv packageに定義されている定数や関数を参照してコードを書くことがあります。例えば、以下の例ではResourceに対してservice.name
属性を付与しています。
import semconv "go.opentelemetry.io/otel/semconv/v1.31.0" resource.New(ctx, resource.WithAttributes(semconv.ServiceName("myService")))
ここまで読んでサッパリ何のことがわからない場合には、残念ながらこのエントリを興味深く読めないかもしれないです。
さて、Semantic Conventionsで定められた慣例というのはバージョンを追うごとに変化していきます。例えば、データベースのクエリを表すdb.statement
という属性は、v1.25.0からdb.query.text
にリネームされました*1。
自分たちの計装が古いSemantic Conventionsに定められた属性に依存していないか、依存していたら新しい名称にリネームできないかというのは興味の対象になります。
semconview-go
今回、semconview-goというCLIツールを開発しリリースしました。
Goのソースコードを静的解析することで、semconv.ServiceName("myService")
やsemconv.ServiceNameKey
などといった、semconv packageの呼び出しを記録し、これをattributeのkey名に変換してリストアップするものです。
将来的には、リネームされ非推奨になった属性を教えてくれるような機能を追加したり、属性だけでなくメトリック名にも対応することを目指しています。
Homebrewでのインストールができたり、コンテナイメージも配布していたりしますので、ぜひご利用ください。
実装
semconv packageの定数・関数の呼び出し
まず、Goのソースコードの各ファイルごとimport文を解析し、importしているOpenTelemetry Go SDKのsemconv packageのパスとローカル変数名との対応を取ります。
import ( semconv127 "go.opentelemetry.io/otel/semconv/v1.27.0" semconv "go.opentelemetry.io/otel/semconv/v1.31.0" )
というコードであれば、semconv127
→go.opentelemetry.io/otel/semconv/v1.27.0
、semconv
→go.opentelemetry.io/otel/semconv/v1.31.0
という対応表を作る、というイメージです。
internal/semconview/analyzer.go#L122-L139
semconv packageの定数・関数名をAttributeキーに変換する表の作成
次に、importしているsemconv packageの中身を解析し、定数名や関数名をAttributeキーに変換する表を作っていきます。Go packageの解析にはgolang.org/x/tools/go/packagesを使用できます。
以下のようなコードでpackageをロードします。
internal/semconview/semconvpkg.go#L82-L103
次に、attribute.Key
型の定数を探します。
const ( ServiceNameKey = attribute.Key("service.name") )
のようなコードになっているので、attribute.Keyの型変換が右辺にあるような代入式を探し、Keyの引数を取り出すことで、定数名と属性キー名の対応表を作ることができます。
/internal/semconview/semconvpkg.go#L105-L164
最後に、attribute.KeyValue型を返却する関数を探します。
func ServiceName(val string) attribute.KeyValue { return ServiceNameKey.String(val) }
のようなコードになっていて、return文の中には先ほど検出対象としたattribute.Key型の定数が含まれています。先ほど作った、定数名と属性キー名の対応表を活用することで、関数名と属性キー名の対応表も作ることができます。
internal/semconview/semconvpkg.go#L166-L206
semconv packageからの呼び出しを検出して属性キーに変換
ここまで来ればあと少しです。元のGoのソースコードを解析し、importして利用しているsemconvの定数や変数を検出します。そして、先ほど作った表を用いて属性キーに変換します。
internal/semconview/analyzer.go#L141-L197
余談
今回、目的を達成するツールを作りたいというほかに、2つのチャレンジをしていました。
- Goのコードを静的解析する方法を身につける
- 生成AIエージェントの支援を得てコーディングする
仕事であんまりコードを書かなくなりつつあるので、ちょっとした余暇でものづくりをする際にいろんな目論見を一回の挑戦に詰め込みがちになっています。