opentelemetry-go: exec.Commandで外部コマンドを呼ぶ時にトレースコンテキストを伝搬させる

exec.Commandで外部コマンドを起動する時にトレースコンテキストを伝搬させてみました

今回のコード例

github.com

環境変数+propagation.TraceContextを利用することで伝搬させてみました。

解説

httpやgRPCの場合のコンテキスト伝搬

こちらの記事にあるように、呼び出す側/呼び出される側の両方で以下のようにPropagatorを設定した上でotelhttpotelgrpcなどのインターセプターを利用します。

otel.SetTextMapPropagator(
        propagation.NewCompositeTextMapPropagator(
                propagation.TraceContext{},
                propagation.Baggage{},
        ),
)

exec.Commandで外部コマンドを起動する時にはotelhttpやotelgrpcのようなインターセプターがありませんので自前でInject/Extractする必要があります。

どうやってトレースコンテキストを渡す?

W3C TraceContextのような仕様がないか探したのですが、いくつかの実装はあるものの仕様としてはまだない模様でした。

github.com

そこで参考としてequinix-labs/otel-cliでの環境変数経由でトレースコンテキストを参照する部分の実装をみたところ、

  • 環境変数名としてtraceparentを用いる
  • 値のフォーマットはW3C TraceContextを利用

となっていました。

これならpropagation.TraceContextの実装が使えるのでは?

上記の仕様で渡すのであればpropagation.TraceContextが使えそうです。
propagation.TraceContextは引数で渡されたpropagation.TextMapCarrierに対して読み書きを行います。

環境変数をなんらかの形でラップしてpropagation.TextMapCarrierの形にした上でInject/Extractを呼べばなんとかできそうです。

やってみた

改めて今回のコード例はこちらに置いています。

github.com

親コマンド側では以下のようにotel.GetTextMapPropagator().Inject()した上で環境変数を組み立てています。 https://github.com/yamamoto-febc/otel-env-context/blob/6db0d50cbf473c8c3095413657da234a57c63ddb/cmd/otel-parent/main.go#L47-L51

// 親コマンド側

// Propagatorに指定されているpropagation.TraceContextを用いてトレースコンテキストをenvCarrierに書き出し
envCarrier := propagation.MapCarrier{}
otel.GetTextMapPropagator().Inject(ctx, envCarrier)

// 書き出したトレースコンテキストを環境変数に設定
for _, key := range envCarrier.Keys() {
    cmd.Env = append(cmd.Env, key+"="+envCarrier.Get(key))
}

子コマンド側ではotel.GetTextMapPropagator().Extract()してあげます。 https://github.com/yamamoto-febc/otel-env-context/blob/4f30a8c453d64b567eecae2301226327114f1a5e/cmd/otel-child/main.go#L27-L33

// 子コマンド側

envCarrier := propagation.MapCarrier{
    "traceparent": os.Getenv("traceparent"),
    "tracestate":  os.Getenv("tracestate"),
}
// 環境変数からトレースコンテキストを抽出
parentCtx := otel.GetTextMapPropagator().Extract(context.Background(), envCarrier)

// トレース開始
ctx, span := otel.Tracer(instrumentationName).Start(parentCtx, "child")

これでいい感じにトレースコンテキストの伝搬ができてるはずです。

終わりに

もっといいやり方があれば是非教えてください。

以上です。

参考にしたサイト

christina04.hatenablog.com blog.cybozu.io www.w3.org github.com