🐥

GoアプリでのDatadogエラートラッキングの改善

に公開

Datadog APMについて

カンムではGoアプリケーションのパフォーマンス監視にDatadog APMを利用しています。

WebアプリケーションのフレームワークとしてはGorilla Muxを使っているので、以下のようにトレーシングライブラリのdd-trace-goを組み込みます。

package main

import (
	"net/http"

	muxtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gorilla/mux"
)

func handler(w http.ResponseWriter, _ *http.Request) {
	w.Write([]byte("Hello World!\n"))
}

func main() {
	mux := muxtrace.NewRouter()
	mux.HandleFunc("/", handler)
	http.ListenAndServe(":8080", mux)
}

contrib/gorilla/muxはGorilla Muxのルーターをラップして、リクエストごとのSpanを開始・終了、Spanへのステータスコードのセット、ステータスコードからのエラー情報のセットなどを行います。

細かい設定をしなくても自動的にいろいろやってくれる便利なライブラリなのですが、エラートラッキングにすこし問題があり、forkしたバージョンを使わざるを得ない状況でした。

エラートラッキングの問題

DatadogのエラートラッキングはAPMと密に結合しています。
以下のようにspan.SetTag(ext.Error, err)でエラーをセットすると

span, _ := tracer.SpanFromContext(ctx)
span.SetTag(ext.Error, err)

セットしたエラーをAPMコンソールで見ることができます。

この機能、Spanに対してもう一度span.SetTag(ext.Error, err)を実行すると元のエラーが上書きされるという問題があります。

前述の通り、contrib/gorilla/muxはステータスコードをみてエラー情報をセットするので実際に発生したエラーが*errors.errorString: 500: Internal Server Errorで上書きされてしまいます。

https://github.com/DataDog/dd-trace-go/blob/6becedc3e7d4c8da52788409123a61f2e07c316a/contrib/internal/httptrace/httptrace.go#L74-L76

こうなると元のエラーのメッセージもスタックトレースも失われるので、原因の調査が難しくなります。

dd-trace-goの改修の試み

Datadogの導入後、この問題に気づいてdd-trace-goの改修を試みました。

残念ながら開発側の対応がアクティブなものではなく、またこちらとしてもそれほど時間を割くことができなかったので、dd-trace-goをforkしてエラーの上書きをしないように修正したものを使っていました。
forkすると独自の修正は入れられるもののアップストリームとの同期の手間が発生するため、あまり望ましい状況とはいえません。

WithStatusCheck()の追加

以前、Goのコネクションプーリングのメトリクス収集の記事を書きましたが、dd-trace-go側にDBStatsを収集する機能が入ったことを知ったのはリリースからしばらく経ってからのことです。

fork版がアップストリームに追いつけていない状況を実感し、なんとかせねばとdd-trace-goのコードを眺めていたのですが、ステータスコードの処理の部分に関数を差し込めるように変わっていることに気づきました。

https://github.com/DataDog/dd-trace-go/blob/9eb806973f1b726ea1e09e85f2ba68bb57d6d036/instrumentation/httptrace/httptrace.go#L154-L166

調べてみると以下のPRでcontrib/go-chi/chiWithIsErrorCheck()というオプション関数が追加され、ステータスコードのチェックの動作を変える関数を追加できるようになったようでした。

https://github.com/DataDog/dd-trace-go/pull/773

「これを使えばエラーの上書きを抑止できる!」と喜んだのですが、どういうわけかcontrib/gorilla/muxにはこの関数は存在せず…

仕方ないのでcontrib/gorilla/muxに関数を追加するPRを作成して無事にマージされました。(既存の機能をコピーしただけなのですぐにマージされたのだと思います)

https://github.com/DataDog/dd-trace-go/pull/3166

dd-trace-go v2の実装が進んでいたので、もしかしたらv1に修正が入らないかもしれない、と思っていましたが、先日、無事にリリース。
https://github.com/DataDog/dd-trace-go/releases/tag/v1.73.0

WithStatusCheck()でfalseを返す関数をセットするとエラーが上書きされないことを確認できたので、現在アプリのライブラリの差し替え作業を進めています。

mux := muxtrace.NewRouter(
	muxtrace.WithStatusCheck(
		func(statusCode int) bool { return false },
	),
)
株式会社カンム

Discussion