uber-go/zap
を使いつつNew Relic Logs in contextを利用するためのライブラリを作り始めた。
New Relic logs in context
New Relic Oneを使っていると分散トレーシングを取得することができる。
そしてNew Relic Oneはログも取得することができるためログに適切な情報を含んでいれば分散トレースとログを紐付けて確認することができる。
GoでNew Relic Oneを利用する場合は newrelic/go-agent/v3
を使ってもろもろを仕込む。
newrelic/go-agent/v3
に含まれているlogs in context用の実装は logrus
logger向けの実装しかなく、私が普段使っている zap
logger向けの実装が存在しなかった。
そこで、せっかくなので自作してみようと考えた。
logs in contextを実現するために必要なこと
New Relicである分散トレースにログを結びつけるにはどうしたらいいか?
既存のプラグインのコードを確認したところ、ログのJSONのルートへlogscontext
pkgに定義されるキーワードを使って分散トレースIDなどを埋め込めば良さそうだった。
Webアプリケーションを実装している場合、これらの情報はcontext.Context
の中からnewrelic.LinkingMetadata
を取り出せば取得できる。
zap
logger自体はcontext.Context
を扱うようなインターフェイスではないため、まずはcontext.Context
から[]zap.FIeld
として情報を取り出すヘルパー関数だけ作ることにした。
nrzap
nrzapはzap
でNew Relic Logs in contextをよしなにするための実装ライブラリ(の予定)だ。
GtNrMetadataFields
2021/03/21現在nrzapは1つの機能しか提供していない。
GtNrMetadataFields
関数は分散トレースIDなどの情報をzap
loggerのInfo
メソッドやError
メソッドの引数に渡す[]zap.Field
として生成する。
単純な利用方法だとこのようになる。
func ExampleHandler(w http.ResponseWriter, r *http.Request) {
logger, _ := zap.NewProduction()
defer logger.Sync()
// nrgorillaなどでcontextから*newrelic.Transactionが取れる前提
nrfs := nrzap.GetNrMetadataFields(r.Context())
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
nrfs...,
)
}
このようにロギングメソッドを呼ぶと、New Relicが識別するためのログトレースIDなどがログの中へ含まれるようになる。
注意
なお、今回作成した関数はあくまでログのJSONに情報を埋め込むだけなので、実際に利用するには次の前提条件がある。
- すでに分散トレースをNew Relic One上で計測する仕組みが存在する
- 別途アプリケーションのログをNew Relicに送信している
この条件を満たしている状態でライブラリを使ってログを加工すればlogs in contextが実現する。
終わりに
実はlogs in contextはまだあまり使いこなせていないので、もうすこし使い込んだら他の実装も増えるかもしれない。