Skip to content

Commit 7366720

Browse files
panjf2000jba
authored andcommitted
log/slog: catch panics during formatting
Fixes #61648 Change-Id: I6b7f4948ca89142a358d74624607daf42ea8b304 Reviewed-on: https://go-review.googlesource.com/c/go/+/514135 Reviewed-by: Jonathan Amsterdam <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Andy Pan <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]>
1 parent 873f76d commit 7366720

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

src/log/slog/handler.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io"
1111
"log/slog/internal/buffer"
12+
"reflect"
1213
"slices"
1314
"strconv"
1415
"sync"
@@ -512,6 +513,23 @@ func (s *handleState) appendString(str string) {
512513
}
513514

514515
func (s *handleState) appendValue(v Value) {
516+
defer func() {
517+
if r := recover(); r != nil {
518+
// If it panics with a nil pointer, the most likely cases are
519+
// an encoding.TextMarshaler or error fails to guard against nil,
520+
// in which case "<nil>" seems to be the feasible choice.
521+
//
522+
// Adapted from the code in fmt/print.go.
523+
if v := reflect.ValueOf(v.any); v.Kind() == reflect.Pointer && v.IsNil() {
524+
s.appendString("<nil>")
525+
return
526+
}
527+
528+
// Otherwise just print the original panic message.
529+
s.appendString(fmt.Sprintf("!PANIC: %v", r))
530+
}
531+
}()
532+
515533
var err error
516534
if s.h.json {
517535
err = appendJSONValue(s, v)

src/log/slog/logger_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io"
1313
"log"
1414
loginternal "log/internal"
15+
"os"
1516
"path/filepath"
1617
"regexp"
1718
"runtime"
@@ -83,6 +84,13 @@ func TestConnections(t *testing.T) {
8384
Info("msg", "a", 1)
8485
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg a=1`)
8586
logbuf.Reset()
87+
Info("msg", "p", nil)
88+
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg p=<nil>`)
89+
logbuf.Reset()
90+
var r *regexp.Regexp
91+
Info("msg", "r", r)
92+
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg r=<nil>`)
93+
logbuf.Reset()
8694
Warn("msg", "b", 2)
8795
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: WARN msg b=2`)
8896
logbuf.Reset()
@@ -571,3 +579,62 @@ func wantAllocs(t *testing.T, want int, f func()) {
571579
t.Errorf("got %d allocs, want %d", got, want)
572580
}
573581
}
582+
583+
// panicTextAndJsonMarshaler is a type that panics in MarshalText and MarshalJSON.
584+
type panicTextAndJsonMarshaler struct {
585+
msg any
586+
}
587+
588+
func (p panicTextAndJsonMarshaler) MarshalText() ([]byte, error) {
589+
panic(p.msg)
590+
}
591+
592+
func (p panicTextAndJsonMarshaler) MarshalJSON() ([]byte, error) {
593+
panic(p.msg)
594+
}
595+
596+
func TestPanics(t *testing.T) {
597+
// Revert any changes to the default logger. This is important because other
598+
// tests might change the default logger using SetDefault. Also ensure we
599+
// restore the default logger at the end of the test.
600+
currentLogger := Default()
601+
t.Cleanup(func() {
602+
SetDefault(currentLogger)
603+
log.SetOutput(os.Stderr)
604+
log.SetFlags(log.LstdFlags)
605+
})
606+
607+
var logBuf bytes.Buffer
608+
log.SetOutput(&logBuf)
609+
log.SetFlags(log.Lshortfile &^ log.LstdFlags)
610+
611+
SetDefault(New(newDefaultHandler(loginternal.DefaultOutput)))
612+
for _, pt := range []struct {
613+
in any
614+
out string
615+
}{
616+
{(*panicTextAndJsonMarshaler)(nil), `logger_test.go:\d+: INFO msg p=<nil>`},
617+
{panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `logger_test.go:\d+: INFO msg p="!PANIC: unexpected EOF"`},
618+
{panicTextAndJsonMarshaler{"panicking"}, `logger_test.go:\d+: INFO msg p="!PANIC: panicking"`},
619+
{panicTextAndJsonMarshaler{42}, `logger_test.go:\d+: INFO msg p="!PANIC: 42"`},
620+
} {
621+
Info("msg", "p", pt.in)
622+
checkLogOutput(t, logBuf.String(), pt.out)
623+
logBuf.Reset()
624+
}
625+
626+
SetDefault(New(NewJSONHandler(&logBuf, nil)))
627+
for _, pt := range []struct {
628+
in any
629+
out string
630+
}{
631+
{(*panicTextAndJsonMarshaler)(nil), `{"time":".*?","level":"INFO","msg":"msg","p":null}`},
632+
{panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: unexpected EOF"}`},
633+
{panicTextAndJsonMarshaler{"panicking"}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: panicking"}`},
634+
{panicTextAndJsonMarshaler{42}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: 42"}`},
635+
} {
636+
Info("msg", "p", pt.in)
637+
checkLogOutput(t, logBuf.String(), pt.out)
638+
logBuf.Reset()
639+
}
640+
}

0 commit comments

Comments
 (0)