Skip to content

Commit

Permalink
field/error: Handle panic in Error() (#867)
Browse files Browse the repository at this point in the history
We shouldn't panic if the `Error()` method of an `error` panics.

```
type T struct{ msg string }

func (t *T) Error() string { return t.msg }

// The following panics.
var n *T = nil
log.Info("panic", zap.Error(n))
```

Co-authored-by: Abhinav Gupta <mail@abhinavg.net>
  • Loading branch information
FMLS and abhinav committed Nov 24, 2020
1 parent 495658f commit 4b2b07c
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 2 deletions.
19 changes: 18 additions & 1 deletion zapcore/error.go
Expand Up @@ -22,6 +22,7 @@ package zapcore

import (
"fmt"
"reflect"
"sync"
)

Expand All @@ -42,7 +43,23 @@ import (
// ...
// ],
// }
func encodeError(key string, err error, enc ObjectEncoder) error {
func encodeError(key string, err error, enc ObjectEncoder) (retErr error) {
// Try to capture panics (from nil references or otherwise) when calling
// the Error() method
defer func() {
if rerr := recover(); rerr != nil {
// If it's a nil pointer, just say "<nil>". The likeliest causes are a
// error that fails to guard against nil or a nil pointer for a
// value receiver, and in either case, "<nil>" is a nice result.
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
enc.AddString(key, "<nil>")
return
}

retErr = fmt.Errorf("PANIC=%v", rerr)
}
}()

basic := err.Error()
enc.AddString(key, basic)

Expand Down
2 changes: 1 addition & 1 deletion zapcore/field.go
Expand Up @@ -167,7 +167,7 @@ func (f Field) AddTo(enc ObjectEncoder) {
case StringerType:
err = encodeStringer(f.Key, f.Interface, enc)
case ErrorType:
encodeError(f.Key, f.Interface.(error), enc)
err = encodeError(f.Key, f.Interface.(error), enc)
case SkipType:
break
default:
Expand Down
15 changes: 15 additions & 0 deletions zapcore/field_test.go
Expand Up @@ -80,6 +80,19 @@ func (o *obj) String() string {
return "obj"
}

type errObj struct {
kind int
errMsg string
}

func (eobj *errObj) Error() string {
if eobj.kind == 1 {
panic("panic in Error() method")
} else {
return eobj.errMsg
}
}

func TestUnknownFieldType(t *testing.T) {
unknown := Field{Key: "k", String: "foo"}
assert.Equal(t, UnknownType, unknown.Type, "Expected zero value of FieldType to be UnknownType.")
Expand All @@ -102,6 +115,7 @@ func TestFieldAddingError(t *testing.T) {
{t: StringerType, iface: &obj{1}, want: empty, err: "PANIC=panic with string"},
{t: StringerType, iface: &obj{2}, want: empty, err: "PANIC=panic with error"},
{t: StringerType, iface: &obj{3}, want: empty, err: "PANIC=<nil>"},
{t: ErrorType, iface: &errObj{kind: 1}, want: empty, err: "PANIC=panic in Error() method"},
}
for _, tt := range tests {
f := Field{Key: "k", Interface: tt.iface, Type: tt.t}
Expand Down Expand Up @@ -150,6 +164,7 @@ func TestFields(t *testing.T) {
{t: SkipType, want: interface{}(nil)},
{t: StringerType, iface: (*url.URL)(nil), want: "<nil>"},
{t: StringerType, iface: (*users)(nil), want: "<nil>"},
{t: ErrorType, iface: (*errObj)(nil), want: "<nil>"},
}

for _, tt := range tests {
Expand Down

0 comments on commit 4b2b07c

Please sign in to comment.