diff --git a/core/logger/attrs.go b/core/logger/attrs.go index 2d6e2c3..b49a0af 100644 --- a/core/logger/attrs.go +++ b/core/logger/attrs.go @@ -85,12 +85,18 @@ func Body(val any) zap.Field { return zap.Any(BodyAttr, m) } return zap.String(BodyAttr, item) - case []byte: + case []byte, json.RawMessage: + var val []byte + if msg, ok := item.(json.RawMessage); ok { + val = msg + } else { + val = item.([]byte) + } var m interface{} - if err := json.Unmarshal(item, &m); err == nil { + if err := json.Unmarshal(val, &m); err == nil { return zap.Any(BodyAttr, m) } - return zap.String(BodyAttr, string(item)) + return zap.String(BodyAttr, string(val)) case io.Reader: data, err := io.ReadAll(item) if err != nil { diff --git a/core/logger/attrs_test.go b/core/logger/attrs_test.go index 016a550..b87136c 100644 --- a/core/logger/attrs_test.go +++ b/core/logger/attrs_test.go @@ -2,6 +2,7 @@ package logger import ( "bytes" + "encoding/json" "errors" "fmt" "github.com/stretchr/testify/require" @@ -125,6 +126,11 @@ func TestBody(t *testing.T) { input: []byte("test body"), result: "test body", }, + { + name: "json.RawMessage input", + input: json.RawMessage("test body"), + result: "test body", + }, { name: "json byte slice input", input: []byte(`{"success":true}`), diff --git a/core/logger/buffer_logger_test.go b/core/logger/buffer_logger_test.go index d1a36ed..9a90db7 100644 --- a/core/logger/buffer_logger_test.go +++ b/core/logger/buffer_logger_test.go @@ -85,16 +85,19 @@ func (l *bufferLogger) WithLazy(fields ...zapcore.Field) Logger { } } +// ForHandler returns a new logger that is associated with the given handler. func (l *bufferLogger) ForHandler(handler any) Logger { - return l.WithLazy(zap.Any(HandlerAttr, handler)) + return l.clone(l.parentOrCurrent().WithLazy(zap.Any(HandlerAttr, handler))) } +// ForConnection returns a new logger that is associated with the given connection. func (l *bufferLogger) ForConnection(conn any) Logger { - return l.WithLazy(zap.Any(ConnectionAttr, conn)) + return l.clone(l.parentOrCurrent().WithLazy(zap.Any(ConnectionAttr, conn))) } +// ForAccount returns a new logger that is associated with the given account. func (l *bufferLogger) ForAccount(acc any) Logger { - return l.WithLazy(zap.Any(AccountAttr, acc)) + return l.clone(l.parentOrCurrent().WithLazy(zap.Any(AccountAttr, acc))) } // Read bytes from the logger buffer. io.Reader implementation. @@ -117,6 +120,28 @@ func (l *bufferLogger) Reset() { l.buf.Reset() } +// clone creates a copy of the given logger. +func (l *bufferLogger) clone(log *zap.Logger) Logger { + parent := l.parent + if parent == nil { + parent = l.Logger + } + return &bufferLogger{ + Default: Default{ + Logger: log, + parent: parent, + }, + } +} + +// parentOrCurrent returns parent logger if it exists or current logger otherwise. +func (l *bufferLogger) parentOrCurrent() *zap.Logger { + if l.parent != nil { + return l.parent + } + return l.Logger +} + type lockableBuffer struct { buf bytes.Buffer rw sync.RWMutex diff --git a/core/logger/default.go b/core/logger/default.go index 017b4b5..6ac3b61 100644 --- a/core/logger/default.go +++ b/core/logger/default.go @@ -47,6 +47,7 @@ type Logger interface { // Default is a default logger implementation. type Default struct { *zap.Logger + parent *zap.Logger } // NewDefault creates a new default logger with the given format and debug level. @@ -68,22 +69,34 @@ func (l *Default) WithLazy(fields ...zap.Field) Logger { // ForHandler returns a new logger that is associated with the given handler. func (l *Default) ForHandler(handler any) Logger { - return l.WithLazy(zap.Any(HandlerAttr, handler)) + return l.clone(l.parentOrCurrent().WithLazy(zap.Any(HandlerAttr, handler))) } // ForConnection returns a new logger that is associated with the given connection. func (l *Default) ForConnection(conn any) Logger { - return l.WithLazy(zap.Any(ConnectionAttr, conn)) + return l.clone(l.parentOrCurrent().WithLazy(zap.Any(ConnectionAttr, conn))) } // ForAccount returns a new logger that is associated with the given account. func (l *Default) ForAccount(acc any) Logger { - return l.WithLazy(zap.Any(AccountAttr, acc)) + return l.clone(l.parentOrCurrent().WithLazy(zap.Any(AccountAttr, acc))) } // clone creates a copy of the given logger. func (l *Default) clone(log *zap.Logger) Logger { - return &Default{Logger: log} + parent := l.parent + if parent == nil { + parent = l.Logger + } + return &Default{Logger: log, parent: parent} +} + +// parentOrCurrent returns parent logger if it exists or current logger otherwise. +func (l *Default) parentOrCurrent() *zap.Logger { + if l.parent != nil { + return l.parent + } + return l.Logger } // AnyZapFields converts an array of values to zap fields. diff --git a/core/logger/default_test.go b/core/logger/default_test.go index 7301568..343c9dd 100644 --- a/core/logger/default_test.go +++ b/core/logger/default_test.go @@ -61,6 +61,14 @@ func (s *TestDefaultSuite) TestForHandler() { s.Assert().Equal("Handler", items[0].Handler) } +func (s *TestDefaultSuite) TestForHandlerNoDuplicate() { + log := newBufferLogger() + log.ForHandler("handler1").ForHandler("handler2").Info("test") + + s.Assert().Contains(log.String(), "handler2") + s.Assert().NotContains(log.String(), "handler1") +} + func (s *TestDefaultSuite) TestForConnection() { log := newBufferLogger() log.ForConnection("connection").Info("test") @@ -71,6 +79,14 @@ func (s *TestDefaultSuite) TestForConnection() { s.Assert().Equal("connection", items[0].Connection) } +func (s *TestDefaultSuite) TestForConnectionNoDuplicate() { + log := newBufferLogger() + log.ForConnection("conn1").ForConnection("conn2").Info("test") + + s.Assert().Contains(log.String(), "conn2") + s.Assert().NotContains(log.String(), "conn1") +} + func (s *TestDefaultSuite) TestForAccount() { log := newBufferLogger() log.ForAccount("account").Info("test") @@ -81,6 +97,14 @@ func (s *TestDefaultSuite) TestForAccount() { s.Assert().Equal("account", items[0].Account) } +func (s *TestDefaultSuite) TestForAccountNoDuplicate() { + log := newBufferLogger() + log.ForAccount("acc1").ForAccount("acc2").Info("test") + + s.Assert().Contains(log.String(), "acc2") + s.Assert().NotContains(log.String(), "acc1") +} + func TestAnyZapFields(t *testing.T) { fields := AnyZapFields([]interface{}{zap.String("k0", "v0"), "ooga", "booga"}) require.Len(t, fields, 3) diff --git a/core/logger/json_with_context_encoder.go b/core/logger/json_with_context_encoder.go index 79b1a2f..14d1d47 100644 --- a/core/logger/json_with_context_encoder.go +++ b/core/logger/json_with_context_encoder.go @@ -44,6 +44,10 @@ var _jsonWithContextPool = NewPool(func() *jsonWithContextEncoder { }) func init() { + registerJSONWithContext() +} + +func registerJSONWithContext() { err := zap.RegisterEncoder("json-with-context", func(config zapcore.EncoderConfig) (zapcore.Encoder, error) { return NewJSONWithContextEncoder(config), nil })