💡
【golang】slogを雑に使ってみる
golang1.21から標準パッケージに追加されたslogを雑に使ってみたメモ
今やっているPJに合わせて作ったものなので汎用性は無い。(contextから値取り出してログのパラメータに設定するとか)
slogを直接呼び出す
package logger
import (
"log/slog"
"os"
)
func InitLogger(logLevel string, format string) {
logger := newSlogLogger(logLevel, format)
slog.SetDefault(logger)
}
func newSlogLogger(logLevel string, format string) *slog.Logger {
handler := newJSONHandler(logLevel, format)
return slog.New(handler)
}
func newJSONHandler(logLevel string, format string) slog.Handler {
var level slog.Level
switch logLevel {
case "debug":
level = slog.LevelDebug
case "info":
level = slog.LevelInfo
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
default:
level = slog.LevelInfo
}
opt := slog.HandlerOptions{
AddSource: true,
Level: level,
}
switch format {
case "text":
return slog.NewTextHandler(os.Stdout, &opt)
case "json":
return slog.NewJSONHandler(os.Stdout, &opt)
default:
return slog.NewJSONHandler(os.Stdout, &opt)
}
}
通常のslogの関数slog.Info()
等で呼び出す。
自前のロガー経由でslogを呼び出す
PJの思想の中にはcontextにロガーを与えたり、シングルトンなロガーを定義したりというロガーへの要件があることもあります。
その場合は以下のようなロガーstructを定義して使用するのが良いと思います。
以下では各ログレベルのログメソッドの第一引数をmsgとしていますが、これは私の好みなだけなので好きに定義を変えてしまってください。
また、contextから値を呼び出してログに設定する実装は以下には無いです、必要な場合は各メソッドの引数にcontextを追加して
package logger
import (
"context"
"log/slog"
"os"
"runtime"
"time"
)
type MyLogger interface {
Log(...interface{})
Debug(msg string, args ...interface{})
Info(msg string, args ...interface{})
Warn(msg string, args ...interface{})
Error(msg string, err error, args ...interface{})
}
type SlogLogger struct {
Logger *slog.Logger
}
func NewSlogLogger(logLevel string, format string) *SlogLogger {
handler := newJSONHandler(logLevel, format)
logger := slog.New(handler)
return &SlogLogger{
Logger: logger,
}
}
func newJSONHandler(logLevel string, format string) slog.Handler {
var level slog.Level
switch logLevel {
case "debug":
level = slog.LevelDebug
case "info":
level = slog.LevelInfo
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
default:
level = slog.LevelInfo
}
opt := slog.HandlerOptions{
AddSource: true,
Level: level,
}
switch format {
case "text":
return slog.NewTextHandler(os.Stdout, &opt)
case "json":
return slog.NewJSONHandler(os.Stdout, &opt)
default:
return slog.NewJSONHandler(os.Stdout, &opt)
}
}
func (l SlogLogger) Log(args ...interface{}) {
if l.Logger == nil {
return
}
if len(args) == 0 {
return
}
// 第一引数がstringの場合は、それをメッセージとして扱う
if msg, ok := args[0].(string); ok {
args = args[1:]
l.Info(msg, args...)
} else {
// stringでない場合は、そのままInfoとして扱う
l.Info("", args...)
}
}
func (l SlogLogger) Debug(msg string, args ...interface{}) {
l.log(slog.LevelDebug, msg, nil, args...)
}
func (l SlogLogger) Info(msg string, args ...interface{}) {
l.log(slog.LevelInfo, msg, nil, args...)
}
func (l SlogLogger) Warn(msg string, args ...interface{}) {
l.log(slog.LevelWarn, msg, nil, args...)
}
func (l SlogLogger) Error(msg string, err error, args ...interface{}) {
l.log(slog.LevelError, msg, err, args...)
}
func (l SlogLogger) log(level slog.Level, msg string, err error, args ...interface{}) {
if l.Logger == nil {
return
}
// トレース情報が欲しいので、runtime.Callersを使って呼び出し元の情報を取得
// 参考: https://pkg.go.dev/log/slog#hdr-Wrapping_output_methods
var pcs [1]uintptr
runtime.Callers(3, pcs[:]) // skipの値は呼び出し方によって変わるため呼び出し階層が変わった場合等に注意
now := time.Now()
r := slog.NewRecord(now, level, msg, pcs[0])
if err != nil {
r.AddAttrs(slog.Any("err", err))
}
r.Add(args...)
_ = l.Logger.Handler().Handle(context.Background(), r)
}
Discussion