追記
nrechoというライブラリが公式から提供されているのでこっちを使ったほうがよいと思います。わざわざ自前実装をしてから見つけてしまったけど見つけられたのはよかったということでここはひとつ……。
そういうわけなので、以下の文章は読まなくてもよいことになりましたが、記録のために残しておきます。
(追記ここまで)
あらすじ
練習でISUCON9予選のコードにNew Relicエージェントを導入したときは、gojiを使っていて、 mux.HandleFunc
でhandlerを登録する形式だったのでちょっと書き換えればすぐ導入できたのですが、echoだとひと工夫必要でした。
そもそも公式のGoエージェントではどうやっているのか、gojiの場合は
newrelic.WrapHandleFunc
といった関数を使うことでGoエージェントを有効にできます(使用例のコードを引用)。
http.HandleFunc(newrelic.WrapHandleFunc(app, "/users", func(w http.ResponseWriter, req *http.Request) { txn := newrelic.FromContext(req.Context()) txn.AddAttribute("customerLevel", "gold") io.WriteString(w, "users page") }))
gojiの場合はこんな感じで導入できます。goji.io/patを使っていたので、軽いwrapperを噛ませて使っていました。
func wrapHandleFuncGet(path string, f func(w http.ResponseWriter, r *http.Request)) (*pat.Pattern, func(w http.ResponseWriter, r *http.Request)) { pathStr, wrapped := newrelic.WrapHandleFunc(newRelicApp, path, f) return pat.Get(pathStr), wrapped } func wrapHandleFuncPost(path string, f func(w http.ResponseWriter, r *http.Request)) (*pat.Pattern, func(w http.ResponseWriter, r *http.Request)) { pathStr, wrapped := newrelic.WrapHandleFunc(newRelicApp, path, f) return pat.Post(pathStr), wrapped } func main() { // (snip) mux.HandleFunc(wrapHandleFuncGet("/users/:user_id.json", getUserItems)) mux.HandleFunc(wrapHandleFuncPost("/buy", postBuy)) // (snip) }
echoミドルウェア選定
GitHubで echo newrelic
というクエリでGo言語のリポジトリに絞って検索したところ、2020/09/13 時点で以下の5つのリポジトリがヒットしました。
- GitHub - dafiti/echo-middleware: Middlewares for Echo Framework
- GitHub - jessie-codes/echo-relic: Echo middleware for new relic
- GitHub - notyim/echo-middleware-newrelic: NewRelic middleware for Echo
- GitHub - phacops/echorelic: Middleware for Echo (https://echo.labstack.com/) to integrate with New Relic (https://newrelic.com/).
- GitHub - golang-common-packages/monitoring: This package for service monitoring and It can work with Echo as middleware
エンドポイントごとにレスポンスを返すのにかかった時間を計測するのはどれでもできそうですが、Transactionオブジェクトをcontextで引き回さないと、DBアクセスにかかった時間までは測定できません。github.com/jessie-codes/echo-relic 以外は そのような実装になっています。
github.com/dafiti/echo-middleware は、ライブラリ側でNewRelicエージェントを初期化しており、また初期化に失敗したらpanicするので、エージェントを無効にしてアプリケーションを動かしたいというユースケースには合わなさそうです。
github.com/notyim/echo-middleware-newrelic は、importしたさいに環境変数の情報をもとにNew Relicエージェントを初期化しています。またエージェント初期化処理のエラーハンドリングがライブラリに委ねられています。ログを吐く処理とか環境変数をどうするか、みたいなのは自分でコントロールしたいので使わなさそうです。 runable
変数はどこで使っているのだろう……。
github.com/phacops/echorelic はNew Relicエージェントを渡してミドルウェアを作るかたちになっていてよさそうですが、c.Request().URL.Path
をもとにトランザクション名を決めているのはよくなさそうでした。GET /api/chair/:id
へのリクエストが :id
ごとに散らばることになってしまいます。
github.com/golang-common-packages/monitoring (名前の範囲が広い!!) は、これもライブラリ側でNewRelicエージェントを初期化してpanicしているので、github.com/dafiti/echo-middleware と同様に採用できなさそうです。
いろいろ見てきましたが、どのライブラリも自分のユースケースには合わなさそうです。ISUCON10予選本番では、echoとNew Relicを組み合わせる例をその場で探して出てきたものを試した結果、 github.com/jessie-codes/echo-relic を採用してしまったのでなかなか大変でした。
エンドポイントにかかった時間は計測できたけど、分散トレーシングとか、SQLにかかった時間が取れてない!!! なんでなの!! と言いながらechorelicのコードを読みに行きました。どうもcontextが渡ってない予感がする? echoのミドルウェアを書くのか? と思いつつ echo.WrapMiddleware っていうのを使うとなんか書けそう、ということでガッと試しました。
ISUCON10 予選突破した #isucon - 私が歌川です
作った
e.Use(なんとか(app))
ってやるだけでエージェントが有効になってほしい- 全てのエンドポイントを計測したいので、エンドポイントごとにwrapするよりミドルウェアにしたほうが早い
- エージェントの初期化処理はライブラリを使う側で行いたい
- エージェントの設定を自由に行いたい
- エラーハンドリングはこっちでやりたい (panicしないでほしい)
- トランザクションの名前は
c.Path()
をもとにしたいGET /api/chair/1
ではなくGET /api/chair/:id
にしたい
- できるだけNew Relicの公式ライブラリが提供する仕組みに乗っかりたい
以上の要件を満たすようなechoミドルウェアはなさそう、そしてちょっと実装すれば欲しいものはできそう、ISUCON10本戦でもNew Relicを使うことになるだろう、ということで作りました。go get github.com/utgwkk/echo-newrelic/v3
して今すぐご利用いただけます。ISUCON10予選の感想戦がてらちょっと試してみて、New Relicにデータが送信されていることは確かめました。