aws-xray-sdk-goを使う時はhttp.Client.Getとか使ってはダメで必ずcontext.Contextを渡さないといけない

aws-xray-sdk-goというAWS X-Rayでトレースを記録する便利グッズがある。

これはoutgoing HTTP requestも記録できるのだけれども、ある時を機会にトレースの記録に失敗するようになって試行錯誤したけど今日、IQ200になってすべてを理解した。
結論はタイトルの通りで、以下は詳細。

具体的にはこういうエラー・警告が出ていた:

[00] 2019-05-16T15:50:34+09:00 [Error] Suppressing AWS X-Ray context missing panic: failed to begin subsegment named 'example.com': segment cannot be found.
[00] 2019-05-16T15:50:35+09:00 [Warn] failed to record HTTP transaction: segment cannot be found.

つまり親のsegmentが取れていないということ。

HTTP APIなので xray.Handler を使ってsegmentを作っているはずだけど……。
コードはREADMEにある以下のようなかんじ:

func main() {
  http.Handle("/", xray.Handler(xray.NewFixedSegmentNamer("myApp"), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello!"))
  })))
  http.ListenAndServe(":8000", nil)
}

aws-xray-sdk-goはcontextを使ってrecorderというオブジェクトを持ち回しおり、このrecorderというオブジェクトがsegmentを保持している。

トレースできなかったコードはこういうかんじ:

	resp, err := c.httpClient.Get(endpointURL.String())

このメソッドにcontextは渡しているけれど、HTTPリクエストを送る時に渡していないのが原因
どうしたらいいかというとhttp.Requestオブジェクトを作ってWithContext()を呼ぶとうまくいった。

	req, err := http.NewRequest(http.MethodGet, endpointURL.String(), nil)
	if err != nil {
		return nil, fmt.Errorf("[BUG] failed to build request: %s", err)
	}
	resp, err := c.httpClient.Do(req.WithContext(ctx))

いや〜〜〜〜〜〜〜大変。

気付くのに時間がかかったポイントとしては、以前のリビジョンではうまくいっていたのでHTTPリクエストを送るコードより設定やライブラリのバージョンに関心が寄っていたこと、他にトレースを発行するコードがなかったので全体に問題があるのかHTTPリクエスト送信に問題があるのか切り分けがむずかしかったこと、があげられそう。
手でsubsegmentを作ってうまくいくか見れば、トレースが取れてxray.Clientの使い方がおかしいと気付けたかもしれない。

ちなみになぜ以前はうまくいっていたかというと、その当時呼んでいたメソッドはWithContextを呼んでおり、最新のアプリではhttp.Client.Getを呼ぶ実装になっていたため……。