Skip to content

Commit 2d091ad

Browse files
author
Russ Egan
committed
Added middleware which emulates ReplaceAttr functionality
useful for wrapping handers which don't natively support it.
1 parent 7fd88a8 commit 2d091ad

File tree

4 files changed

+422
-71
lines changed

4 files changed

+422
-71
lines changed

v2/TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@
4242
- Working on a slog handler -> zap core bridge, and a zap core -> slog handler bridge
4343
- [ ] A gofix style tool to migrate a codebase from v1 to v2, and migrating from Log() to LogCtx() calls?
4444
- [x] I think the states need to be re-organized back into a parent-child graph, and sinks need to trickle down that tree. Creating all the handlers and states in conf isn't working the way it was intended. Rebuilding the leaf handlers is grouping the cached attrs wrong (need tests to verify this), and is also inefficient, since it creates inefficient calls to the sink's WithAttrs()
45-
- [ ] Add a middleware which supports ReplaceAttr. Could be used to add ReplaceAttr support to Handlers which don't natively support it
45+
- [x] Add a middleware which supports ReplaceAttr. Could be used to add ReplaceAttr support to Handlers which don't natively support it
4646
- [ ] We could then promote ReplaceAttr support to the root of Config. If the selected handler natively supports ReplaceAttr, great, otherwise we can add the middleware. To support this, change the way handlers are registered with Config, so that each registration provides a factory method for building the handler, which can take the Config object, and adapt it to the native options that handler supports.

v2/handler_test.go

Lines changed: 90 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,36 @@ func removeKeys(keys ...string) func([]string, slog.Attr) slog.Attr {
2424
}
2525
}
2626

27+
func replaceKey(key string, newAttr slog.Attr) func([]string, slog.Attr) slog.Attr {
28+
return func(_ []string, a slog.Attr) slog.Attr {
29+
if a.Key == key {
30+
return newAttr
31+
}
32+
33+
return a
34+
}
35+
}
36+
2737
func TestHandlers(t *testing.T) {
28-
tests := []struct {
29-
name string
30-
wantJSON string
31-
wantText string
32-
level slog.Level
33-
handlerFn func(t *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler
34-
}{
38+
tests := []handlerTest{
3539
{
3640
name: "nil",
3741
handlerFn: func(_ *testing.T, _ *bytes.Buffer, _ *slog.HandlerOptions) slog.Handler {
3842
return NewController(nil).Handler("h1")
3943
},
4044
},
4145
{
42-
name: "factory constructor",
43-
wantJSON: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
46+
name: "factory constructor",
47+
want: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
48+
json: true,
4449
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
4550
return NewController(slog.NewJSONHandler(buf, opts)).Handler("h1")
4651
},
4752
},
4853
{
49-
name: "change default sink before handler",
50-
wantJSON: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
54+
name: "change default sink before handler",
55+
want: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
56+
json: true,
5157
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
5258
f := NewController(nil)
5359
f.SetDefaultSink(slog.NewJSONHandler(buf, opts))
@@ -56,8 +62,8 @@ func TestHandlers(t *testing.T) {
5662
},
5763
},
5864
{
59-
name: "change default sink after handler",
60-
wantText: "level=INFO msg=hi logger=h1\n",
65+
name: "change default sink after handler",
66+
want: "level=INFO msg=hi logger=h1\n",
6167
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
6268
f := NewController(slog.NewJSONHandler(buf, opts))
6369
h := f.Handler("h1")
@@ -67,8 +73,9 @@ func TestHandlers(t *testing.T) {
6773
},
6874
},
6975
{
70-
name: "change other sink before handler",
71-
wantJSON: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
76+
name: "change other sink before handler",
77+
want: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
78+
json: true,
7279
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
7380
f := NewController(slog.NewJSONHandler(buf, opts))
7481
f.SetSink("h2", slog.NewTextHandler(buf, opts))
@@ -77,8 +84,8 @@ func TestHandlers(t *testing.T) {
7784
},
7885
},
7986
{
80-
name: "change sink before handler",
81-
wantText: "level=INFO msg=hi logger=h1\n",
87+
name: "change sink before handler",
88+
want: "level=INFO msg=hi logger=h1\n",
8289
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
8390
f := NewController(slog.NewJSONHandler(buf, opts))
8491
f.SetSink("h1", slog.NewTextHandler(buf, opts))
@@ -87,8 +94,8 @@ func TestHandlers(t *testing.T) {
8794
},
8895
},
8996
{
90-
name: "change sink after handler",
91-
wantText: "level=INFO msg=hi logger=h1\n",
97+
name: "change sink after handler",
98+
want: "level=INFO msg=hi logger=h1\n",
9299
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
93100
f := NewController(slog.NewJSONHandler(buf, opts))
94101
h := f.Handler("h1")
@@ -98,8 +105,8 @@ func TestHandlers(t *testing.T) {
98105
},
99106
},
100107
{
101-
name: "WithXXX",
102-
wantText: "level=INFO msg=hi logger=h1 props.color=red\n",
108+
name: "WithXXX",
109+
want: "level=INFO msg=hi logger=h1 props.color=red\n",
103110
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
104111
f := NewController(slog.NewTextHandler(buf, opts))
105112
h := f.Handler("h1")
@@ -109,8 +116,8 @@ func TestHandlers(t *testing.T) {
109116
},
110117
},
111118
{
112-
name: "change sink after WithXXX",
113-
wantText: "level=INFO msg=hi logger=h1 size=big props.color=red props.address.street=mockingbird\n",
119+
name: "change sink after WithXXX",
120+
want: "level=INFO msg=hi logger=h1 size=big props.color=red props.address.street=mockingbird\n",
114121
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
115122
f := NewController(slog.NewJSONHandler(buf, opts))
116123
h := f.Handler("h1")
@@ -122,8 +129,8 @@ func TestHandlers(t *testing.T) {
122129
},
123130
},
124131
{
125-
name: "change sink before WithXXX",
126-
wantText: "level=INFO msg=hi logger=h1 size=big props.color=red props.address.street=mockingbird\n",
132+
name: "change sink before WithXXX",
133+
want: "level=INFO msg=hi logger=h1 size=big props.color=red props.address.street=mockingbird\n",
127134
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
128135
f := NewController(slog.NewJSONHandler(buf, opts))
129136
f.SetSink("h1", slog.NewTextHandler(buf, opts))
@@ -155,8 +162,9 @@ func TestHandlers(t *testing.T) {
155162
},
156163
},
157164
{
158-
name: "set other logger to nil",
159-
wantJSON: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
165+
name: "set other logger to nil",
166+
want: `{"level": "INFO", "logger": "h1", "msg":"hi"}`,
167+
json: true,
160168
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
161169
f := NewController(slog.NewJSONHandler(buf, opts))
162170
h := f.Handler("h1")
@@ -166,24 +174,25 @@ func TestHandlers(t *testing.T) {
166174
},
167175
},
168176
{
169-
name: "default",
170-
wantJSON: `{"level": "INFO", "logger": "def1", "msg":"hi"}`,
177+
name: "default",
178+
want: `{"level": "INFO", "logger": "def1", "msg":"hi"}`,
179+
json: true,
171180
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
172181
Default().SetDefaultSink(slog.NewJSONHandler(buf, opts))
173182
return Handler("def1")
174183
},
175184
},
176185
{
177-
name: "default with text",
178-
wantText: "level=INFO msg=hi logger=def1\n",
186+
name: "default with text",
187+
want: "level=INFO msg=hi logger=def1\n",
179188
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
180189
Default().SetDefaultSink(slog.NewTextHandler(buf, opts))
181190
return Handler("def1")
182191
},
183192
},
184193
{
185-
name: "default with specific logger",
186-
wantText: "level=INFO msg=hi logger=def2\n",
194+
name: "default with specific logger",
195+
want: "level=INFO msg=hi logger=def2\n",
187196
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
188197
Default().SetDefaultSink(slog.NewJSONHandler(buf, opts))
189198
Default().SetSink("def2", slog.NewTextHandler(buf, opts))
@@ -201,9 +210,9 @@ func TestHandlers(t *testing.T) {
201210
},
202211
},
203212
{
204-
name: "set default log level",
205-
level: slog.LevelDebug,
206-
wantText: "level=DEBUG msg=hi logger=def1\n",
213+
name: "set default log level",
214+
level: slog.LevelDebug,
215+
want: "level=DEBUG msg=hi logger=def1\n",
207216
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
208217
Default().SetDefaultSink(slog.NewTextHandler(buf, opts))
209218
Default().SetDefaultLevel(slog.LevelDebug)
@@ -212,9 +221,9 @@ func TestHandlers(t *testing.T) {
212221
},
213222
},
214223
{
215-
name: "set specific log level",
216-
level: slog.LevelDebug,
217-
wantText: "level=DEBUG msg=hi logger=TestHandlers/set_specific_log_level\n",
224+
name: "set specific log level",
225+
level: slog.LevelDebug,
226+
want: "level=DEBUG msg=hi logger=TestHandlers/set_specific_log_level\n",
218227
handlerFn: func(t *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
219228
Default().SetDefaultSink(slog.NewTextHandler(buf, opts))
220229
Default().SetDefaultLevel(slog.LevelInfo)
@@ -224,8 +233,8 @@ func TestHandlers(t *testing.T) {
224233
},
225234
},
226235
{
227-
name: "ensure cloned slices",
228-
wantText: "level=INFO msg=hi logger=h1 props.flavor=lemon props.color=red\n",
236+
name: "ensure cloned slices",
237+
want: "level=INFO msg=hi logger=h1 props.flavor=lemon props.color=red\n",
229238
handlerFn: func(_ *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler {
230239
ctl := NewController(slog.NewTextHandler(buf, opts))
231240
h1 := ctl.Handler("h1").WithGroup("props").WithAttrs([]slog.Attr{slog.String("flavor", "lemon")})
@@ -241,20 +250,46 @@ func TestHandlers(t *testing.T) {
241250
}
242251

243252
for _, test := range tests {
244-
t.Run(test.name, func(t *testing.T) {
245-
buf := bytes.NewBuffer(nil)
246-
h := test.handlerFn(t, buf, &slog.HandlerOptions{ReplaceAttr: removeKeys(slog.TimeKey)})
247-
l := slog.New(h)
248-
l.Log(context.Background(), test.level, "hi")
249-
switch {
250-
case test.wantJSON != "":
251-
assert.JSONEq(t, test.wantJSON, buf.String())
252-
case test.wantText != "":
253-
assert.Equal(t, test.wantText, buf.String())
254-
default:
255-
assert.Empty(t, buf.String())
256-
}
257-
})
253+
t.Run(test.name, test.Run)
254+
}
255+
}
256+
257+
const emptyMsg = "<<<EMPTY>>>"
258+
259+
type handlerTest struct {
260+
name string
261+
json bool
262+
want string
263+
// defaults to "hi". set to emptyMsg to use an empty message.
264+
msg string
265+
level slog.Level
266+
args []any
267+
268+
handlerFn func(t *testing.T, buf *bytes.Buffer, opts *slog.HandlerOptions) slog.Handler
269+
}
270+
271+
func (ht handlerTest) Run(t *testing.T) {
272+
t.Helper()
273+
buf := bytes.NewBuffer(nil)
274+
h := ht.handlerFn(t, buf, &slog.HandlerOptions{ReplaceAttr: removeKeys(slog.TimeKey)})
275+
l := slog.New(h)
276+
277+
msg := ht.msg
278+
switch msg {
279+
case "":
280+
msg = "hi"
281+
case emptyMsg:
282+
msg = ""
283+
}
284+
285+
l.Log(context.Background(), ht.level, msg, ht.args...)
286+
switch {
287+
case ht.want == "":
288+
assert.Empty(t, buf.String())
289+
case ht.json:
290+
assert.JSONEq(t, ht.want, buf.String())
291+
default:
292+
assert.Equal(t, ht.want, buf.String())
258293
}
259294
}
260295

0 commit comments

Comments
 (0)