From b1447dab0df5f9ea8b1ba900cd1919db4c7e2afd Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Mon, 24 May 2021 22:49:54 -0700 Subject: [PATCH 1/2] feat: add option for injecting additional log fields --- go.sum | 2 -- logger.go | 11 ++++++++--- logger_test.go | 21 +++++++++++++++++++++ options.go | 10 ++++++++++ options_test.go | 12 ++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/go.sum b/go.sum index ccf740f..7280641 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -18,7 +17,6 @@ github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200601152816-913338de1bd2 h1:VEmvx0P+GVTgkNu2EdTN988YCZPcD3lo9AoczZpucwc= gopkg.in/yaml.v3 v3.0.0-20200601152816-913338de1bd2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logger.go b/logger.go index 95e10cf..67c5a08 100644 --- a/logger.go +++ b/logger.go @@ -101,11 +101,16 @@ func (l *logger) log(ctx context.Context, lvl Level, msg string, start time.Time return } - data := map[string]interface{}{ - l.opt.timeFieldname: l.opt.timeFormat.format(time.Now()), - l.opt.durationFieldname: l.opt.durationUnit.format(time.Since(start)), + data := make(map[string]interface{}) + + // set the user-defined fields first so they don't clobber any internal fields + for k, v := range l.opt.additionalFields { + data[k] = v } + data[l.opt.timeFieldname] = l.opt.timeFormat.format(time.Now()) + data[l.opt.durationFieldname] = l.opt.durationUnit.format(time.Since(start)) + if l.opt.includeStartTime { data[l.opt.startTimeFieldname] = l.opt.timeFormat.format(start) } diff --git a/logger_test.go b/logger_test.go index afd27b1..dba8b7f 100644 --- a/logger_test.go +++ b/logger_test.go @@ -177,6 +177,27 @@ func TestLogInternalWithData(t *testing.T) { bl.Reset() } +func TestLogWithAdditionalFields(t *testing.T) { + cfg := &options{} + setDefaultOptions(cfg) + WithAdditionalFields(map[string]interface{}{"hello": "world"})(cfg) + bl := &bufferTestLogger{} + l := &logger{opt: cfg, logger: bl} + l.log(context.TODO(), LevelInfo, "msg", time.Now(), nil, l.withQuery("query")) + + var content bufLog + err := json.Unmarshal(bl.Bytes(), &content) + assert.NoError(t, err) + assert.Contains(t, content.Data, cfg.timeFieldname) + assert.Contains(t, content.Data, cfg.durationFieldname) + assert.Contains(t, content.Data, cfg.sqlQueryFieldname) + assert.Equal(t, LevelInfo.String(), content.Level) + assert.Equal(t, "msg", content.Message) + assert.Equal(t, "query", content.Data[cfg.sqlQueryFieldname]) + assert.Equal(t, "world", content.Data["hello"]) + bl.Reset() +} + func TestLogInternalErrorLevel(t *testing.T) { cfg := &options{} setDefaultOptions(cfg) diff --git a/options.go b/options.go index 7653d4d..df14d0f 100644 --- a/options.go +++ b/options.go @@ -18,6 +18,7 @@ type options struct { stmtIDFieldname string connIDFieldname string txIDFieldname string + additionalFields map[string]interface{} sqlQueryAsMsg bool logArgs bool logDriverErrSkip bool @@ -44,6 +45,7 @@ func setDefaultOptions(opt *options) { opt.stmtIDFieldname = "stmt_id" opt.connIDFieldname = "conn_id" opt.txIDFieldname = "tx_id" + opt.additionalFields = make(map[string]interface{}) opt.sqlQueryAsMsg = false opt.minimumLogLevel = LevelDebug opt.logArgs = true @@ -331,6 +333,14 @@ func WithTransactionIDFieldname(name string) Option { } } +func WithAdditionalFields(fields map[string]interface{}) Option { + return func(opt *options) { + for k, v := range fields { + opt.additionalFields[k] = v + } + } +} + // WithWrapResult set flag to wrap Queryer(Context) and Execer(Context) driver.Rows/driver.Result response. // // When set to false, result returned from db (driver.Rows/driver.Result object), diff --git a/options_test.go b/options_test.go index 67ebea8..fd833a1 100644 --- a/options_test.go +++ b/options_test.go @@ -176,6 +176,18 @@ func TestWithTransactionIDFieldname(t *testing.T) { assert.Equal(t, "trxid", cfg.txIDFieldname) } +func TestWithAdditionalFields(t *testing.T) { + cfg := &options{} + setDefaultOptions(cfg) + + WithAdditionalFields(map[string]interface{}{"hello": "world"})(cfg) + assert.EqualValues(t, map[string]interface{}{"hello": "world"}, cfg.additionalFields) + + // additional calls should merge + WithAdditionalFields(map[string]interface{}{"foo": "bar", "hello": "baz"})(cfg) + assert.EqualValues(t, map[string]interface{}{"hello": "baz", "foo": "bar"}, cfg.additionalFields) +} + func TestWithWrapResult(t *testing.T) { cfg := &options{} setDefaultOptions(cfg) From ca1364ee78d3e6bdb336e13b8d6507cd5ec81d19 Mon Sep 17 00:00:00 2001 From: Dominic Barnes Date: Mon, 24 May 2021 22:55:16 -0700 Subject: [PATCH 2/2] feat: add option for injecting a single log field --- logger_test.go | 21 +++++++++++++++++++++ options.go | 14 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/logger_test.go b/logger_test.go index dba8b7f..5c07c9b 100644 --- a/logger_test.go +++ b/logger_test.go @@ -177,6 +177,27 @@ func TestLogInternalWithData(t *testing.T) { bl.Reset() } +func TestLogWithAdditionalField(t *testing.T) { + cfg := &options{} + setDefaultOptions(cfg) + WithAdditionalField("hello", "world")(cfg) + bl := &bufferTestLogger{} + l := &logger{opt: cfg, logger: bl} + l.log(context.TODO(), LevelInfo, "msg", time.Now(), nil, l.withQuery("query")) + + var content bufLog + err := json.Unmarshal(bl.Bytes(), &content) + assert.NoError(t, err) + assert.Contains(t, content.Data, cfg.timeFieldname) + assert.Contains(t, content.Data, cfg.durationFieldname) + assert.Contains(t, content.Data, cfg.sqlQueryFieldname) + assert.Equal(t, LevelInfo.String(), content.Level) + assert.Equal(t, "msg", content.Message) + assert.Equal(t, "query", content.Data[cfg.sqlQueryFieldname]) + assert.Equal(t, "world", content.Data["hello"]) + bl.Reset() +} + func TestLogWithAdditionalFields(t *testing.T) { cfg := &options{} setDefaultOptions(cfg) diff --git a/options.go b/options.go index df14d0f..a8339b1 100644 --- a/options.go +++ b/options.go @@ -333,6 +333,10 @@ func WithTransactionIDFieldname(name string) Option { } } +// WithAdditionalFields allows injecting multiple log fields. +// +// In the event of another log field name colliding with any additional fields, +// it will be overwritten by what comes later. func WithAdditionalFields(fields map[string]interface{}) Option { return func(opt *options) { for k, v := range fields { @@ -341,6 +345,16 @@ func WithAdditionalFields(fields map[string]interface{}) Option { } } +// WithAdditionalField allows injecting a single log field. +// +// In the event of another log field name colliding with any additional fields, +// it will be overwritten by what comes later. +func WithAdditionalField(name string, value interface{}) Option { + return func(opt *options) { + opt.additionalFields[name] = value + } +} + // WithWrapResult set flag to wrap Queryer(Context) and Execer(Context) driver.Rows/driver.Result response. // // When set to false, result returned from db (driver.Rows/driver.Result object),