📚

slog を触る(Group, Context)

2023/09/27に公開

はじめに

以前、Go のバージョン 1.21 のリリースで導入された slog パッケージについての記事(slog を触る(ログレベル, フォーマット))を書きました。今回は、その続編として、前回の記事で触れなかった Group と Context を試してみたいと思います。

Group

slog には、ログメッセージをグループ化するための Group という機能があります。これにより、関連するログメッセージを一つのグループとしてまとめることができ、ログの分析やデバッグがしやすくなります。JSON フォーマットの場合、ネストを作るイメージです。

HTTP リクエストのログを取る際の例です。

package main

import (
	"log/slog"
	"os"
)

func main() {
	logger := slog.New(
		slog.NewJSONHandler(
			os.Stdout,
			nil,
		),
	)

	logger = logger.WithGroup("http")
	logger.Info("request", slog.String("url", "/hoge"), slog.Int("status", 200))
	// {"time":"2023-09-26T08:09:04.516553+09:00","level":"INFO","msg":"request","http":{"url":"/hoge","status":200}}
}

Context

slog では、DebugContext, InfoContext など、 Context.context を利用できる関数があります。ただ、これらの関数に、任意のキー・バリューを入れた Context.context を渡せばよしなにアウトプットしてくれるというわけではなく、コンテキストを扱う処理を含んだハンドラを自身で用意してあげる必要があります。
構造体の埋め込みを利用して、既存のハンドラを利用しつつ、新しいハンドラを作ってみます。

package main

import (
	"context"
	"log/slog"
	"os"
)

type MyHandler struct {
	slog.Handler
}

type traceIDKey struct{}

var tidKey = traceIDKey{}

func (h MyHandler) Handle(ctx context.Context, r slog.Record) error {
	r.AddAttrs(slog.String("traceID", ctx.Value(tidKey).(string)))
	return h.Handler.Handle(ctx, r)
}

func main() {
	myHandler := MyHandler{
		slog.NewJSONHandler(
			os.Stdout,
			nil,
		),
	}
	logger := slog.New(myHandler)

	ctx := context.WithValue(context.Background(), tidKey, "12345")
	logger.InfoContext(ctx, "Hello World1", "foo1", "bar1")
	// {"time":"2023-09-27T12:42:07.074539+09:00","level":"INFO","msg":"Hello World1","foo1":"bar1","traceID":"12345"}

	slog.SetDefault(logger)
	slog.InfoContext(ctx, "Hello World2", "foo2", "bar2")
	// {"time":"2023-09-27T12:42:07.075095+09:00","level":"INFO","msg":"Hello World2","foo2":"bar2","traceID":"12345"}
}

構造体 MyHandler を定義し、slog.Handler をフィールドに指定し埋め込んでいます。
さらにその構造体にトレースIDをログに追加する処理を書き、その直後に元のハンドラの Handle メソッドを呼び出す処理を、MyHandlerHandle メソッドに記述しています。
呼び出し側では、今回用意したハンドラを使ってロガーを作成することで、コンテキストを渡して InfoContext を呼ぶと、ハンドラに書いた通りに traceID フィールドがログに追加されていることが確認できます。
ついでに、作成したロガーをデフォルトロガーに設定することで、slog.InfoContext という slog 側で用意された関数呼び出しでも traceID が表示されることも確認しています。

終わりに

本記事では、Go の slog パッケージにおける Group と Context の機能に焦点を当て、それぞれの特徴や利用方法について書きました。Group によるログのグループ化は、関連するログメッセージを一つのまとまりとして扱うことで、ログの分析やデバッグをより効率的に行うことができます。一方、Context を活用することで、ログにコンテキスト情報を追加することが可能となり、より詳細な情報を持ったログを出力することができます。またハンドラの作成を通して、独自のニーズに合わせたログ出力ができることも示せたかと思います。

最後に、slog はまだ新しいライブラリであるため、今後のアップデートや定まっていくベストプラクティスを追うことも重要かと思います。(自戒も込めて)
本記事が、皆様の Go 開発におけるロギングの参考となれば幸いです。

参考文献

Discussion