Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

Goのソースコードを解析して、依存しているOpenTelemetry Semantic ConventionsのAttributeを洗い出すツールを作りました

モチベーション

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ツールを開発しリリースしました。

github.com

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"
)

というコードであれば、semconv127go.opentelemetry.io/otel/semconv/v1.27.0semconvgo.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エージェントの支援を得てコーディングする

仕事であんまりコードを書かなくなりつつあるので、ちょっとした余暇でものづくりをする際にいろんな目論見を一回の挑戦に詰め込みがちになっています。