pgx
pgx copied to clipboard
slog compatible structured logging
Hello & thanks for the awesome project!
I have recently been playing with slog, which is a new (currently experimental) structured logging interface for the Go programming language. In one of the upcoming Go releases it will be merged into the standard library.
Recommended resources:
- https://pkg.go.dev/golang.org/x/exp/slog
- https://www.youtube.com/watch?v=gd_Vyb5vEw0
- https://github.com/golang/go/issues/56345
The function signatures for tracelog.QueryTracer.Log
and slog.Log
are almost compatible:
Log(ctx context.Context, level LogLevel, msg string, data map[string]any)
https://godocs.io/github.com/jackc/pgx/v5/tracelog#Logger
func (l *Logger) Log(ctx context.Context, level Level, msg string, args ...any)
https://pkg.go.dev/golang.org/x/exp/slog#Logger.Log
It would be awesome if they were fully compatible, since then a slog
Logger can be passed as-is to pgx/tracelog -- though I'm not sure if anything can be done about that at this point.
Alternatively, I came up with the following adapter for tracelog / slog:
package adapter
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/tracelog"
"golang.org/x/exp/slog"
)
// This type implements the tracelog.Logger interface by wrapping a slog.Logger
// https://godocs.io/github.com/jackc/pgx/v5/tracelog
// https://godocs.io/github.com/jackc/pgx/v5#QueryTracer
// https://godocs.io/golang.org/x/exp/slog
type Logger struct {
slogger *slog.Logger
}
func NewTracerLogger(l *slog.Logger) pgx.QueryTracer {
return &tracelog.TraceLog{
Logger: &Logger{slogger: l},
LogLevel: tracelog.LogLevelTrace,
}
}
func (l *Logger) Log(ctx context.Context, level tracelog.LogLevel, msg string, data map[string]any) {
var attrs []slog.Attr
for k, v := range data {
attrs = append(attrs, slog.Any(k, v))
}
l.slogger.LogAttrs(ctx, translateLevel(level), msg, attrs...)
}
func translateLevel(level tracelog.LogLevel) slog.Level {
switch level {
case tracelog.LogLevelTrace:
return slog.LevelDebug
case tracelog.LogLevelDebug:
return slog.LevelDebug
case tracelog.LogLevelInfo:
return slog.LevelInfo
case tracelog.LogLevelWarn:
return slog.LevelWarn
case tracelog.LogLevelError:
return slog.LevelError
case tracelog.LogLevelNone:
return slog.LevelError
default:
return slog.LevelError
}
}
Since I'm not aware of all the best-practices regarding Go module structures, I'm not sure if this adapter should go into a separate repo, be part of the tracelog package, have it's own module but be part of the pgx repo etc.
Long term, I think the best case scenario would be to add a traceslog package that directly supported the new slog package instead of an adapter for tracelog. Internally, tracelog uses maps for args so we lose the presumed performance advantages of the new logging interface if we use an adapter.
This package could live outside of pgx until slog was finally released in Go.
Hi Jack!
Long term, I think the best case scenario would be to add a traceslog package
Yes, I think that totally makes sense.
Let's leave the issue open until then.
slog is now in go 1.21 also https://pkg.go.dev/github.com/mcosta74/pgx-slog
Long term, I think the best case scenario would be to add a traceslog package that directly supported the new slog package
@jackc If someone contributes such an adapter to this repo, would you be willing to merge it?
Given that slog is part of the stdlib in Go 1.21 now (and therefore has the associated stability guarantees), the slog.Logger
interface should be stable and safe to use.
@jackc If someone contributes such an adapter to this repo, would you be willing to merge it? Given that slog is part of the stdlib in Go 1.21 now (and therefore has the associated stability guarantees), the slog.Logger interface should be stable and safe to use.
Yes, but I'm not sure if it can be done yet. pgx supports the latest 2 versions of Go. If it can be done in a way that works on Go 1.20 then it can happen now. If not, it would need to wait until Go 1.22 is released.
Yes, but I'm not sure if it can be done yet. pgx supports the latest 2 versions of Go. [...] If not, it would need to wait until Go 1.22 is released.
That's great news and means we'll have enough time to come up with a solid implementation :-)
@jacksgt FYI, regarding your implementation of Log(..., data map[string]any)
: a downside of this implementation is that the attrs are passed to slog in an ever-changing order, because order of a map[string]any
is not guaranteed. This makes it difficult to find information in the logs when scanning through them manually. I would suggest the following, for now, until this is fixed in pgx
:
func (l *Logger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]any) {
var keys []string
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
var attrs []slog.Attr
for _, k := range keys {
attrs = append(attrs, slog.Any(k, data[k]))
}
l.logger.LogAttrs(ctx, translateLevel(level), msg, attrs...)
}