Go言語には従来 log パッケージを標準パッケージのロガーとして提供していましたが複雑な構造化ロガーとしての機能を提供していませんでした。

そのため zap, logrus, zerolog などのサードパーティーのツールが利用されてきていました。
そのような背景がありGo 1.21 からはGo言語標準ライブラリとして slog が採用されて構造化ロギングにも対応する形となりました。

今回は New Relic の Go Agent で slog の Logs in Context に対応したのでその方法をご紹介します。
Logs in Context については別の記事で詳細に触れられていますので別の記事をご参照ください。

https://newrelic.com/jp/blog/how-to-relic/troubleshooting-logs-in-context

slog とは

slog(読み方はスロッグ) は先述でも触れたようにGo1.21から採用されたGo言語の標準構造化ロガーとして採用されており従来のサードパーティのツールを利用する方法からGo言語が標準で提供する形になりますのでツールごとの一貫性がないことの課題やコミュニティの強力なサポートを受けられるなどの期待があります。

ドキュメントにも利用方法がしっかりと書かれており使い方もシンプルなので簡単に利用しやすいものとなっています。
https://pkg.go.dev/log/slog

slog を用いた Logs in Context

Go Agent v3.30.0 から slog を用いた Logs in Context に対応しました。
この対応は nrslog というライブラリを用いて slog のログ情報に対して New Relic APM と紐づけられる情報を装飾して APM Agent から直接ログを転送できるようにするという対応をできるようになったことを指しています。

APM Agent からのアプリケーションログの転送については別の記事で紹介しておりますのでそちらも合わせて見ていただければと思います。
https://newrelic.com/jp/blog/nerdlog/apm-native-log-sending

本機能を試すためのサンプルリポジトリも用意しています。
コードの全体像としては以下のようになっています。

package main

import (
	"log/slog"
	"os"
	"time"

	"github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrslog"
	"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
	app, err := newrelic.NewApplication(
		newrelic.ConfigAppName("slog-nr-example"),
		newrelic.ConfigAppLogEnabled(true),
		newrelic.ConfigLicense(os.Getenv("NEWRELIC_LICENSE_KEY")),
	)
	if err != nil {
		panic(err)
	}

	app.WaitForConnection(time.Second * 5)
	log := slog.New(nrslog.TextHandler(app, os.Stdout, &slog.HandlerOptions{}))

	log.Info("I am a log message")

	txn := app.StartTransaction("example transaction")
	txnLogger := nrslog.WithTransaction(txn, log)
	txnLogger.Info("I am a log inside a transaction")

	// pretend to do some work
	time.Sleep(500 * time.Millisecond)
	txnLogger.Warn("Uh oh, something important happened!")
	txn.End()

	log.Info("All Done!")

	app.Shutdown(time.Second * 10)
}

New Relic Go Agent を動かすための  github.com/newrelic/go-agent/v3/newrelic と Logs in Context を実現するための github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrslog を利用します。

log := slog.New(nrslog.TextHandler(app, os.Stdout, &slog.HandlerOptions{})) 

この実装によって nrslog が slog のログ情報をラップして New Relic と紐づけできる情報を装飾(New Relic のドキュメント上はDecorationといいます)して APM で登録されたアプリケーションと紐づけられた状態になります

txn := app.StartTransaction("example transaction")
txnLogger := nrslog.WithTransaction(txn, log)

またこれらの処理で Transaction に対してログを紐づけることができるのでエラー発生時に紐づけられたり分散トレースのデータに紐づけさせることができます。

New Relic への接続処理や slog の初期化処理をすることで必要なものは揃っていますので後はプログラムを go run main.go で実行して見ましょう。
ターミナルにこのような情報が出力されるはずです。New Relic のライセンスキーが環境変数として必要なので以下の手順で実行します。

export NEWRELIC_LICENSE_KEY=<your license key>

go run ./main.go

time=2024-02-19T13:21:25.588+09:00 level=INFO msg="I am a log message"
time=2024-02-19T13:21:25.589+09:00 level=INFO msg="I am a log inside a transaction"
time=2024-02-19T13:21:26.091+09:00 level=WARN msg="Uh oh, something important happened!"
time=2024-02-19T13:21:26.094+09:00 level=INFO msg="All Done!"

New Relic の画面を確認してもらって APM の画面から Logs をクリックしてもらってログが出力されてているかどうかを確認してください。

 

最後に

APM の画面にログが出力されていると Logs in Context としての連携は成功で手順としては以上になります。

他にもエラーを発生させたり分散トレースの画面で処理に対してログが紐づけされているか確認してください。
Logs in Context を利用することで様々な情報と連携することができるようになるのでより皆さんの問題発見や MTTR を向上してくれる助けの一つになります。

また Logs in Context のログ情報をどのように New Relic に転送するのかというブログ記事もありますのでこちらも運用時には併せてご確認ください。
https://newrelic.com/jp/blog/best-practices/patterns-using-logs-in-context