Skip to content

Commit c17dfd2

Browse files
authored
Support keysAndArgs API of stdlib slog (#173)
1 parent 2202bcf commit c17dfd2

File tree

4 files changed

+97
-14
lines changed

4 files changed

+97
-14
lines changed

s.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (w stdlogWriter) Write(p []byte) (n int, err error) {
4141
// we do not want.
4242
msg = strings.TrimSuffix(msg, "\n")
4343

44-
w.l.log(w.ctx, w.level, msg, Map{})
44+
w.l.log(w.ctx, w.level, msg, nil)
4545

4646
return len(p), nil
4747
}

slog.go

+59-7
Original file line numberDiff line numberDiff line change
@@ -80,40 +80,67 @@ func Make(sinks ...Sink) Logger {
8080
}
8181

8282
// Debug logs the msg and fields at LevelDebug.
83-
func (l Logger) Debug(ctx context.Context, msg string, fields ...Field) {
83+
// See Info for information on the fields argument.
84+
func (l Logger) Debug(ctx context.Context, msg string, fields ...any) {
8485
l.log(ctx, LevelDebug, msg, fields)
8586
}
8687

8788
// Info logs the msg and fields at LevelInfo.
88-
func (l Logger) Info(ctx context.Context, msg string, fields ...Field) {
89+
// Fields may contain any combination of key value pairs, Field, and Map.
90+
// For example:
91+
//
92+
// log.Info(ctx, "something happened", "user", "alex", slog.F("age", 20))
93+
//
94+
// is equivalent to:
95+
//
96+
// log.Info(ctx, "something happened", slog.F("user", "alex"), slog.F("age", 20))
97+
//
98+
// is equivalent to:
99+
//
100+
// log.Info(ctx, "something happened", slog.M(
101+
// slog.F("user", "alex"),
102+
// slog.F("age", 20),
103+
// ))
104+
//
105+
// is equivalent to:
106+
//
107+
// log.Info(ctx, "something happened", "user", "alex", "age", 20)
108+
//
109+
// In general, prefer using key value pairs over Field and Map, as that is how
110+
// the standard library's slog package works.
111+
func (l Logger) Info(ctx context.Context, msg string, fields ...any) {
89112
l.log(ctx, LevelInfo, msg, fields)
90113
}
91114

92115
// Warn logs the msg and fields at LevelWarn.
93-
func (l Logger) Warn(ctx context.Context, msg string, fields ...Field) {
116+
// See Info() for information on the fields argument.
117+
func (l Logger) Warn(ctx context.Context, msg string, fields ...any) {
94118
l.log(ctx, LevelWarn, msg, fields)
95119
}
96120

97121
// Error logs the msg and fields at LevelError.
122+
// See Info() for information on the fields argument.
98123
//
99124
// It will then Sync().
100-
func (l Logger) Error(ctx context.Context, msg string, fields ...Field) {
125+
func (l Logger) Error(ctx context.Context, msg string, fields ...any) {
101126
l.log(ctx, LevelError, msg, fields)
102127
l.Sync()
103128
}
104129

105130
// Critical logs the msg and fields at LevelCritical.
131+
// See Info() for information on the fields argument.
106132
//
107133
// It will then Sync().
108-
func (l Logger) Critical(ctx context.Context, msg string, fields ...Field) {
134+
func (l Logger) Critical(ctx context.Context, msg string, fields ...any) {
109135
l.log(ctx, LevelCritical, msg, fields)
110136
l.Sync()
111137
}
112138

113139
// Fatal logs the msg and fields at LevelFatal.
140+
// See Info() for information on the fields argument.
114141
//
115142
// It will then Sync() and os.Exit(1).
116-
func (l Logger) Fatal(ctx context.Context, msg string, fields ...Field) {
143+
func (l Logger) Fatal(ctx context.Context, msg string, fields ...any) {
117144
l.log(ctx, LevelFatal, msg, fields)
118145
l.Sync()
119146

@@ -155,7 +182,32 @@ func (l Logger) AppendSinks(s ...Sink) Logger {
155182
return l
156183
}
157184

158-
func (l Logger) log(ctx context.Context, level Level, msg string, fields Map) {
185+
func (l Logger) log(ctx context.Context, level Level, msg string, rawFields []any) {
186+
fields := make(Map, 0, len(rawFields))
187+
var wipField Field
188+
for i, f := range rawFields {
189+
if wipField.Name != "" {
190+
wipField.Value = f
191+
fields = append(fields, wipField)
192+
wipField = Field{}
193+
continue
194+
}
195+
switch f := f.(type) {
196+
case Field:
197+
fields = append(fields, f)
198+
case Map:
199+
fields = append(fields, f...)
200+
case string:
201+
wipField.Name = f
202+
default:
203+
panic(fmt.Sprintf("unexpected field type %T at index %v (does it have a key?)", f, i))
204+
}
205+
}
206+
207+
if wipField.Name != "" {
208+
panic(fmt.Sprintf("field %q has no value", wipField.Name))
209+
}
210+
159211
ent := l.entry(ctx, level, msg, fields)
160212
l.Log(ctx, ent)
161213
}

slog_test.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package slog_test
22

33
import (
44
"context"
5+
"fmt"
56
"io"
67
"runtime"
78
"testing"
@@ -75,7 +76,7 @@ func TestLogger(t *testing.T) {
7576

7677
File: slogTestFile,
7778
Func: "cdr.dev/slog_test.TestLogger.func2",
78-
Line: 67,
79+
Line: 68,
7980

8081
Fields: slog.M(
8182
slog.F("ctx", 1024),
@@ -108,7 +109,7 @@ func TestLogger(t *testing.T) {
108109

109110
File: slogTestFile,
110111
Func: "cdr.dev/slog_test.TestLogger.func3",
111-
Line: 98,
112+
Line: 99,
112113

113114
SpanContext: span.SpanContext(),
114115

@@ -149,6 +150,36 @@ func TestLogger(t *testing.T) {
149150
assert.Equal(t, "level", slog.LevelFatal, s.entries[5].Level)
150151
assert.Equal(t, "exits", 1, exits)
151152
})
153+
154+
t.Run("kv", func(t *testing.T) {
155+
s := &fakeSink{}
156+
l := slog.Make(s)
157+
158+
// All of these formats should be equivalent.
159+
formats := [][]any{
160+
{"animal", "cat", "weight", 15},
161+
{slog.F("animal", "cat"), "weight", 15},
162+
{slog.M(
163+
slog.F("animal", "cat"),
164+
slog.F("weight", 15),
165+
)},
166+
{slog.F("animal", "cat"), slog.F("weight", 15)},
167+
}
168+
169+
for _, format := range formats {
170+
l.Info(bg, "msg", format...)
171+
}
172+
173+
assert.Len(t, "entries", 4, s.entries)
174+
175+
for i := range s.entries {
176+
assert.Equal(
177+
t, fmt.Sprintf("%v", i),
178+
s.entries[0].Fields,
179+
s.entries[i].Fields,
180+
)
181+
}
182+
})
152183
}
153184

154185
func TestLevel_String(t *testing.T) {

sloggers/slogtest/t.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -103,25 +103,25 @@ func l(t testing.TB) slog.Logger {
103103
}
104104

105105
// Debug logs the given msg and fields to t via t.Log at the debug level.
106-
func Debug(t testing.TB, msg string, fields ...slog.Field) {
106+
func Debug(t testing.TB, msg string, fields ...any) {
107107
slog.Helper()
108108
l(t).Debug(ctx, msg, fields...)
109109
}
110110

111111
// Info logs the given msg and fields to t via t.Log at the info level.
112-
func Info(t testing.TB, msg string, fields ...slog.Field) {
112+
func Info(t testing.TB, msg string, fields ...any) {
113113
slog.Helper()
114114
l(t).Info(ctx, msg, fields...)
115115
}
116116

117117
// Error logs the given msg and fields to t via t.Error at the error level.
118-
func Error(t testing.TB, msg string, fields ...slog.Field) {
118+
func Error(t testing.TB, msg string, fields ...any) {
119119
slog.Helper()
120120
l(t).Error(ctx, msg, fields...)
121121
}
122122

123123
// Fatal logs the given msg and fields to t via t.Fatal at the fatal level.
124-
func Fatal(t testing.TB, msg string, fields ...slog.Field) {
124+
func Fatal(t testing.TB, msg string, fields ...any) {
125125
slog.Helper()
126126
l(t).Fatal(ctx, msg, fields...)
127127
}

0 commit comments

Comments
 (0)