From c335f007948253c2802b541bdfb3660ce0277752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96rjan=20Fors?= Date: Tue, 7 May 2024 14:39:25 +0200 Subject: [PATCH] feat: support slog attributes --- json.go | 25 +++++++++++++++++++++++-- json_121.go | 15 +++++++++++++++ json_no121.go | 27 +++++++++++++++++++++++++++ logger_121_test.go | 28 ++++++++++++++++++++++++++-- 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 json_121.go create mode 100644 json_no121.go diff --git a/json.go b/json.go index 15d12c8..0a374d7 100644 --- a/json.go +++ b/json.go @@ -11,8 +11,18 @@ func (l *Logger) jsonFormatter(keyvals ...interface{}) { jw := &jsonWriter{w: &l.b} jw.start() - for i := 0; i < len(keyvals); i += 2 { - l.jsonFormatterKeyVal(jw, keyvals[i], keyvals[i+1]) + i := 0 + for i < len(keyvals) { + switch kv := keyvals[i].(type) { + case slogAttr: + l.jsonFormatterKeyVal(jw, kv.Key, kv.Value) + i++ + default: + if i+1 < len(keyvals) { + l.jsonFormatterKeyVal(jw, keyvals[i], keyvals[i+1]) + } + i += 2 + } } jw.end() @@ -53,6 +63,17 @@ func (l *Logger) jsonFormatterKeyVal(jw *jsonWriter, anyKey, value any) { switch v := value.(type) { case error: jw.objectValue(v.Error()) + case slogValue: + switch v.Kind() { + case slogKindGroup: + jw.start() + for _, attr := range v.Group() { + l.jsonFormatterKeyVal(jw, attr.Key, attr.Value) + } + jw.end() + default: + jw.objectValue(v.String()) + } case fmt.Stringer: jw.objectValue(v.String()) default: diff --git a/json_121.go b/json_121.go new file mode 100644 index 0000000..4e49cb7 --- /dev/null +++ b/json_121.go @@ -0,0 +1,15 @@ +//go:build go1.21 +// +build go1.21 + +package log + +import "log/slog" + +type ( + slogAttr = slog.Attr + slogValue = slog.Value +) + +const ( + slogKindGroup = slog.KindGroup +) diff --git a/json_no121.go b/json_no121.go new file mode 100644 index 0000000..2098266 --- /dev/null +++ b/json_no121.go @@ -0,0 +1,27 @@ +//go:build !go1.21 +// +build !go1.21 + +package log + +type ( + slogAttr struct { + Key string + Value slogValue + } + slogValue struct{} + slogKind int +) + +const slogKindGroup slogKind = iota + +func (slogValue) String() string { + panic("should not be reached") +} + +func (slogValue) Group() []slogAttr { + panic("should not be reached") +} + +func (slogValue) Kind() slogKind { + panic("should not be reached") +} diff --git a/logger_121_test.go b/logger_121_test.go index d893ebc..f23f284 100644 --- a/logger_121_test.go +++ b/logger_121_test.go @@ -6,11 +6,10 @@ package log import ( "bytes" "context" + "log/slog" "testing" "time" - "log/slog" - "github.com/stretchr/testify/assert" ) @@ -183,3 +182,28 @@ func TestSlogCustomLevel(t *testing.T) { }) } } + +func TestSlogAttr(t *testing.T) { + cases := []struct { + name string + expected string + keyvals []interface{} + }{ + { + name: "group", + expected: `{"level":"info","msg":"message","g":{"b":"true"}}` + "\n", + keyvals: []any{slog.Group("g", slog.Bool("b", true))}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + var buf bytes.Buffer + l := NewWithOptions(&buf, Options{Formatter: JSONFormatter}) + l.Info("message", c.keyvals...) + assert.Equal(t, c.expected, buf.String()) + }) + } +}