GoのWebアプリコードにNew Relic APMでSpan(Segment)を計測するコードを自動挿入するOSSを作った。
New RelicをGoのアプリケーションへ導入するときに利用をオススメする。
TL;DR
- New Relic APMは分散トレーシングなどにも対応しているAPM
- Spanを計測するためには、関数ごとにSegmentの処理を差し込む必要がある
- 指定ディレクトリ以下にあるGoのコードにFunction segmentsを自動挿入するOSSを作った
- https://github.com/budougumi0617/nrseg
New Relic APM
New Relic APMはNew Relic Oneに含まれている分散トレーシングなどにも対応したモニタリングツールだ。
個人でも業務でも使い始めた1のだが、エージェントを挿入すれば簡単にアプリケーションを丸裸にできる。
GoのWebアプリケーションにNew Relic APMを導入する手順
APMだけならば導入もすぐ完了する。
- Install New Relic for Go
ざっくりとまとめると、まずアプリ起動時にNew Relicと接続するためのApplication
オブジェクトを生成する。
app, err := newrelic.NewApplication(
newrelic.ConfigAppName("Your Application Name"),
newrelic.ConfigLicense("__YOUR_NEW_RELIC_LICENSE_KEY__"),
)
そして各種ハンドラーのエンドポイントを次のようにラップする。
http.HandleFunc(newrelic.WrapHandleFunc(app, "/users", usersHandler))
主要なフレームワーク、MySQLなどならば専用のラッパーも用意されている。
Spanの計測が大変
これでNew Relic上からTraceを確認できるようになる。
ここまでの対応ならば、すぐにできるだろう。しかし、ここまでだとTraceは取得できるようになっても、Span2が取得できない。
- Instrument Go transactions1
- Instrument Go segments
Goのアプリケーション上でSpanを取得するには、関数やメソッドごとにFunction segmentsを明示的に埋め込んでいく必要がある。
defer txn.StartSegment("mySegmentName").End()
これを中規模以上の既存のアプリケーションに実施しようとすると、関数/メソッド1つ1つを修正していく必要があり非常に面倒なことになる。
nrseg
上記の面倒くささを解消するためのコマンドラインツールがnrsegだ。
Macの場合はBrewでインストールすることができる。
$ brew install budougumi0617/tap/nrseg
このような既存のコードがあったとする。
package input
import (
"context"
"fmt"
"net/http"
)
type FooBar struct{}
func (f *FooBar) SampleMethod(ctx context.Context) {
fmt.Println("Hello, playground")
fmt.Println("end function")
}
func SampleFunc(ctx context.Context) {
fmt.Println("Hello, playground")
fmt.Println("end function")
}
func SampleHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Hello, %q", req.URL.Path)
}
// nrseg:ignore you can be ignored if you want to not insert segment.
func IgnoreHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Hello, %q", req.URL.Path)
}
上記のコードを含んだディレクトリにnrsegを実行する。
$ nrseg .
そうすると、上記のようなコードが次のようになる。
package input
import (
"context"
"fmt"
"net/http"
"github.com/newrelic/go-agent/v3/newrelic"
)
type FooBar struct{}
func (f *FooBar) SampleMethod(ctx context.Context) {
defer newrelic.FromContext(ctx).StartSegment("foo_bar_sample_method").End()
fmt.Println("Hello, playground")
fmt.Println("end function")
}
func SampleFunc(ctx context.Context) {
defer newrelic.FromContext(ctx).StartSegment("sample_func").End()
fmt.Println("Hello, playground")
fmt.Println("end function")
}
func SampleHandler(w http.ResponseWriter, req *http.Request) {
defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End()
fmt.Fprintf(w, "Hello, %q", req.URL.Path)
}
// nrseg:ignore you can be ignored if you want to not insert segment.
func IgnoreHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Hello, %q", req.URL.Path)
}
nrseg:ignore
とコメントした関数以外のすべての関数/メソッドに対してSpanを生成するためのNew Relicライブラリの呼び出しが自動挿入される。
nrsegコマンドはディレクトリの下のすべてのGoコード3に対して上記の修正を行なう。
既存アプリケーションのコード量が多ければ多いほど便利!
技術的なところ
やっていることはディレクトリをトラバースしながら各コードのASTに無理やり該当コードを挿入してファイルを更新している。
挿入時にシグネチャの中にあるcontext.Context
の変数名を取得したり、細かいASTのパースも行なっている。
めんどくさかったところはおいおい個別の記事でメモを公開したいと思う。
終わりに
今年の目標(2020年のTry)として「ひとつ突き詰めて実装してみる
」を掲げている。
nrseg
はGoのASTと仲良くなれるし業務で利用しはじめたため、目標とマッチしつつモチベーションも保ちやすい。
また、New Relicを使って今年のISUCONを出ることを想定して「(サブミット時に)挿入したコードをすべて削除する」サブコマンドなども用意したいと思う。
引き続き機能強化していきたい。
参考資料
- https://github.com/budougumi0617/nrseg
- Install New Relic for Go
- Instrument Go segments
- 2020年振り返り