integrate with log/slog
The log/slog package will be in Go 1.21.
Consider adding a Logger that wraps a slog.Handler.
It's not clear to me how to build a Go kit Logger that wraps a slog.Handler. I hope you can provide some guidance!
I guess it would have to look something like the following.
type slogLogger struct {
handler slog.Handler
}
func NewSlogLogger(h slog.Handler) Logger {
return &slogLogger{handler: h}
}
func (sl *slogLogger) Log(keyvals ...any) error {
ctx := context.Background() // I think this is right
rec := ???
return sl.handler.Handle(ctx, rec)
}
What isn't clear to me is how to construct a slog.Record. The slog.NewRecord constructor takes
- A time argument, which we can produce easily enough.
- A level argument, which is tricky — the Go kit logger implements levels via package level which basically models them as a key=val pair in the log event, same as any other. Log events are not guaranteed to have a level at all, and even when a level does exist, it won't necessarily map to the slog.Level definitions. I guess a path forward here would be to assume that each Go kit log event either has a level (via some specific key) or, if it doesn't, it will be assigned a "default" level. That's somewhat unsatisfying; is there a better approach?
- A msg argument, which has no obvious solution — the Go kit logger doesn't mandate that log events include any specific key, so there's no way to reliably get a "msg" from a set of keyvals. I guess a path forward here would be to simply assume that each Go kit log event has a "msg" key, and parse/use the corresponding val, even if it is the empty string. That's somewhat unsatisfying; is there a better approach?
- A pc uintptr argument, which we can definitely just assign — but there are many subtle issues with that approach, specifically with helpers like log.With, or, more generally, with any kind of Logger decorator. Do you have some guidance on producing a pc uintptr which is robust to these use cases?
Looking forward to any thoughts you might have.
- level: if you see a level.Key(), map the value. It looks like the four levels you name are the same that slog names, so the mapping seems straightforward. I would use Info if no level is provided. Most people think of it as the default.
- msg: your suggestion seems as good as any.
- pc: I may not understand your concern with
log.Withand other decorators. I think the pc should point to theLogcall, which is the line that actually pushes out the log event. With slog, the code
would create a source attribute pointing to the second line; nothing about the first line is recorded anywhere.logger := logger.With("a", 1) logger.Info("msg")
I'm not too worried about getting the mapping perfect, or even great. I think there will be a lot of value in just having the code in your repo, so go-kit/log users will see how it's done. For example, if someone consistently uses "message" instead of "msg", they can copy your code, or maybe suggest adding an option to NewSlogLogger. You may want to consider adding an options struct at the beginning, or functional options if that's what you prefer.
What about wrapping the *slog.Logger.<LogFunc> method? Something like..
type logFunc func(msg string, keysAndValues ...interface{})
func (l logFunc) Log(keyvals ...interface{}) error {
l("", keyvals...)
return nil
}
type config struct {
level slog.Level
}
type Option func(*config)
func WithLevel(level slog.Level) Option {
return func(l *config) {
l.level = level
}
}
func NewSlogLogger(logger *slog.Logger, options ...Option) kitlog.Logger {
c := &config{}
for _, option := range options {
option(c)
}
var logFunc logFunc
switch c.level {
case slog.LevelDebug:
logFunc = slog.Debug
case slog.LevelInfo:
logFunc = slog.Info
case slog.LevelWarn:
logFunc = slog.Warn
case slog.LevelError:
logFunc = slog.Error
default:
logFunc = slog.Info
}
return logFunc
}
example with log/slog: https://github.com/isauran/logger
@xyluet thank you!
If anyone is interested, I put together an adapter from go-kit/log to slog, for Grafana Mimir. I can't guarantee it works perfectly (especially uncertain about WithGroup), but it does have tests.
@aknuds1 and I have extracted the adapter out of mimir and generalized it into a standalone library. Hope it helps :+1:
https://github.com/tjhop/slog-gokit https://pkg.go.dev/github.com/tjhop/slog-gokit
Thanks, adding it to https://go.dev/wiki/Resources-for-slog.