Repository: jackc/pgx
Pull request #1081 (2021-09-17 07:10:15)

Add context options to zerologadapter
### WithContextFunc
WithContextFunc adds possibility to get request scoped values from the ctx.Context before logging lines.

I usually insert the request id from `zerolog.hlog.IDFromCtx` in the pgx logger and this lets users do that or even get the whole logger from `zerolog/log.Ctx` to get the whole logger from the context if they want to.

### WithoutPGXModule
WithoutPGXModule disables adding module:pgx to the default logger context.

I usually set the caller to pgx instead so I have just removed the module:pgx from my own modified version. 


thomasf: Maybe the tests are a bit too verbose, they probably go a little bit into example test territory.

I have some variant of this zerologadapter change in every project with a http handler for a while now so I guess attempting to upstream it is a good idea.

Patch:
diff --git a/log/zerologadapter/adapter.go b/log/zerologadapter/adapter.go
index efbcb5bf..9bfa5788 100644
--- a/log/zerologadapter/adapter.go
+++ b/log/zerologadapter/adapter.go
@@ -9,15 +9,42 @@ import (
 )
 
 type Logger struct {
-	logger zerolog.Logger
+	logger     zerolog.Logger
+	withFunc   func(context.Context, zerolog.Context) zerolog.Context
+	skipModule bool
+}
+
+// option options for configuring the logger when creating a new logger.
+type option func(logger *Logger)
+
+// WithContextFunc adds possibility to get request scoped values from the
+// ctx.Context before logging lines.
+func WithContextFunc(withFunc func(context.Context, zerolog.Context) zerolog.Context) option {
+	return func(logger *Logger) {
+		logger.withFunc = withFunc
+	}
+}
+
+// WithoutPGXModule disables adding module:pgx to the default logger context.
+func WithoutPGXModule() option {
+	return func(logger *Logger) {
+		logger.skipModule = true
+	}
 }
 
 // NewLogger accepts a zerolog.Logger as input and returns a new custom pgx
-// logging fascade as output.
-func NewLogger(logger zerolog.Logger) *Logger {
-	return &Logger{
-		logger: logger.With().Str("module", "pgx").Logger(),
+// logging facade as output.
+func NewLogger(logger zerolog.Logger, options ...option) *Logger {
+	l := Logger{
+		logger: logger,
+	}
+	for _, opt := range options {
+		opt(&l)
+	}
+	if !l.skipModule {
+		l.logger = l.logger.With().Str("module", "pgx").Logger()
 	}
+	return &l
 }
 
 func (pl *Logger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) {
@@ -36,7 +63,10 @@ func (pl *Logger) Log(ctx context.Context, level pgx.LogLevel, msg string, data
 	default:
 		zlevel = zerolog.DebugLevel
 	}
-
-	pgxlog := pl.logger.With().Fields(data).Logger()
+	zctx := pl.logger.With()
+	if pl.withFunc != nil {
+		zctx = pl.withFunc(ctx, zctx)
+	}
+	pgxlog := zctx.Fields(data).Logger()
 	pgxlog.WithLevel(zlevel).Msg(msg)
 }


Test patch:
diff --git a/log/zerologadapter/adapter_test.go b/log/zerologadapter/adapter_test.go
new file mode 100644
index 00000000..deda6ed4
--- /dev/null
+++ b/log/zerologadapter/adapter_test.go
@@ -0,0 +1,81 @@
+package zerologadapter_test
+
+import (
+	"bytes"
+	"context"
+	"testing"
+
+	"github.com/jackc/pgx/v4"
+	"github.com/jackc/pgx/v4/log/zerologadapter"
+	"github.com/rs/zerolog"
+)
+
+func TestLogger(t *testing.T) {
+
+	t.Run("default", func(t *testing.T) {
+		var buf bytes.Buffer
+		zlogger := zerolog.New(&buf)
+		logger := zerologadapter.NewLogger(zlogger)
+		logger.Log(context.Background(), pgx.LogLevelInfo, "hello", map[string]interface{}{"one": "two"})
+		const want = `{"level":"info","module":"pgx","one":"two","message":"hello"}
+`
+		got := buf.String()
+		if got != want {
+			t.Errorf("%s != %s", got, want)
+		}
+	})
+
+	t.Run("disable pgx module", func(t *testing.T) {
+		var buf bytes.Buffer
+		zlogger := zerolog.New(&buf)
+		logger := zerologadapter.NewLogger(zlogger, zerologadapter.WithoutPGXModule())
+		logger.Log(context.Background(), pgx.LogLevelInfo, "hello", nil)
+		const want = `{"level":"info","message":"hello"}
+`
+		got := buf.String()
+		if got != want {
+			t.Errorf("%s != %s", got, want)
+		}
+	})
+
+	var buf bytes.Buffer
+	type key string
+	var ck key
+	zlogger := zerolog.New(&buf)
+	logger := zerologadapter.NewLogger(zlogger,
+		zerologadapter.WithContextFunc(func(ctx context.Context, logWith zerolog.Context) zerolog.Context {
+			// You can use zerolog.hlog.IDFromCtx(ctx) or even
+			// zerolog.log.Ctx(ctx) to fetch the whole logger instance from the
+			// context if you want.
+			id, ok := ctx.Value(ck).(string)
+			if ok {
+				logWith = logWith.Str("req_id", id)
+			}
+			return logWith
+		}))
+
+	t.Run("no request id", func(t *testing.T) {
+		buf.Reset()
+		ctx := context.Background()
+		logger.Log(ctx, pgx.LogLevelInfo, "hello", nil)
+		const want = `{"level":"info","module":"pgx","message":"hello"}
+`
+		got := buf.String()
+		if got != want {
+			t.Errorf("%s != %s", got, want)
+		}
+	})
+
+	t.Run("with request id", func(t *testing.T) {
+		buf.Reset()
+		ctx := context.WithValue(context.Background(), ck, "1")
+		logger.Log(ctx, pgx.LogLevelInfo, "hello", map[string]interface{}{"two": "2"})
+		const want = `{"level":"info","module":"pgx","req_id":"1","two":"2","message":"hello"}
+`
+		got := buf.String()
+		if got != want {
+			t.Errorf("%s != %s", got, want)
+		}
+	})
+
+}