New Relic で connect-go のトレースを URL パスごとに取得する
はじめに
New Relic で connect-go のトレースを URL パスごとに取得する方法についてです。
New Relic や connect-go の細かい説明は省略します。
課題
New Relic は HTTP リクエストに関するトレース情報を簡単に取得することができます。
しかし connect-go を利用している場合、以下のように URL パスに関する情報が可視化されず、全てのリクエストが /XXX_SERVICE/
のように集約されます。
目標
以下のように URL パスごとにトレース情報を取得することが目標です。
原因
課題に挙げたように URL パスが集約される原因は New Relic の SDK と connect-go で生成されたコードの相性が悪いためです。
New Relic を HTTP ミドルウェアに仕込む際は通常以下のような実装を行います。
mux := http.NewServeMux()
path, handler := v1connect.NewXXXServiceHandler(...)
mux.Handle(newrelic.WrapHandle(nr, path, handler))
WrapHandle
内部で自動的にトレース名が生成されます。
ここで connect-go で自動生成されている NewXXXServiceHandler
を確認してみます。
詳細は省きますが、戻り値は以下のようになっています。
func NewXXXServiceHandler(svc XXXServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
return "/XXXService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case XXXServiceGetUserProcedure:
XXXServiceGetUserHandler.ServeHTTP(w, r)
case XXXServiceCreateUserProcedure:
XXXServiceCreateUserHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
})
}
ここで問題になるのが、 New Relic の SDK 側でトレース名に利用される path
が connect-go 側で /XXXService/
に固定されてしまっているためです。
connect-go 側ではパスを一括で受け取った後に関数内部で switch
を利用して分岐しています。
これらの組み合わせによって connect-go の 1 つの Service はパスによらず全て同一名で New Relic 側に表示されます。
対策
対策としては mux.Handle()
に渡す関数を更にもう 1 段階ラップすることで、パスごとにトレース情報を取得することができます。
ラップ関数では http.Reqest
からパスを取得し、そのパスの情報を New Relic に渡しているだけです。
New Relic 側ではパス情報はトレース名の生成にしか利用していないため、このような実装で問題なくパスごとにトレース情報を取得することができます。
mux.Handle()
に渡すパスは connect-go 側の NewXXXServiceHandler
から取得したものをそのまま利用しています。
func main() {
mux := http.NewServeMux()
path, handler := v1connect.NewXXXServiceHandler(...)
mux.Handle(path, customNewRelicHandler(nr, handler))
}
func customNewRelicHandler(app *newrelic.Application, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, handler = newrelic.WrapHandle(app, r.URL.Path, handler)
handler.ServeHTTP(w, r)
})
}
Discussion