I like to use log/slog in Go. And I use custom LogValue() functions to have
more control over how my structs get logged. But sometimes getting the right
format is tedious.
Logging pointers leaves a bit to be desired.
// LogValue implements the slog.LogValuer interface.
func (s *Struct) LogValue() slog.Value {
attrs := []slog.Attr{}
if s.Pint == nil {
attrs = append(attrs, slog.Any("s.Pint", s.Pint))
} else {
attrs = append(attrs, slog.Int("s.PInt", *s.Pint))
}
return slog.GroupValue(
slog.Int("s.Int", s.Int), // logged as 123
slog.Any("s.PInt", s.PInt), // pointer, logged as 0x123456789012
slog.Any("s.Pint", s.Pint), // nil logged as <nil>
slog.Int("*s.Pint", *s.Pint), // deref, logged as 123, panic on nil
attrs...
)
}
Generics can be employed to avoid writing long nil checks.
func nilSlogAttr[T comparable](s string, x any, f func(string, T) slog.Attr) slog.Attr {
switch v := x.(type) {
case *T:
if v == nil {
return slog.Any(s, v)
}
return f(s, *v)
default:
return slog.Any(s, v)
}
}
func (s *Struct) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("s.Int", s.Int),
nilSlogAttr[int]("s.Pint", s.Pint, slog.Int),
}
}