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
で上書きされてしまいます。
こうなると元のエラーのメッセージもスタックトレースも失われるので、原因の調査が難しくなります。
dd-trace-goの改修の試み
Datadogの導入後、この問題に気づいてdd-trace-goの改修を試みました。
残念ながら開発側の対応がアクティブなものではなく、またこちらとしてもそれほど時間を割くことができなかったので、dd-trace-goをforkしてエラーの上書きをしないように修正したものを使っていました。
forkすると独自の修正は入れられるもののアップストリームとの同期の手間が発生するため、あまり望ましい状況とはいえません。
WithStatusCheck()
の追加
以前、Goのコネクションプーリングのメトリクス収集の記事を書きましたが、dd-trace-go側にDBStatsを収集する機能が入ったことを知ったのはリリースからしばらく経ってからのことです。
fork版がアップストリームに追いつけていない状況を実感し、なんとかせねばとdd-trace-goのコードを眺めていたのですが、ステータスコードの処理の部分に関数を差し込めるように変わっていることに気づきました。
調べてみると以下のPRでcontrib/go-chi/chi
にWithIsErrorCheck()
というオプション関数が追加され、ステータスコードのチェックの動作を変える関数を追加できるようになったようでした。
「これを使えばエラーの上書きを抑止できる!」と喜んだのですが、どういうわけかcontrib/gorilla/mux
にはこの関数は存在せず…
仕方ないのでcontrib/gorilla/mux
に関数を追加するPRを作成して無事にマージされました。(既存の機能をコピーしただけなのですぐにマージされたのだと思います)
dd-trace-go v2の実装が進んでいたので、もしかしたらv1に修正が入らないかもしれない、と思っていましたが、先日、無事にリリース。
WithStatusCheck()
でfalseを返す関数をセットするとエラーが上書きされないことを確認できたので、現在アプリのライブラリの差し替え作業を進めています。
mux := muxtrace.NewRouter(
muxtrace.WithStatusCheck(
func(statusCode int) bool { return false },
),
)
Discussion