My External Storage

Mar 21, 2021 - 3 minute read - Comments - go newrelic

[Go] go.uber.org/zapでNew Relic logs in contextをするためのライブラリを書き始めた

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を取り出せば取得できる。
zaplogger自体はcontext.Contextを扱うようなインターフェイスではないため、まずはcontext.Contextから[]zap.FIeldとして情報を取り出すヘルパー関数だけ作ることにした。

nrzap

nrzapはzapでNew Relic Logs in contextをよしなにするための実装ライブラリ(の予定)だ。

GtNrMetadataFields

2021/03/21現在nrzapは1つの機能しか提供していない。
GtNrMetadataFields 関数は分散トレースIDなどの情報をzaploggerの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はまだあまり使いこなせていないので、もうすこし使い込んだら他の実装も増えるかもしれない。

参考

関連記事