]> Cypherpunks.ru repositories - gostls13.git/blob - src/log/slog/logger_test.go
log/slog: fix Record.back slice always too small during Add()
[gostls13.git] / src / log / slog / logger_test.go
1 // Copyright 2022 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package slog
6
7 import (
8         "bytes"
9         "context"
10         "internal/race"
11         "internal/testenv"
12         "io"
13         "log"
14         loginternal "log/internal"
15         "path/filepath"
16         "regexp"
17         "runtime"
18         "slices"
19         "strings"
20         "sync"
21         "testing"
22         "time"
23 )
24
25 const timeRE = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,9}(Z|[+-]\d{2}:\d{2})`
26
27 func TestLogTextHandler(t *testing.T) {
28         ctx := context.Background()
29         var buf bytes.Buffer
30
31         l := New(NewTextHandler(&buf, nil))
32
33         check := func(want string) {
34                 t.Helper()
35                 if want != "" {
36                         want = "time=" + timeRE + " " + want
37                 }
38                 checkLogOutput(t, buf.String(), want)
39                 buf.Reset()
40         }
41
42         l.Info("msg", "a", 1, "b", 2)
43         check(`level=INFO msg=msg a=1 b=2`)
44
45         // By default, debug messages are not printed.
46         l.Debug("bg", Int("a", 1), "b", 2)
47         check("")
48
49         l.Warn("w", Duration("dur", 3*time.Second))
50         check(`level=WARN msg=w dur=3s`)
51
52         l.Error("bad", "a", 1)
53         check(`level=ERROR msg=bad a=1`)
54
55         l.Log(ctx, LevelWarn+1, "w", Int("a", 1), String("b", "two"))
56         check(`level=WARN\+1 msg=w a=1 b=two`)
57
58         l.LogAttrs(ctx, LevelInfo+1, "a b c", Int("a", 1), String("b", "two"))
59         check(`level=INFO\+1 msg="a b c" a=1 b=two`)
60
61         l.Info("info", "a", []Attr{Int("i", 1)})
62         check(`level=INFO msg=info a.i=1`)
63
64         l.Info("info", "a", GroupValue(Int("i", 1)))
65         check(`level=INFO msg=info a.i=1`)
66 }
67
68 func TestConnections(t *testing.T) {
69         var logbuf, slogbuf bytes.Buffer
70
71         // Revert any changes to the default logger. This is important because other
72         // tests might change the default logger using SetDefault. Also ensure we
73         // restore the default logger at the end of the test.
74         currentLogger := Default()
75         currentLogWriter := log.Writer()
76         currentLogFlags := log.Flags()
77         SetDefault(New(newDefaultHandler(loginternal.DefaultOutput)))
78         t.Cleanup(func() {
79                 SetDefault(currentLogger)
80                 log.SetOutput(currentLogWriter)
81                 log.SetFlags(currentLogFlags)
82         })
83
84         // The default slog.Logger's handler uses the log package's default output.
85         log.SetOutput(&logbuf)
86         log.SetFlags(log.Lshortfile &^ log.LstdFlags)
87         Info("msg", "a", 1)
88         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg a=1`)
89         logbuf.Reset()
90         Info("msg", "p", nil)
91         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg p=<nil>`)
92         logbuf.Reset()
93         var r *regexp.Regexp
94         Info("msg", "r", r)
95         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg r=<nil>`)
96         logbuf.Reset()
97         Warn("msg", "b", 2)
98         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: WARN msg b=2`)
99         logbuf.Reset()
100         Error("msg", "err", io.EOF, "c", 3)
101         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: ERROR msg err=EOF c=3`)
102
103         // Levels below Info are not printed.
104         logbuf.Reset()
105         Debug("msg", "c", 3)
106         checkLogOutput(t, logbuf.String(), "")
107
108         t.Run("wrap default handler", func(t *testing.T) {
109                 // It should be possible to wrap the default handler and get the right output.
110                 // This works because the default handler uses the pc in the Record
111                 // to get the source line, rather than a call depth.
112                 logger := New(wrappingHandler{Default().Handler()})
113                 logger.Info("msg", "d", 4)
114                 checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg d=4`)
115         })
116
117         // Once slog.SetDefault is called, the direction is reversed: the default
118         // log.Logger's output goes through the handler.
119         SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{AddSource: true})))
120         log.Print("msg2")
121         checkLogOutput(t, slogbuf.String(), "time="+timeRE+` level=INFO source=.*logger_test.go:\d{3}"? msg=msg2`)
122
123         // The default log.Logger always outputs at Info level.
124         slogbuf.Reset()
125         SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{Level: LevelWarn})))
126         log.Print("should not appear")
127         if got := slogbuf.String(); got != "" {
128                 t.Errorf("got %q, want empty", got)
129         }
130
131         // Setting log's output again breaks the connection.
132         logbuf.Reset()
133         slogbuf.Reset()
134         log.SetOutput(&logbuf)
135         log.SetFlags(log.Lshortfile &^ log.LstdFlags)
136         log.Print("msg3")
137         checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: msg3`)
138         if got := slogbuf.String(); got != "" {
139                 t.Errorf("got %q, want empty", got)
140         }
141 }
142
143 type wrappingHandler struct {
144         h Handler
145 }
146
147 func (h wrappingHandler) Enabled(ctx context.Context, level Level) bool {
148         return h.h.Enabled(ctx, level)
149 }
150 func (h wrappingHandler) WithGroup(name string) Handler              { return h.h.WithGroup(name) }
151 func (h wrappingHandler) WithAttrs(as []Attr) Handler                { return h.h.WithAttrs(as) }
152 func (h wrappingHandler) Handle(ctx context.Context, r Record) error { return h.h.Handle(ctx, r) }
153
154 func TestAttrs(t *testing.T) {
155         check := func(got []Attr, want ...Attr) {
156                 t.Helper()
157                 if !attrsEqual(got, want) {
158                         t.Errorf("got %v, want %v", got, want)
159                 }
160         }
161
162         l1 := New(&captureHandler{}).With("a", 1)
163         l2 := New(l1.Handler()).With("b", 2)
164         l2.Info("m", "c", 3)
165         h := l2.Handler().(*captureHandler)
166         check(h.attrs, Int("a", 1), Int("b", 2))
167         check(attrsSlice(h.r), Int("c", 3))
168 }
169
170 func TestCallDepth(t *testing.T) {
171         ctx := context.Background()
172         h := &captureHandler{}
173         var startLine int
174
175         check := func(count int) {
176                 t.Helper()
177                 const wantFunc = "log/slog.TestCallDepth"
178                 const wantFile = "logger_test.go"
179                 wantLine := startLine + count*2
180                 got := h.r.source()
181                 gotFile := filepath.Base(got.File)
182                 if got.Function != wantFunc || gotFile != wantFile || got.Line != wantLine {
183                         t.Errorf("got (%s, %s, %d), want (%s, %s, %d)",
184                                 got.Function, gotFile, got.Line, wantFunc, wantFile, wantLine)
185                 }
186         }
187
188         logger := New(h)
189         SetDefault(logger)
190
191         // Calls to check must be one line apart.
192         // Determine line where calls start.
193         f, _ := runtime.CallersFrames([]uintptr{callerPC(2)}).Next()
194         startLine = f.Line + 4
195         // Do not change the number of lines between here and the call to check(0).
196
197         logger.Log(ctx, LevelInfo, "")
198         check(0)
199         logger.LogAttrs(ctx, LevelInfo, "")
200         check(1)
201         logger.Debug("")
202         check(2)
203         logger.Info("")
204         check(3)
205         logger.Warn("")
206         check(4)
207         logger.Error("")
208         check(5)
209         Debug("")
210         check(6)
211         Info("")
212         check(7)
213         Warn("")
214         check(8)
215         Error("")
216         check(9)
217         Log(ctx, LevelInfo, "")
218         check(10)
219         LogAttrs(ctx, LevelInfo, "")
220         check(11)
221 }
222
223 func TestAlloc(t *testing.T) {
224         ctx := context.Background()
225         dl := New(discardHandler{})
226         defer SetDefault(Default()) // restore
227         SetDefault(dl)
228
229         t.Run("Info", func(t *testing.T) {
230                 wantAllocs(t, 0, func() { Info("hello") })
231         })
232         t.Run("Error", func(t *testing.T) {
233                 wantAllocs(t, 0, func() { Error("hello") })
234         })
235         t.Run("logger.Info", func(t *testing.T) {
236                 wantAllocs(t, 0, func() { dl.Info("hello") })
237         })
238         t.Run("logger.Log", func(t *testing.T) {
239                 wantAllocs(t, 0, func() { dl.Log(ctx, LevelDebug, "hello") })
240         })
241         t.Run("2 pairs", func(t *testing.T) {
242                 s := "abc"
243                 i := 2000
244                 wantAllocs(t, 2, func() {
245                         dl.Info("hello",
246                                 "n", i,
247                                 "s", s,
248                         )
249                 })
250         })
251         t.Run("2 pairs disabled inline", func(t *testing.T) {
252                 l := New(discardHandler{disabled: true})
253                 s := "abc"
254                 i := 2000
255                 wantAllocs(t, 2, func() {
256                         l.Log(ctx, LevelInfo, "hello",
257                                 "n", i,
258                                 "s", s,
259                         )
260                 })
261         })
262         t.Run("2 pairs disabled", func(t *testing.T) {
263                 l := New(discardHandler{disabled: true})
264                 s := "abc"
265                 i := 2000
266                 wantAllocs(t, 0, func() {
267                         if l.Enabled(ctx, LevelInfo) {
268                                 l.Log(ctx, LevelInfo, "hello",
269                                         "n", i,
270                                         "s", s,
271                                 )
272                         }
273                 })
274         })
275         t.Run("9 kvs", func(t *testing.T) {
276                 s := "abc"
277                 i := 2000
278                 d := time.Second
279                 wantAllocs(t, 10, func() {
280                         dl.Info("hello",
281                                 "n", i, "s", s, "d", d,
282                                 "n", i, "s", s, "d", d,
283                                 "n", i, "s", s, "d", d)
284                 })
285         })
286         t.Run("pairs", func(t *testing.T) {
287                 wantAllocs(t, 0, func() { dl.Info("", "error", io.EOF) })
288         })
289         t.Run("attrs1", func(t *testing.T) {
290                 wantAllocs(t, 0, func() { dl.LogAttrs(ctx, LevelInfo, "", Int("a", 1)) })
291                 wantAllocs(t, 0, func() { dl.LogAttrs(ctx, LevelInfo, "", Any("error", io.EOF)) })
292         })
293         t.Run("attrs3", func(t *testing.T) {
294                 wantAllocs(t, 0, func() {
295                         dl.LogAttrs(ctx, LevelInfo, "hello", Int("a", 1), String("b", "two"), Duration("c", time.Second))
296                 })
297         })
298         t.Run("attrs3 disabled", func(t *testing.T) {
299                 logger := New(discardHandler{disabled: true})
300                 wantAllocs(t, 0, func() {
301                         logger.LogAttrs(ctx, LevelInfo, "hello", Int("a", 1), String("b", "two"), Duration("c", time.Second))
302                 })
303         })
304         t.Run("attrs6", func(t *testing.T) {
305                 wantAllocs(t, 1, func() {
306                         dl.LogAttrs(ctx, LevelInfo, "hello",
307                                 Int("a", 1), String("b", "two"), Duration("c", time.Second),
308                                 Int("d", 1), String("e", "two"), Duration("f", time.Second))
309                 })
310         })
311         t.Run("attrs9", func(t *testing.T) {
312                 wantAllocs(t, 1, func() {
313                         dl.LogAttrs(ctx, LevelInfo, "hello",
314                                 Int("a", 1), String("b", "two"), Duration("c", time.Second),
315                                 Int("d", 1), String("e", "two"), Duration("f", time.Second),
316                                 Int("d", 1), String("e", "two"), Duration("f", time.Second))
317                 })
318         })
319 }
320
321 func TestSetAttrs(t *testing.T) {
322         for _, test := range []struct {
323                 args []any
324                 want []Attr
325         }{
326                 {nil, nil},
327                 {[]any{"a", 1}, []Attr{Int("a", 1)}},
328                 {[]any{"a", 1, "b", "two"}, []Attr{Int("a", 1), String("b", "two")}},
329                 {[]any{"a"}, []Attr{String(badKey, "a")}},
330                 {[]any{"a", 1, "b"}, []Attr{Int("a", 1), String(badKey, "b")}},
331                 {[]any{"a", 1, 2, 3}, []Attr{Int("a", 1), Int(badKey, 2), Int(badKey, 3)}},
332         } {
333                 r := NewRecord(time.Time{}, 0, "", 0)
334                 r.Add(test.args...)
335                 got := attrsSlice(r)
336                 if !attrsEqual(got, test.want) {
337                         t.Errorf("%v:\ngot  %v\nwant %v", test.args, got, test.want)
338                 }
339         }
340 }
341
342 func TestSetDefault(t *testing.T) {
343         // Verify that setting the default to itself does not result in deadlock.
344         ctx, cancel := context.WithTimeout(context.Background(), time.Second)
345         defer cancel()
346         defer func(w io.Writer) { log.SetOutput(w) }(log.Writer())
347         log.SetOutput(io.Discard)
348         go func() {
349                 Info("A")
350                 SetDefault(Default())
351                 Info("B")
352                 cancel()
353         }()
354         <-ctx.Done()
355         if err := ctx.Err(); err != context.Canceled {
356                 t.Errorf("wanted canceled, got %v", err)
357         }
358 }
359
360 func TestLoggerError(t *testing.T) {
361         var buf bytes.Buffer
362
363         removeTime := func(_ []string, a Attr) Attr {
364                 if a.Key == TimeKey {
365                         return Attr{}
366                 }
367                 return a
368         }
369         l := New(NewTextHandler(&buf, &HandlerOptions{ReplaceAttr: removeTime}))
370         l.Error("msg", "err", io.EOF, "a", 1)
371         checkLogOutput(t, buf.String(), `level=ERROR msg=msg err=EOF a=1`)
372         buf.Reset()
373         // use local var 'args' to defeat vet check
374         args := []any{"err", io.EOF, "a"}
375         l.Error("msg", args...)
376         checkLogOutput(t, buf.String(), `level=ERROR msg=msg err=EOF !BADKEY=a`)
377 }
378
379 func TestNewLogLogger(t *testing.T) {
380         var buf bytes.Buffer
381         h := NewTextHandler(&buf, nil)
382         ll := NewLogLogger(h, LevelWarn)
383         ll.Print("hello")
384         checkLogOutput(t, buf.String(), "time="+timeRE+` level=WARN msg=hello`)
385 }
386
387 func TestLoggerNoOps(t *testing.T) {
388         l := Default()
389         if l.With() != l {
390                 t.Error("wanted receiver, didn't get it")
391         }
392         if With() != l {
393                 t.Error("wanted receiver, didn't get it")
394         }
395         if l.WithGroup("") != l {
396                 t.Error("wanted receiver, didn't get it")
397         }
398 }
399
400 func TestContext(t *testing.T) {
401         // Verify that the context argument to log output methods is passed to the handler.
402         // Also check the level.
403         h := &captureHandler{}
404         l := New(h)
405         defer SetDefault(Default()) // restore
406         SetDefault(l)
407
408         for _, test := range []struct {
409                 f         func(context.Context, string, ...any)
410                 wantLevel Level
411         }{
412                 {l.DebugContext, LevelDebug},
413                 {l.InfoContext, LevelInfo},
414                 {l.WarnContext, LevelWarn},
415                 {l.ErrorContext, LevelError},
416                 {DebugContext, LevelDebug},
417                 {InfoContext, LevelInfo},
418                 {WarnContext, LevelWarn},
419                 {ErrorContext, LevelError},
420         } {
421                 h.clear()
422                 ctx := context.WithValue(context.Background(), "L", test.wantLevel)
423
424                 test.f(ctx, "msg")
425                 if gv := h.ctx.Value("L"); gv != test.wantLevel || h.r.Level != test.wantLevel {
426                         t.Errorf("got context value %v, level %s; want %s for both", gv, h.r.Level, test.wantLevel)
427                 }
428         }
429 }
430
431 func checkLogOutput(t *testing.T, got, wantRegexp string) {
432         t.Helper()
433         got = clean(got)
434         wantRegexp = "^" + wantRegexp + "$"
435         matched, err := regexp.MatchString(wantRegexp, got)
436         if err != nil {
437                 t.Fatal(err)
438         }
439         if !matched {
440                 t.Errorf("\ngot  %s\nwant %s", got, wantRegexp)
441         }
442 }
443
444 // clean prepares log output for comparison.
445 func clean(s string) string {
446         if len(s) > 0 && s[len(s)-1] == '\n' {
447                 s = s[:len(s)-1]
448         }
449         return strings.ReplaceAll(s, "\n", "~")
450 }
451
452 type captureHandler struct {
453         mu     sync.Mutex
454         ctx    context.Context
455         r      Record
456         attrs  []Attr
457         groups []string
458 }
459
460 func (h *captureHandler) Handle(ctx context.Context, r Record) error {
461         h.mu.Lock()
462         defer h.mu.Unlock()
463         h.ctx = ctx
464         h.r = r
465         return nil
466 }
467
468 func (*captureHandler) Enabled(context.Context, Level) bool { return true }
469
470 func (c *captureHandler) WithAttrs(as []Attr) Handler {
471         c.mu.Lock()
472         defer c.mu.Unlock()
473         var c2 captureHandler
474         c2.r = c.r
475         c2.groups = c.groups
476         c2.attrs = concat(c.attrs, as)
477         return &c2
478 }
479
480 func (c *captureHandler) WithGroup(name string) Handler {
481         c.mu.Lock()
482         defer c.mu.Unlock()
483         var c2 captureHandler
484         c2.r = c.r
485         c2.attrs = c.attrs
486         c2.groups = append(slices.Clip(c.groups), name)
487         return &c2
488 }
489
490 func (c *captureHandler) clear() {
491         c.mu.Lock()
492         defer c.mu.Unlock()
493         c.ctx = nil
494         c.r = Record{}
495 }
496
497 type discardHandler struct {
498         disabled bool
499         attrs    []Attr
500 }
501
502 func (d discardHandler) Enabled(context.Context, Level) bool { return !d.disabled }
503 func (discardHandler) Handle(context.Context, Record) error  { return nil }
504 func (d discardHandler) WithAttrs(as []Attr) Handler {
505         d.attrs = concat(d.attrs, as)
506         return d
507 }
508 func (h discardHandler) WithGroup(name string) Handler {
509         return h
510 }
511
512 // concat returns a new slice with the elements of s1 followed
513 // by those of s2. The slice has no additional capacity.
514 func concat[T any](s1, s2 []T) []T {
515         s := make([]T, len(s1)+len(s2))
516         copy(s, s1)
517         copy(s[len(s1):], s2)
518         return s
519 }
520
521 // This is a simple benchmark. See the benchmarks subdirectory for more extensive ones.
522 func BenchmarkNopLog(b *testing.B) {
523         ctx := context.Background()
524         l := New(&captureHandler{})
525         b.Run("no attrs", func(b *testing.B) {
526                 b.ReportAllocs()
527                 for i := 0; i < b.N; i++ {
528                         l.LogAttrs(ctx, LevelInfo, "msg")
529                 }
530         })
531         b.Run("attrs", func(b *testing.B) {
532                 b.ReportAllocs()
533                 for i := 0; i < b.N; i++ {
534                         l.LogAttrs(ctx, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
535                 }
536         })
537         b.Run("attrs-parallel", func(b *testing.B) {
538                 b.ReportAllocs()
539                 b.RunParallel(func(pb *testing.PB) {
540                         for pb.Next() {
541                                 l.LogAttrs(ctx, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
542                         }
543                 })
544         })
545         b.Run("keys-values", func(b *testing.B) {
546                 b.ReportAllocs()
547                 for i := 0; i < b.N; i++ {
548                         l.Log(ctx, LevelInfo, "msg", "a", 1, "b", "two", "c", true)
549                 }
550         })
551         b.Run("WithContext", func(b *testing.B) {
552                 b.ReportAllocs()
553                 for i := 0; i < b.N; i++ {
554                         l.LogAttrs(ctx, LevelInfo, "msg2", Int("a", 1), String("b", "two"), Bool("c", true))
555                 }
556         })
557         b.Run("WithContext-parallel", func(b *testing.B) {
558                 b.ReportAllocs()
559                 b.RunParallel(func(pb *testing.PB) {
560                         for pb.Next() {
561                                 l.LogAttrs(ctx, LevelInfo, "msg", Int("a", 1), String("b", "two"), Bool("c", true))
562                         }
563                 })
564         })
565 }
566
567 // callerPC returns the program counter at the given stack depth.
568 func callerPC(depth int) uintptr {
569         var pcs [1]uintptr
570         runtime.Callers(depth, pcs[:])
571         return pcs[0]
572 }
573
574 func wantAllocs(t *testing.T, want int, f func()) {
575         if race.Enabled {
576                 t.Skip("skipping test in race mode")
577         }
578         testenv.SkipIfOptimizationOff(t)
579         t.Helper()
580         got := int(testing.AllocsPerRun(5, f))
581         if got != want {
582                 t.Errorf("got %d allocs, want %d", got, want)
583         }
584 }
585
586 // panicTextAndJsonMarshaler is a type that panics in MarshalText and MarshalJSON.
587 type panicTextAndJsonMarshaler struct {
588         msg any
589 }
590
591 func (p panicTextAndJsonMarshaler) MarshalText() ([]byte, error) {
592         panic(p.msg)
593 }
594
595 func (p panicTextAndJsonMarshaler) MarshalJSON() ([]byte, error) {
596         panic(p.msg)
597 }
598
599 func TestPanics(t *testing.T) {
600         // Revert any changes to the default logger. This is important because other
601         // tests might change the default logger using SetDefault. Also ensure we
602         // restore the default logger at the end of the test.
603         currentLogger := Default()
604         currentLogWriter := log.Writer()
605         currentLogFlags := log.Flags()
606         t.Cleanup(func() {
607                 SetDefault(currentLogger)
608                 log.SetOutput(currentLogWriter)
609                 log.SetFlags(currentLogFlags)
610         })
611
612         var logBuf bytes.Buffer
613         log.SetOutput(&logBuf)
614         log.SetFlags(log.Lshortfile &^ log.LstdFlags)
615
616         SetDefault(New(newDefaultHandler(loginternal.DefaultOutput)))
617         for _, pt := range []struct {
618                 in  any
619                 out string
620         }{
621                 {(*panicTextAndJsonMarshaler)(nil), `logger_test.go:\d+: INFO msg p=<nil>`},
622                 {panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `logger_test.go:\d+: INFO msg p="!PANIC: unexpected EOF"`},
623                 {panicTextAndJsonMarshaler{"panicking"}, `logger_test.go:\d+: INFO msg p="!PANIC: panicking"`},
624                 {panicTextAndJsonMarshaler{42}, `logger_test.go:\d+: INFO msg p="!PANIC: 42"`},
625         } {
626                 Info("msg", "p", pt.in)
627                 checkLogOutput(t, logBuf.String(), pt.out)
628                 logBuf.Reset()
629         }
630
631         SetDefault(New(NewJSONHandler(&logBuf, nil)))
632         for _, pt := range []struct {
633                 in  any
634                 out string
635         }{
636                 {(*panicTextAndJsonMarshaler)(nil), `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":null}`},
637                 {panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":"!PANIC: unexpected EOF"}`},
638                 {panicTextAndJsonMarshaler{"panicking"}, `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":"!PANIC: panicking"}`},
639                 {panicTextAndJsonMarshaler{42}, `{"time":"` + timeRE + `","level":"INFO","msg":"msg","p":"!PANIC: 42"}`},
640         } {
641                 Info("msg", "p", pt.in)
642                 checkLogOutput(t, logBuf.String(), pt.out)
643                 logBuf.Reset()
644         }
645 }