🔖
slog.LogValuer の実装を自動生成するツールを作った
Go 1.21 で新たに log/slog という構造化ロギングのための標準パッケージが追加されました。パッケージの内容や使い方は Go Doc や先日公開された Go Blog などで確認できます。
slog には slog.LogValuer というインターフェースが定義されており、この実装を満たすことでログに出力する内容をカスタマイズできます。Go Doc に例がありますが、構造体のフィールドをグループ化したり、ログに出力したくない場合に代わりの文字を出力したりすることができます。
type Name struct {
First, Last string
}
func (n Name) LogValue() slog.Value {
return slog.GroupValue(
slog.String("first", n.First),
slog.String("last", n.Last))
}
n := Name{"Perry", "Platypus"}
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
}}))
logger.Info("mission accomplished", "agent", n)
// Output:
//
// level=INFO msg="mission accomplished" agent.first=Perry agent.last=Platypus
// JSON Output would look in part like:
// {
// ...
// "msg": "mission accomplished",
// "agent": {
// "first": "Perry",
// "last": "Platypus"
// }
// }
type Token string
func (Token) LogValue() slog.Value {
return slog.StringValue("REDACTED_TOKEN")
}
t := Token("shhhh!")
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
}}))
logger.Info("permission granted", "user", "Perry", "token", t)
// Output:
//
// level=INFO msg="permission granted" user=Perry token=REDACTED_TOKEN
構造体のフィールドをグループ化するためには、LogValue
メソッド内でグルーピングしたいフィールドを列挙する必要があります。これについて、slog.LogValuer
の実装を自動生成する簡易的なツール Logvaluer を作ってみました。コマンドライン引数で指定された型を探して LogValue
メソッドを実装します。現状は構造体タグによってフィールドを出力対象外にしたり、ログに値を出力したくないフィールドで代替文字を出力したりできます。
例えば以下の構造体については
type Foo struct {
Str string
pstr string
IgnoreStr string `ignored:"true"`
Passwd string `mask:"true"`
Flo float64
M int
N int64
L uint64
Flag bool
Date time.Time
Dur time.Duration
}
このようなコードを生成します。
func (f Foo) LogValue() slog.Value {
return slog.GroupValue(
slog.String("Str", f.Str),
slog.String("pstr", f.pstr),
slog.String("Passwd", "MASK"),
slog.Float64("Flo", f.Flo),
slog.Int("M", f.M),
slog.Int64("N", f.N),
slog.Any("L", f.L),
slog.Bool("Flag", f.Flag),
slog.Time("Date", f.Date),
slog.Duration("Dur", f.Dur),
)
}
他の構造体タグを使ってログ出力をカスタマイズするとより便利かもしれません。json タグを見るようにしたりするとか
Discussion