はじめに
分散トレースでリクエスト全体の中でのボトルネックを発見すると同時に、ユーザーに対するSLOとしてトレース全体のレイテンシーを取得することもあると思います。その場合OpenTelemetryではすんなりとルートSpanのレイテンシーを取得できないため、その方法をメモしておきます。
バージョン
通常の計装
まずこういう典型的な計装があったとします。
func sleepHandler(w http.ResponseWriter, r *http.Request) { tracer := otel.Tracer("handler.sleep") ctx := context.Background() ctx, span := tracer.Start(ctx, "request.sleep") defer span.End() ...(なにかする)... }
この span
にかかったレイテンシーを取得したいとします。しかし go.opentelemetry.io/otel/trace.Span
には開始時刻と終了時刻のタイムスタンプを取得するメソッドが定義されていません。
しかし直感でも分かる通り、また仕様でも定義されている通り、Spanはその開始時刻と終了時刻を保持しているので、アクセスするためのインターフェースがどこかにあるはずです。
tracesdk.ReadOnlySpanに変換する
で、よくコードを読んでみると go.opentelemetry.io/otel/sdk/trace.ReadOnlySpan
にはそのためのインターフェースが定義されています。
したがって、上記計装から次のようにすれば span
のレイテンシーを取得できます。
func sleepHandler(w http.ResponseWriter, r *http.Request) { tracer := otel.Tracer("handler.sleep") ctx := context.Background() ctx, span := tracer.Start(ctx, "request.sleep") ...(なにかする)... span.End() ro := span.(tracesdk.ReadOnlySpan) start, end := ro.StartTime(), ro.EndTime() duration := end - start ...(durationをメトリクスとしてバックエンドに送る)... }
注意することとしては span.End()
を先に呼んでいないとspanのendtimeが打刻されないので、defer span.End()
にしていた部分は何かしら書き換えないといけないということです。
他の言語でも同様
上記の例はすべてGoで書いていたけれど、OpenTelemetryは仕様が全言語でインターフェースレベルで共通で実装されているので、おおよそこういった実装は他の言語でも共通になっています。
2022.06.15 追記
OpenTelemetryの仕様 によると、No-op Tracerの場合はそもそもReadOnlySpanにキャストできない様子なので、その場合の対応は考える必要がありそうです。