1
0
mirror of https://github.com/golang/go synced 2024-11-17 16:34:43 -07:00

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 <jba@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Andy Pan <panjf2000@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Andy Pan 2023-07-30 11:42:14 +08:00 committed by Jonathan Amsterdam
parent 873f76d27b
commit 73667209c1
2 changed files with 85 additions and 0 deletions

View File

@ -9,6 +9,7 @@ import (
"fmt"
"io"
"log/slog/internal/buffer"
"reflect"
"slices"
"strconv"
"sync"
@ -512,6 +513,23 @@ func (s *handleState) appendString(str string) {
}
func (s *handleState) appendValue(v Value) {
defer func() {
if r := recover(); r != nil {
// If it panics with a nil pointer, the most likely cases are
// an encoding.TextMarshaler or error fails to guard against nil,
// in which case "<nil>" seems to be the feasible choice.
//
// Adapted from the code in fmt/print.go.
if v := reflect.ValueOf(v.any); v.Kind() == reflect.Pointer && v.IsNil() {
s.appendString("<nil>")
return
}
// Otherwise just print the original panic message.
s.appendString(fmt.Sprintf("!PANIC: %v", r))
}
}()
var err error
if s.h.json {
err = appendJSONValue(s, v)

View File

@ -12,6 +12,7 @@ import (
"io"
"log"
loginternal "log/internal"
"os"
"path/filepath"
"regexp"
"runtime"
@ -83,6 +84,13 @@ func TestConnections(t *testing.T) {
Info("msg", "a", 1)
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg a=1`)
logbuf.Reset()
Info("msg", "p", nil)
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg p=<nil>`)
logbuf.Reset()
var r *regexp.Regexp
Info("msg", "r", r)
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: INFO msg r=<nil>`)
logbuf.Reset()
Warn("msg", "b", 2)
checkLogOutput(t, logbuf.String(), `logger_test.go:\d+: WARN msg b=2`)
logbuf.Reset()
@ -571,3 +579,62 @@ func wantAllocs(t *testing.T, want int, f func()) {
t.Errorf("got %d allocs, want %d", got, want)
}
}
// panicTextAndJsonMarshaler is a type that panics in MarshalText and MarshalJSON.
type panicTextAndJsonMarshaler struct {
msg any
}
func (p panicTextAndJsonMarshaler) MarshalText() ([]byte, error) {
panic(p.msg)
}
func (p panicTextAndJsonMarshaler) MarshalJSON() ([]byte, error) {
panic(p.msg)
}
func TestPanics(t *testing.T) {
// Revert any changes to the default logger. This is important because other
// tests might change the default logger using SetDefault. Also ensure we
// restore the default logger at the end of the test.
currentLogger := Default()
t.Cleanup(func() {
SetDefault(currentLogger)
log.SetOutput(os.Stderr)
log.SetFlags(log.LstdFlags)
})
var logBuf bytes.Buffer
log.SetOutput(&logBuf)
log.SetFlags(log.Lshortfile &^ log.LstdFlags)
SetDefault(New(newDefaultHandler(loginternal.DefaultOutput)))
for _, pt := range []struct {
in any
out string
}{
{(*panicTextAndJsonMarshaler)(nil), `logger_test.go:\d+: INFO msg p=<nil>`},
{panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `logger_test.go:\d+: INFO msg p="!PANIC: unexpected EOF"`},
{panicTextAndJsonMarshaler{"panicking"}, `logger_test.go:\d+: INFO msg p="!PANIC: panicking"`},
{panicTextAndJsonMarshaler{42}, `logger_test.go:\d+: INFO msg p="!PANIC: 42"`},
} {
Info("msg", "p", pt.in)
checkLogOutput(t, logBuf.String(), pt.out)
logBuf.Reset()
}
SetDefault(New(NewJSONHandler(&logBuf, nil)))
for _, pt := range []struct {
in any
out string
}{
{(*panicTextAndJsonMarshaler)(nil), `{"time":".*?","level":"INFO","msg":"msg","p":null}`},
{panicTextAndJsonMarshaler{io.ErrUnexpectedEOF}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: unexpected EOF"}`},
{panicTextAndJsonMarshaler{"panicking"}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: panicking"}`},
{panicTextAndJsonMarshaler{42}, `{"time":".*?","level":"INFO","msg":"msg","p":"!PANIC: 42"}`},
} {
Info("msg", "p", pt.in)
checkLogOutput(t, logBuf.String(), pt.out)
logBuf.Reset()
}
}