Source file
src/log/slog/handler_test.go
1
2
3
4
5 package slog
6
7 import (
8 "bytes"
9 "context"
10 "encoding/json"
11 "fmt"
12 "io"
13 "log/slog/internal/buffer"
14 "os"
15 "path/filepath"
16 "slices"
17 "strconv"
18 "strings"
19 "sync"
20 "testing"
21 "time"
22 )
23
24 func TestDefaultHandle(t *testing.T) {
25 ctx := context.Background()
26 preAttrs := []Attr{Int("pre", 0)}
27 attrs := []Attr{Int("a", 1), String("b", "two")}
28 for _, test := range []struct {
29 name string
30 with func(Handler) Handler
31 attrs []Attr
32 want string
33 }{
34 {
35 name: "no attrs",
36 want: "INFO message",
37 },
38 {
39 name: "attrs",
40 attrs: attrs,
41 want: "INFO message a=1 b=two",
42 },
43 {
44 name: "preformatted",
45 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
46 attrs: attrs,
47 want: "INFO message pre=0 a=1 b=two",
48 },
49 {
50 name: "groups",
51 attrs: []Attr{
52 Int("a", 1),
53 Group("g",
54 Int("b", 2),
55 Group("h", Int("c", 3)),
56 Int("d", 4)),
57 Int("e", 5),
58 },
59 want: "INFO message a=1 g.b=2 g.h.c=3 g.d=4 e=5",
60 },
61 {
62 name: "group",
63 with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("s") },
64 attrs: attrs,
65 want: "INFO message pre=0 s.a=1 s.b=two",
66 },
67 {
68 name: "empty group",
69 with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("") },
70 attrs: attrs,
71 want: "INFO message pre=0 a=1 b=two",
72 },
73 {
74 name: "preformatted groups",
75 with: func(h Handler) Handler {
76 return h.WithAttrs([]Attr{Int("p1", 1)}).
77 WithGroup("s1").
78 WithAttrs([]Attr{Int("p2", 2)}).
79 WithGroup("s2")
80 },
81 attrs: attrs,
82 want: "INFO message p1=1 s1.p2=2 s1.s2.a=1 s1.s2.b=two",
83 },
84 {
85 name: "two with-groups",
86 with: func(h Handler) Handler {
87 return h.WithAttrs([]Attr{Int("p1", 1)}).
88 WithGroup("s1").
89 WithGroup("s2")
90 },
91 attrs: attrs,
92 want: "INFO message p1=1 s1.s2.a=1 s1.s2.b=two",
93 },
94 } {
95 t.Run(test.name, func(t *testing.T) {
96 var got string
97 var h Handler = newDefaultHandler(func(_ uintptr, b []byte) error {
98 got = string(b)
99 return nil
100 })
101 if test.with != nil {
102 h = test.with(h)
103 }
104 r := NewRecord(time.Time{}, LevelInfo, "message", 0)
105 r.AddAttrs(test.attrs...)
106 if err := h.Handle(ctx, r); err != nil {
107 t.Fatal(err)
108 }
109 if got != test.want {
110 t.Errorf("\ngot %s\nwant %s", got, test.want)
111 }
112 })
113 }
114 }
115
116 func TestConcurrentWrites(t *testing.T) {
117 ctx := context.Background()
118 count := 1000
119 for _, handlerType := range []string{"text", "json"} {
120 t.Run(handlerType, func(t *testing.T) {
121 var buf bytes.Buffer
122 var h Handler
123 switch handlerType {
124 case "text":
125 h = NewTextHandler(&buf, nil)
126 case "json":
127 h = NewJSONHandler(&buf, nil)
128 default:
129 t.Fatalf("unexpected handlerType %q", handlerType)
130 }
131 sub1 := h.WithAttrs([]Attr{Bool("sub1", true)})
132 sub2 := h.WithAttrs([]Attr{Bool("sub2", true)})
133 var wg sync.WaitGroup
134 for i := 0; i < count; i++ {
135 sub1Record := NewRecord(time.Time{}, LevelInfo, "hello from sub1", 0)
136 sub1Record.AddAttrs(Int("i", i))
137 sub2Record := NewRecord(time.Time{}, LevelInfo, "hello from sub2", 0)
138 sub2Record.AddAttrs(Int("i", i))
139 wg.Add(1)
140 go func() {
141 defer wg.Done()
142 if err := sub1.Handle(ctx, sub1Record); err != nil {
143 t.Error(err)
144 }
145 if err := sub2.Handle(ctx, sub2Record); err != nil {
146 t.Error(err)
147 }
148 }()
149 }
150 wg.Wait()
151 for i := 1; i <= 2; i++ {
152 want := "hello from sub" + strconv.Itoa(i)
153 n := strings.Count(buf.String(), want)
154 if n != count {
155 t.Fatalf("want %d occurrences of %q, got %d", count, want, n)
156 }
157 }
158 })
159 }
160 }
161
162
163 func TestJSONAndTextHandlers(t *testing.T) {
164
165 removeAll := func(_ []string, a Attr) Attr { return Attr{} }
166
167 attrs := []Attr{String("a", "one"), Int("b", 2), Any("", nil)}
168 preAttrs := []Attr{Int("pre", 3), String("x", "y")}
169
170 for _, test := range []struct {
171 name string
172 replace func([]string, Attr) Attr
173 addSource bool
174 with func(Handler) Handler
175 preAttrs []Attr
176 attrs []Attr
177 wantText string
178 wantJSON string
179 }{
180 {
181 name: "basic",
182 attrs: attrs,
183 wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2",
184 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2}`,
185 },
186 {
187 name: "empty key",
188 attrs: append(slices.Clip(attrs), Any("", "v")),
189 wantText: `time=2000-01-02T03:04:05.000Z level=INFO msg=message a=one b=2 ""=v`,
190 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","a":"one","b":2,"":"v"}`,
191 },
192 {
193 name: "cap keys",
194 replace: upperCaseKey,
195 attrs: attrs,
196 wantText: "TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG=message A=one B=2",
197 wantJSON: `{"TIME":"2000-01-02T03:04:05Z","LEVEL":"INFO","MSG":"message","A":"one","B":2}`,
198 },
199 {
200 name: "remove all",
201 replace: removeAll,
202 attrs: attrs,
203 wantText: "",
204 wantJSON: `{}`,
205 },
206 {
207 name: "preformatted",
208 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
209 preAttrs: preAttrs,
210 attrs: attrs,
211 wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message pre=3 x=y a=one b=2",
212 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","pre":3,"x":"y","a":"one","b":2}`,
213 },
214 {
215 name: "preformatted cap keys",
216 replace: upperCaseKey,
217 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
218 preAttrs: preAttrs,
219 attrs: attrs,
220 wantText: "TIME=2000-01-02T03:04:05.000Z LEVEL=INFO MSG=message PRE=3 X=y A=one B=2",
221 wantJSON: `{"TIME":"2000-01-02T03:04:05Z","LEVEL":"INFO","MSG":"message","PRE":3,"X":"y","A":"one","B":2}`,
222 },
223 {
224 name: "preformatted remove all",
225 replace: removeAll,
226 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
227 preAttrs: preAttrs,
228 attrs: attrs,
229 wantText: "",
230 wantJSON: "{}",
231 },
232 {
233 name: "remove built-in",
234 replace: removeKeys(TimeKey, LevelKey, MessageKey),
235 attrs: attrs,
236 wantText: "a=one b=2",
237 wantJSON: `{"a":"one","b":2}`,
238 },
239 {
240 name: "preformatted remove built-in",
241 replace: removeKeys(TimeKey, LevelKey, MessageKey),
242 with: func(h Handler) Handler { return h.WithAttrs(preAttrs) },
243 attrs: attrs,
244 wantText: "pre=3 x=y a=one b=2",
245 wantJSON: `{"pre":3,"x":"y","a":"one","b":2}`,
246 },
247 {
248 name: "groups",
249 replace: removeKeys(TimeKey, LevelKey),
250 attrs: []Attr{
251 Int("a", 1),
252 Group("g",
253 Int("b", 2),
254 Group("h", Int("c", 3)),
255 Int("d", 4)),
256 Int("e", 5),
257 },
258 wantText: "msg=message a=1 g.b=2 g.h.c=3 g.d=4 e=5",
259 wantJSON: `{"msg":"message","a":1,"g":{"b":2,"h":{"c":3},"d":4},"e":5}`,
260 },
261 {
262 name: "empty group",
263 replace: removeKeys(TimeKey, LevelKey),
264 attrs: []Attr{Group("g"), Group("h", Int("a", 1))},
265 wantText: "msg=message h.a=1",
266 wantJSON: `{"msg":"message","h":{"a":1}}`,
267 },
268 {
269 name: "nested empty group",
270 replace: removeKeys(TimeKey, LevelKey),
271 attrs: []Attr{
272 Group("g",
273 Group("h",
274 Group("i"), Group("j"))),
275 },
276 wantText: `msg=message`,
277 wantJSON: `{"msg":"message"}`,
278 },
279 {
280 name: "empty group with empty attr",
281 replace: removeKeys(TimeKey, LevelKey),
282 attrs: []Attr{
283 Int("a", 1),
284 Group("g", Attr{}),
285 Group("h", Group("i", Attr{}), Int("b", 2)),
286 Int("c", 3),
287 },
288 wantText: "msg=message a=1 h.b=2 c=3",
289 wantJSON: `{"msg":"message","a":1,"h":{"b":2},"c":3}`,
290 },
291 {
292 name: "nested non-empty group",
293 replace: removeKeys(TimeKey, LevelKey),
294 attrs: []Attr{
295 Group("g",
296 Group("h",
297 Group("i"), Group("j", Int("a", 1)))),
298 },
299 wantText: `msg=message g.h.j.a=1`,
300 wantJSON: `{"msg":"message","g":{"h":{"j":{"a":1}}}}`,
301 },
302 {
303 name: "escapes",
304 replace: removeKeys(TimeKey, LevelKey),
305 attrs: []Attr{
306 String("a b", "x\t\n\000y"),
307 Group(" b.c=\"\\x2E\t",
308 String("d=e", "f.g\""),
309 Int("m.d", 1)),
310 },
311 wantText: `msg=message "a b"="x\t\n\x00y" " b.c=\"\\x2E\t.d=e"="f.g\"" " b.c=\"\\x2E\t.m.d"=1`,
312 wantJSON: `{"msg":"message","a b":"x\t\n\u0000y"," b.c=\"\\x2E\t":{"d=e":"f.g\"","m.d":1}}`,
313 },
314 {
315 name: "LogValuer",
316 replace: removeKeys(TimeKey, LevelKey),
317 attrs: []Attr{
318 Int("a", 1),
319 Any("name", logValueName{"Ren", "Hoek"}),
320 Int("b", 2),
321 },
322 wantText: "msg=message a=1 name.first=Ren name.last=Hoek b=2",
323 wantJSON: `{"msg":"message","a":1,"name":{"first":"Ren","last":"Hoek"},"b":2}`,
324 },
325 {
326
327 name: "resolve",
328 attrs: []Attr{
329 Any("", &replace{Value{}}),
330 Any("name", logValueName{"Ren", "Hoek"}),
331 },
332 wantText: "time=2000-01-02T03:04:05.000Z level=INFO msg=message name.first=Ren name.last=Hoek",
333 wantJSON: `{"time":"2000-01-02T03:04:05Z","level":"INFO","msg":"message","name":{"first":"Ren","last":"Hoek"}}`,
334 },
335 {
336 name: "with-group",
337 replace: removeKeys(TimeKey, LevelKey),
338 with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("s") },
339 attrs: attrs,
340 wantText: "msg=message pre=3 x=y s.a=one s.b=2",
341 wantJSON: `{"msg":"message","pre":3,"x":"y","s":{"a":"one","b":2}}`,
342 },
343 {
344 name: "with-group empty name",
345 replace: removeKeys(TimeKey, LevelKey),
346 with: func(h Handler) Handler { return h.WithAttrs(preAttrs).WithGroup("") },
347 attrs: attrs,
348 wantText: "msg=message pre=3 x=y a=one b=2",
349 wantJSON: `{"msg":"message","pre":3,"x":"y","a":"one","b":2}`,
350 },
351 {
352 name: "preformatted with-groups",
353 replace: removeKeys(TimeKey, LevelKey),
354 with: func(h Handler) Handler {
355 return h.WithAttrs([]Attr{Int("p1", 1)}).
356 WithGroup("s1").
357 WithAttrs([]Attr{Int("p2", 2)}).
358 WithGroup("s2").
359 WithAttrs([]Attr{Int("p3", 3)})
360 },
361 attrs: attrs,
362 wantText: "msg=message p1=1 s1.p2=2 s1.s2.p3=3 s1.s2.a=one s1.s2.b=2",
363 wantJSON: `{"msg":"message","p1":1,"s1":{"p2":2,"s2":{"p3":3,"a":"one","b":2}}}`,
364 },
365 {
366 name: "two with-groups",
367 replace: removeKeys(TimeKey, LevelKey),
368 with: func(h Handler) Handler {
369 return h.WithAttrs([]Attr{Int("p1", 1)}).
370 WithGroup("s1").
371 WithGroup("s2")
372 },
373 attrs: attrs,
374 wantText: "msg=message p1=1 s1.s2.a=one s1.s2.b=2",
375 wantJSON: `{"msg":"message","p1":1,"s1":{"s2":{"a":"one","b":2}}}`,
376 },
377 {
378 name: "empty with-groups",
379 replace: removeKeys(TimeKey, LevelKey),
380 with: func(h Handler) Handler {
381 return h.WithGroup("x").WithGroup("y")
382 },
383 wantText: "msg=message",
384 wantJSON: `{"msg":"message"}`,
385 },
386 {
387 name: "empty with-groups, no non-empty attrs",
388 replace: removeKeys(TimeKey, LevelKey),
389 with: func(h Handler) Handler {
390 return h.WithGroup("x").WithAttrs([]Attr{Group("g")}).WithGroup("y")
391 },
392 wantText: "msg=message",
393 wantJSON: `{"msg":"message"}`,
394 },
395 {
396 name: "one empty with-group",
397 replace: removeKeys(TimeKey, LevelKey),
398 with: func(h Handler) Handler {
399 return h.WithGroup("x").WithAttrs([]Attr{Int("a", 1)}).WithGroup("y")
400 },
401 attrs: []Attr{Group("g", Group("h"))},
402 wantText: "msg=message x.a=1",
403 wantJSON: `{"msg":"message","x":{"a":1}}`,
404 },
405 {
406 name: "GroupValue as Attr value",
407 replace: removeKeys(TimeKey, LevelKey),
408 attrs: []Attr{{"v", AnyValue(IntValue(3))}},
409 wantText: "msg=message v=3",
410 wantJSON: `{"msg":"message","v":3}`,
411 },
412 {
413 name: "byte slice",
414 replace: removeKeys(TimeKey, LevelKey),
415 attrs: []Attr{Any("bs", []byte{1, 2, 3, 4})},
416 wantText: `msg=message bs="\x01\x02\x03\x04"`,
417 wantJSON: `{"msg":"message","bs":"AQIDBA=="}`,
418 },
419 {
420 name: "json.RawMessage",
421 replace: removeKeys(TimeKey, LevelKey),
422 attrs: []Attr{Any("bs", json.RawMessage([]byte("1234")))},
423 wantText: `msg=message bs="1234"`,
424 wantJSON: `{"msg":"message","bs":1234}`,
425 },
426 {
427 name: "inline group",
428 replace: removeKeys(TimeKey, LevelKey),
429 attrs: []Attr{
430 Int("a", 1),
431 Group("", Int("b", 2), Int("c", 3)),
432 Int("d", 4),
433 },
434 wantText: `msg=message a=1 b=2 c=3 d=4`,
435 wantJSON: `{"msg":"message","a":1,"b":2,"c":3,"d":4}`,
436 },
437 {
438 name: "Source",
439 replace: func(gs []string, a Attr) Attr {
440 if a.Key == SourceKey {
441 s := a.Value.Any().(*Source)
442 s.File = filepath.Base(s.File)
443 return Any(a.Key, s)
444 }
445 return removeKeys(TimeKey, LevelKey)(gs, a)
446 },
447 addSource: true,
448 wantText: `source=handler_test.go:$LINE msg=message`,
449 wantJSON: `{"source":{"function":"log/slog.TestJSONAndTextHandlers","file":"handler_test.go","line":$LINE},"msg":"message"}`,
450 },
451 {
452 name: "replace built-in with group",
453 replace: func(_ []string, a Attr) Attr {
454 if a.Key == TimeKey {
455 return Group(TimeKey, "mins", 3, "secs", 2)
456 }
457 if a.Key == LevelKey {
458 return Attr{}
459 }
460 return a
461 },
462 wantText: `time.mins=3 time.secs=2 msg=message`,
463 wantJSON: `{"time":{"mins":3,"secs":2},"msg":"message"}`,
464 },
465 {
466 name: "replace empty",
467 replace: func([]string, Attr) Attr { return Attr{} },
468 attrs: []Attr{Group("g", Int("a", 1))},
469 wantText: "",
470 wantJSON: `{}`,
471 },
472 {
473 name: "replace empty followed by attr",
474 replace: removeKeys(TimeKey, LevelKey, "a"),
475 attrs: []Attr{Group("g", Int("a", 1)), Group("h", Group("i", Int("a", 1)), Int("b", 2)), Int("c", 3)},
476 wantText: "msg=message h.b=2 c=3",
477 wantJSON: `{"msg":"message","h":{"b":2},"c":3}`,
478 },
479 {
480 name: "empty group from LogValuer and ReplaceAttr",
481 with: func(h Handler) Handler {
482 return h.WithGroup("w").WithAttrs([]Attr{Group("wg"), Attr{}})
483 },
484 replace: func(gs []string, a Attr) Attr {
485 if a.Key == "a" {
486 return Group("h")
487 }
488 return removeKeys(TimeKey, LevelKey)(gs, a)
489 },
490 attrs: []Attr{
491 Group("g1", Int("a", 1)),
492 Group("g2", Any("b", &replace{GroupValue()})),
493 },
494 wantText: "msg=message",
495 wantJSON: `{"msg":"message"}`,
496 },
497 {
498 name: "replace empty 1",
499 with: func(h Handler) Handler {
500 return h.WithGroup("g").WithAttrs([]Attr{Int("a", 1)})
501 },
502 replace: func([]string, Attr) Attr { return Attr{} },
503 attrs: []Attr{Group("h", Int("b", 2))},
504 wantText: "",
505 wantJSON: `{}`,
506 },
507 {
508 name: "replace empty 2",
509 with: func(h Handler) Handler {
510 return h.WithGroup("g").WithAttrs([]Attr{Int("a", 1)}).WithGroup("h").WithAttrs([]Attr{Int("b", 2)})
511 },
512 replace: func([]string, Attr) Attr { return Attr{} },
513 attrs: []Attr{Group("i", Int("c", 3))},
514 wantText: "",
515 wantJSON: `{}`,
516 },
517 {
518 name: "replace empty 3",
519 with: func(h Handler) Handler { return h.WithGroup("g") },
520 replace: func([]string, Attr) Attr { return Attr{} },
521 attrs: []Attr{Int("a", 1)},
522 wantText: "",
523 wantJSON: `{}`,
524 },
525 {
526 name: "replace empty inline",
527 with: func(h Handler) Handler {
528 return h.WithGroup("g").WithAttrs([]Attr{Int("a", 1)}).WithGroup("h").WithAttrs([]Attr{Int("b", 2)})
529 },
530 replace: func([]string, Attr) Attr { return Attr{} },
531 attrs: []Attr{Group("", Int("c", 3))},
532 wantText: "",
533 wantJSON: `{}`,
534 },
535 {
536 name: "replace partial empty attrs 1",
537 with: func(h Handler) Handler {
538 return h.WithGroup("g").WithAttrs([]Attr{Int("a", 1)}).WithGroup("h").WithAttrs([]Attr{Int("b", 2)})
539 },
540 replace: func(groups []string, attr Attr) Attr {
541 return removeKeys(TimeKey, LevelKey, MessageKey, "a")(groups, attr)
542 },
543 attrs: []Attr{Group("i", Int("c", 3))},
544 wantText: "g.h.b=2 g.h.i.c=3",
545 wantJSON: `{"g":{"h":{"b":2,"i":{"c":3}}}}`,
546 },
547 {
548 name: "replace partial empty attrs 2",
549 with: func(h Handler) Handler {
550 return h.WithGroup("g").WithAttrs([]Attr{Int("a", 1)}).WithAttrs([]Attr{Int("n", 4)}).WithGroup("h").WithAttrs([]Attr{Int("b", 2)})
551 },
552 replace: func(groups []string, attr Attr) Attr {
553 return removeKeys(TimeKey, LevelKey, MessageKey, "a", "b")(groups, attr)
554 },
555 attrs: []Attr{Group("i", Int("c", 3))},
556 wantText: "g.n=4 g.h.i.c=3",
557 wantJSON: `{"g":{"n":4,"h":{"i":{"c":3}}}}`,
558 },
559 {
560 name: "replace partial empty attrs 3",
561 with: func(h Handler) Handler {
562 return h.WithGroup("g").WithAttrs([]Attr{Int("x", 0)}).WithAttrs([]Attr{Int("a", 1)}).WithAttrs([]Attr{Int("n", 4)}).WithGroup("h").WithAttrs([]Attr{Int("b", 2)})
563 },
564 replace: func(groups []string, attr Attr) Attr {
565 return removeKeys(TimeKey, LevelKey, MessageKey, "a", "c")(groups, attr)
566 },
567 attrs: []Attr{Group("i", Int("c", 3))},
568 wantText: "g.x=0 g.n=4 g.h.b=2",
569 wantJSON: `{"g":{"x":0,"n":4,"h":{"b":2}}}`,
570 },
571 {
572 name: "replace resolved group",
573 replace: func(groups []string, a Attr) Attr {
574 if a.Value.Kind() == KindGroup {
575 return Attr{"bad", IntValue(1)}
576 }
577 return removeKeys(TimeKey, LevelKey, MessageKey)(groups, a)
578 },
579 attrs: []Attr{Any("name", logValueName{"Perry", "Platypus"})},
580 wantText: "name.first=Perry name.last=Platypus",
581 wantJSON: `{"name":{"first":"Perry","last":"Platypus"}}`,
582 },
583 {
584 name: "group and key (or both) needs quoting",
585 replace: removeKeys(TimeKey, LevelKey),
586 attrs: []Attr{
587 Group("prefix",
588 String(" needs quoting ", "v"), String("NotNeedsQuoting", "v"),
589 ),
590 Group("prefix needs quoting",
591 String(" needs quoting ", "v"), String("NotNeedsQuoting", "v"),
592 ),
593 },
594 wantText: `msg=message "prefix. needs quoting "=v prefix.NotNeedsQuoting=v "prefix needs quoting. needs quoting "=v "prefix needs quoting.NotNeedsQuoting"=v`,
595 wantJSON: `{"msg":"message","prefix":{" needs quoting ":"v","NotNeedsQuoting":"v"},"prefix needs quoting":{" needs quoting ":"v","NotNeedsQuoting":"v"}}`,
596 },
597 } {
598 r := NewRecord(testTime, LevelInfo, "message", callerPC(2))
599 source := r.Source()
600 if source == nil {
601 t.Fatal("source is nil")
602 }
603 line := strconv.Itoa(source.Line)
604 r.AddAttrs(test.attrs...)
605 var buf bytes.Buffer
606 opts := HandlerOptions{ReplaceAttr: test.replace, AddSource: test.addSource}
607 t.Run(test.name, func(t *testing.T) {
608 for _, handler := range []struct {
609 name string
610 h Handler
611 want string
612 }{
613 {"text", NewTextHandler(&buf, &opts), test.wantText},
614 {"json", NewJSONHandler(&buf, &opts), test.wantJSON},
615 } {
616 t.Run(handler.name, func(t *testing.T) {
617 h := handler.h
618 if test.with != nil {
619 h = test.with(h)
620 }
621 buf.Reset()
622 if err := h.Handle(nil, r); err != nil {
623 t.Fatal(err)
624 }
625 want := strings.ReplaceAll(handler.want, "$LINE", line)
626 got := strings.TrimSuffix(buf.String(), "\n")
627 if got != want {
628 t.Errorf("\ngot %s\nwant %s\n", got, want)
629 }
630 })
631 }
632 })
633 }
634 }
635
636
637
638 func removeKeys(keys ...string) func([]string, Attr) Attr {
639 return func(_ []string, a Attr) Attr {
640 for _, k := range keys {
641 if a.Key == k {
642 return Attr{}
643 }
644 }
645 return a
646 }
647 }
648
649 func upperCaseKey(_ []string, a Attr) Attr {
650 a.Key = strings.ToUpper(a.Key)
651 return a
652 }
653
654 type logValueName struct {
655 first, last string
656 }
657
658 func (n logValueName) LogValue() Value {
659 return GroupValue(
660 String("first", n.first),
661 String("last", n.last))
662 }
663
664 func TestHandlerEnabled(t *testing.T) {
665 levelVar := func(l Level) *LevelVar {
666 var al LevelVar
667 al.Set(l)
668 return &al
669 }
670
671 for _, test := range []struct {
672 leveler Leveler
673 want bool
674 }{
675 {nil, true},
676 {LevelWarn, false},
677 {&LevelVar{}, true},
678 {levelVar(LevelWarn), false},
679 {LevelDebug, true},
680 {levelVar(LevelDebug), true},
681 } {
682 h := &commonHandler{opts: HandlerOptions{Level: test.leveler}}
683 got := h.enabled(LevelInfo)
684 if got != test.want {
685 t.Errorf("%v: got %t, want %t", test.leveler, got, test.want)
686 }
687 }
688 }
689
690 func TestJSONAndTextHandlersWithUnavailableSource(t *testing.T) {
691
692
693 var buf bytes.Buffer
694 opts := &HandlerOptions{
695 ReplaceAttr: removeKeys(LevelKey),
696 AddSource: true,
697 }
698
699 for _, test := range []struct {
700 name string
701 h Handler
702 want string
703 }{
704 {"text", NewTextHandler(&buf, opts), "msg=message"},
705 {"json", NewJSONHandler(&buf, opts), `{"msg":"message"}`},
706 } {
707 t.Run(test.name, func(t *testing.T) {
708 buf.Reset()
709 r := NewRecord(time.Time{}, LevelInfo, "message", 0)
710 err := test.h.Handle(t.Context(), r)
711 if err != nil {
712 t.Fatal(err)
713 }
714
715 want := strings.TrimSpace(test.want)
716 got := strings.TrimSpace(buf.String())
717 if got != want {
718 t.Errorf("\ngot %s\nwant %s", got, want)
719 }
720 })
721 }
722 }
723
724 func TestSecondWith(t *testing.T) {
725
726
727 var buf bytes.Buffer
728 h := NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeKeys(TimeKey)})
729 logger := New(h).With(
730 String("app", "playground"),
731 String("role", "tester"),
732 Int("data_version", 2),
733 )
734 appLogger := logger.With("type", "log")
735 _ = logger.With("type", "metric")
736 appLogger.Info("foo")
737 got := strings.TrimSpace(buf.String())
738 want := `level=INFO msg=foo app=playground role=tester data_version=2 type=log`
739 if got != want {
740 t.Errorf("\ngot %s\nwant %s", got, want)
741 }
742 }
743
744 func TestReplaceAttrGroups(t *testing.T) {
745
746 type ga struct {
747 groups string
748 key string
749 val string
750 }
751
752 var got []ga
753
754 h := NewTextHandler(io.Discard, &HandlerOptions{ReplaceAttr: func(gs []string, a Attr) Attr {
755 v := a.Value.String()
756 if a.Key == TimeKey {
757 v = "<now>"
758 }
759 got = append(got, ga{strings.Join(gs, ","), a.Key, v})
760 return a
761 }})
762 New(h).
763 With(Int("a", 1)).
764 WithGroup("g1").
765 With(Int("b", 2)).
766 WithGroup("g2").
767 With(
768 Int("c", 3),
769 Group("g3", Int("d", 4)),
770 Int("e", 5)).
771 Info("m",
772 Int("f", 6),
773 Group("g4", Int("h", 7)),
774 Int("i", 8))
775
776 want := []ga{
777 {"", "a", "1"},
778 {"g1", "b", "2"},
779 {"g1,g2", "c", "3"},
780 {"g1,g2,g3", "d", "4"},
781 {"g1,g2", "e", "5"},
782 {"", "time", "<now>"},
783 {"", "level", "INFO"},
784 {"", "msg", "m"},
785 {"g1,g2", "f", "6"},
786 {"g1,g2,g4", "h", "7"},
787 {"g1,g2", "i", "8"},
788 }
789 if !slices.Equal(got, want) {
790 t.Errorf("\ngot %v\nwant %v", got, want)
791 }
792 }
793
794 const rfc3339Millis = "2006-01-02T15:04:05.000Z07:00"
795
796 func TestWriteTimeRFC3339(t *testing.T) {
797 for _, tm := range []time.Time{
798 time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC),
799 time.Date(2000, 1, 2, 3, 4, 5, 400, time.Local),
800 time.Date(2000, 11, 12, 3, 4, 500, 5e7, time.UTC),
801 } {
802 got := string(appendRFC3339Millis(nil, tm))
803 want := tm.Format(rfc3339Millis)
804 if got != want {
805 t.Errorf("got %s, want %s", got, want)
806 }
807 }
808 }
809
810 func BenchmarkWriteTime(b *testing.B) {
811 tm := time.Date(2022, 3, 4, 5, 6, 7, 823456789, time.Local)
812 var buf []byte
813 for b.Loop() {
814 buf = appendRFC3339Millis(buf[:0], tm)
815 }
816 }
817
818 func TestDiscardHandler(t *testing.T) {
819 ctx := context.Background()
820 stdout, stderr := os.Stdout, os.Stderr
821 os.Stdout, os.Stderr = nil, nil
822 t.Cleanup(func() {
823 os.Stdout, os.Stderr = stdout, stderr
824 })
825
826
827 l := New(DiscardHandler)
828 l.Info("msg", "a", 1, "b", 2)
829 l.Debug("bg", Int("a", 1), "b", 2)
830 l.Warn("w", Duration("dur", 3*time.Second))
831 l.Error("bad", "a", 1)
832 l.Log(ctx, LevelWarn+1, "w", Int("a", 1), String("b", "two"))
833 l.LogAttrs(ctx, LevelInfo+1, "a b c", Int("a", 1), String("b", "two"))
834 l.Info("info", "a", []Attr{Int("i", 1)})
835 l.Info("info", "a", GroupValue(Int("i", 1)))
836 }
837
838 func BenchmarkAppendKey(b *testing.B) {
839 for _, size := range []int{5, 10, 30, 50, 100} {
840 for _, quoting := range []string{"no_quoting", "pre_quoting", "key_quoting", "both_quoting"} {
841 b.Run(fmt.Sprintf("%s_prefix_size_%d", quoting, size), func(b *testing.B) {
842 var (
843 hs = NewJSONHandler(io.Discard, nil).newHandleState(buffer.New(), false, "")
844 prefix = bytes.Repeat([]byte("x"), size)
845 key = "key"
846 )
847
848 if quoting == "pre_quoting" || quoting == "both_quoting" {
849 prefix[0] = '"'
850 }
851 if quoting == "key_quoting" || quoting == "both_quoting" {
852 key = "ke\""
853 }
854
855 hs.prefix = (*buffer.Buffer)(&prefix)
856
857 for b.Loop() {
858 hs.appendKey(key)
859 hs.buf.Reset()
860 }
861 })
862 }
863 }
864 }
865
View as plain text