関連記事
Goメモ-477 (slogメモ-01)(基本的な使い方) - いろいろ備忘録日記
Goメモ-478 (slogメモ-02)(構造化ログの出力) - いろいろ備忘録日記
Goメモ-479 (slogメモ-03)(デフォルトロガー) - いろいろ備忘録日記
Goメモ-480 (slogメモ-04)(従来のlogパッケージとの連携) - いろいろ備忘録日記
Goメモ-482 (slogメモ-05)(テキスト形式のログ) - いろいろ備忘録日記
Goメモ-483 (slogメモ-06)(JSON形式のログ) - いろいろ備忘録日記
Goメモ-484 (slogメモ-07)(動的にログレベルを変更) - いろいろ備忘録日記
Goメモ-485 (slogメモ-08)(グループ (1)) - いろいろ備忘録日記
Goメモ-486 (slogメモ-09)(グループ (2)) - いろいろ備忘録日記
Goメモ-487 (slogメモ-10)(機密情報などのマスキング) - いろいろ備忘録日記
Goメモ-488 (slogメモ-11)(カスタムログレベル) - いろいろ備忘録日記
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。
今更ながら、Go 1.21で導入された log/slog
を使ってみたりしています。
少しづつメモしていきます。今回はcontext.Contextとの連携について。
slogでは、InfoContext
メソッドなど、context.Context を渡せる関数がありますが、
コンテキストの中の情報をどのようにログに出力するかは自分で処理しないといけません。
つまり、カスタムハンドラを作成する必要があります。
サンプル
ctxkey.go
package main import "context" type ctxKey struct{} var ( msgKey = ctxKey{} ) func ctxValue(ctx context.Context) string { return ctx.Value(msgKey).(string) } func setCtxValue(ctx context.Context, message string) context.Context { return context.WithValue(ctx, msgKey, message) }
handler.go
package main import ( "context" "log/slog" ) // ContextHandler は、[context.Context]のキーをログの出力に加えるカスタムハンドラです。 type ContextHandler struct { slog.Handler } // NewContextHandler は、指定されたハンドラを元に [ContextHandler] を作成します。 func NewContextHandler(handler slog.Handler) *ContextHandler { return &ContextHandler{handler} } // Handle implements [slog.Handler.Handle]. func (me *ContextHandler) Handle(ctx context.Context, r slog.Record) error { if message := ctxValue(ctx); message != "" { r.AddAttrs(slog.String("ctxkey", message)) } return me.Handler.Handle(ctx, r) } // WithAttrs implements [slog.Handler.WithAttrs]. func (h *ContextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return &ContextHandler{h.Handler.WithAttrs(attrs)} } // WithGroup implements [slog.Handler.WithGroup]. func (h *ContextHandler) WithGroup(name string) slog.Handler { return &ContextHandler{h.Handler.WithGroup(name)} }
main.go
package main import ( "context" "errors" "log" "log/slog" "os" "time" ) const ( MainTimeout = time.Second ProcTimeout = 50 * time.Millisecond ) var ( ErrMainTooSlow = errors.New("(MAIN) TOO SLOW") ErrProcTooSlow = errors.New("(PROC) TOO SLOW") ) func init() { log.SetFlags(0) } func main() { var ( rootCtx = context.Background() mainCtx, mainCxl = context.WithTimeoutCause(rootCtx, time.Second, ErrMainTooSlow) procCtx = run(mainCtx) err error ) defer mainCxl() select { case <-mainCtx.Done(): err = context.Cause(mainCtx) case <-procCtx.Done(): if err = context.Cause(procCtx); errors.Is(err, context.Canceled) { err = nil } } if err != nil { log.Fatal(err) } } func run(pCtx context.Context) context.Context { var ( ctx, cxl = context.WithCancelCause(pCtx) ) go func() { cxl(proc(ctx)) }() go func() { <-time.After(ProcTimeout) cxl(ErrProcTooSlow) }() return ctx } func proc(pCtx context.Context) error { // // slog には、context.Context を受け取るログ出力メソッドも存在する。 // // - DebugContext // - InfoContext // - WarnContext // - ErrorContext // - Log // // 上記のメソッドには、context.Context を指定できるが // 指定しただけだと context.Context の中に設定されている // キー/値 は、何も出力されない。 // // 自前で設定した context.Context のキー/値 をログに出力するには // カスタムハンドラを作成する必要がある。 // // カスタムハンドラを作成する場合の注意点として // Handleメソッドのみをオーバーライドして利用しているサンプルが // 結構あるが、これだと slog.Logger.With() や slog.Logger.WithGroup() などで // 情報を追加したロガーを利用する場合に、元となるハンドラが利用されてしまい // context.Contextの情報が出力されなくなってしまうことに注意が必要。 // slog.Handler.WithAttrs(), slog.Handler.WithGroup() もオーバーライドしておく。 // var ( ctx = setCtxValue(pCtx, "helloworld") level = slog.LevelInfo opt = &slog.HandlerOptions{ Level: level, ReplaceAttr: replaceAttr, } writer = os.Stdout handler = NewContextHandler(slog.NewJSONHandler(writer, opt)) rootLogger = slog.New(handler) logger = rootLogger.With("id", 1) ) logger.DebugContext(ctx, "Call DebugContext") logger.InfoContext(ctx, "Call InfoContext") logger.WarnContext(ctx, "Call WarnContext") logger.ErrorContext(ctx, "Call ErrorContext") return nil } func replaceAttr(group []string, a slog.Attr) slog.Attr { if a.Key == slog.TimeKey { return slog.Attr{} } return a }
実行すると以下のように出力されます。
Taskfile.yml
# https://taskfile.dev version: '3' tasks: default: cmds: - task: run run: cmds: - go run .
shell
$ task task: [run] go run . {"level":"INFO","msg":"Call InfoContext","id":1,"ctxkey":"helloworld"} {"level":"WARN","msg":"Call WarnContext","id":1,"ctxkey":"helloworld"} {"level":"ERROR","msg":"Call ErrorContext","id":1,"ctxkey":"helloworld"}
try-golang/examples/slog at main · devlights/try-golang · GitHub
参考情報
- Structured Logging with slog
- log/slog
- A Guide to Writing slog Handlers
- awesome-slog
- Goのslog使い方まとめ
- Go1.21で登場したlog/slogパッケージのパフォーマンスを徹底解説!!
- Go1.21 log/slogパッケージ超入門
- Go公式の構造化ロガー(として提案されている)slogを触ってみたメモ
- slog を触る(Group, Context)
- 構造化ログと実装 -Goのslogによる実践-
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。