Skip to content

Commit 708ae61

Browse files
committed
handle panics in MarshalLog, Error, String
The previous fix only covered fmt.Stringer.String in klog, but not klogr. error.Error and logr.Marshaler.MarshalLog have the same problem. The replacement string now captures the error, which makes it consistent with go-logr/logr#130. Two different corner cases may be handled differently: - panic for a nil object - panic for a valid object Only zapr v1.2.3 handles the panic in MarshalLog.
1 parent a58a994 commit 708ae61

File tree

7 files changed

+122
-7
lines changed

7 files changed

+122
-7
lines changed

examples/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.13
44

55
require (
66
github.com/go-logr/logr v1.2.2
7-
github.com/go-logr/zapr v1.2.2
7+
github.com/go-logr/zapr v1.2.3
88
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
99
go.uber.org/zap v1.19.0
1010
k8s.io/klog/v2 v2.30.0

examples/go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
88
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
99
github.com/go-logr/zapr v1.2.2 h1:5YNlIL6oZLydaV4dOFjL8YpgXF/tPeTbnpatnu3cq6o=
1010
github.com/go-logr/zapr v1.2.2/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
11+
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
12+
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
1113
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
1214
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
1315
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

internal/serialize/keyvalues.go

+18-4
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
126126
// (https://github.com/kubernetes/kubernetes/pull/106594#issuecomment-975526235).
127127
switch v := v.(type) {
128128
case fmt.Stringer:
129-
writeStringValue(b, true, stringerToString(v))
129+
writeStringValue(b, true, StringerToString(v))
130130
case string:
131131
writeStringValue(b, true, v)
132132
case error:
133-
writeStringValue(b, true, v.Error())
133+
writeStringValue(b, true, ErrorToString(v))
134134
case []byte:
135135
// In https://github.com/kubernetes/klog/pull/237 it was decided
136136
// to format byte slices with "%+q". The advantages of that are:
@@ -151,16 +151,30 @@ func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
151151
}
152152
}
153153

154-
func stringerToString(s fmt.Stringer) (ret string) {
154+
// StringerToString converts a Stringer to a string,
155+
// handling panics if they occur.
156+
func StringerToString(s fmt.Stringer) (ret string) {
155157
defer func() {
156158
if err := recover(); err != nil {
157-
ret = "nil"
159+
ret = fmt.Sprintf("<panic: %s>", err)
158160
}
159161
}()
160162
ret = s.String()
161163
return
162164
}
163165

166+
// ErrorToString converts an error to a string,
167+
// handling panics if they occur.
168+
func ErrorToString(err error) (ret string) {
169+
defer func() {
170+
if err := recover(); err != nil {
171+
ret = fmt.Sprintf("<panic: %s>", err)
172+
}
173+
}()
174+
ret = err.Error()
175+
return
176+
}
177+
164178
func writeStringValue(b *bytes.Buffer, quote bool, v string) {
165179
data := []byte(v)
166180
index := bytes.IndexByte(data, '\n')

internal/serialize/keyvalues_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ No whitespace.`,
137137
},
138138
{
139139
keysValues: []interface{}{"point-1", point{100, 200}, "point-2", emptyPoint},
140-
want: " point-1=\"x=100, y=200\" point-2=\"nil\"",
140+
want: " point-1=\"x=100, y=200\" point-2=\"<panic: value method k8s.io/klog/v2/internal/serialize_test.point.String called using nil *point pointer>\"",
141141
},
142142
}
143143

klogr/klogr.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func (l klogger) Error(err error, msg string, kvList ...interface{}) {
140140
msgStr := flatten("msg", msg)
141141
var loggableErr interface{}
142142
if err != nil {
143-
loggableErr = err.Error()
143+
loggableErr = serialize.ErrorToString(err)
144144
}
145145
switch l.format {
146146
case FormatSerialize:

test/output.go

+74
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,42 @@ I output.go:<LINE>] "test" firstKey=1 secondKey=3
286286
text: "test",
287287
err: errors.New("whoops"),
288288
expectedOutput: `E output.go:<LINE>] "test" err="whoops"
289+
`,
290+
},
291+
"Error() for nil": {
292+
text: "error nil",
293+
err: (*customErrorJSON)(nil),
294+
expectedOutput: `E output.go:<LINE>] "error nil" err="<panic: runtime error: invalid memory address or nil pointer dereference>"
295+
`,
296+
},
297+
"String() for nil": {
298+
text: "stringer nil",
299+
values: []interface{}{"stringer", (*stringer)(nil)},
300+
expectedOutput: `I output.go:<LINE>] "stringer nil" stringer="<panic: runtime error: invalid memory address or nil pointer dereference>"
301+
`,
302+
},
303+
"MarshalLog() for nil": {
304+
text: "marshaler nil",
305+
values: []interface{}{"obj", (*klog.ObjectRef)(nil)},
306+
expectedOutput: `I output.go:<LINE>] "marshaler nil" obj="<panic: value method k8s.io/klog/v2.ObjectRef.String called using nil *ObjectRef pointer>"
307+
`,
308+
},
309+
"Error() that panics": {
310+
text: "error panic",
311+
err: faultyError{},
312+
expectedOutput: `E output.go:<LINE>] "error panic" err="<panic: fake Error panic>"
313+
`,
314+
},
315+
"String() that panics": {
316+
text: "stringer panic",
317+
values: []interface{}{"stringer", faultyStringer{}},
318+
expectedOutput: `I output.go:<LINE>] "stringer panic" stringer="<panic: fake String panic>"
319+
`,
320+
},
321+
"MarshalLog() that panics": {
322+
text: "marshaler panic",
323+
values: []interface{}{"obj", faultyMarshaler{}},
324+
expectedOutput: `I output.go:<LINE>] "marshaler panic" obj={}
289325
`,
290326
},
291327
}
@@ -699,3 +735,41 @@ func (e *customErrorJSON) Error() string {
699735
func (e *customErrorJSON) MarshalJSON() ([]byte, error) {
700736
return json.Marshal(strings.ToUpper(e.s))
701737
}
738+
739+
type stringer struct {
740+
s string
741+
}
742+
743+
// String crashes when called for nil.
744+
func (s *stringer) String() string {
745+
return s.s
746+
}
747+
748+
var _ fmt.Stringer = &stringer{}
749+
750+
type faultyStringer struct{}
751+
752+
// String always panics.
753+
func (f faultyStringer) String() string {
754+
panic("fake String panic")
755+
}
756+
757+
var _ fmt.Stringer = faultyStringer{}
758+
759+
type faultyMarshaler struct{}
760+
761+
// MarshalLog always panics.
762+
func (f faultyMarshaler) MarshalLog() interface{} {
763+
panic("fake MarshalLog panic")
764+
}
765+
766+
var _ logr.Marshaler = faultyMarshaler{}
767+
768+
type faultyError struct{}
769+
770+
// Error always panics.
771+
func (f faultyError) Error() string {
772+
panic("fake Error panic")
773+
}
774+
775+
var _ error = faultyError{}

test/zapr.go

+25
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,31 @@ I output.go:<LINE>] "odd WithValues" keyWithoutValue="(MISSING)"
114114
`: `{"caller":"test/output.go:<WITH-VALUES>","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"basekey2"}
115115
{"caller":"test/output.go:<LINE>","msg":"odd number of arguments passed as key-value pairs for logging","basekey1":"basevar1","ignored key":"akey2"}
116116
{"caller":"test/output.go:<LINE>","msg":"both odd","basekey1":"basevar1","v":0,"akey":"avalue"}
117+
`,
118+
119+
`I output.go:<LINE>] "marshaler nil" obj="<panic: value method k8s.io/klog/v2.ObjectRef.String called using nil *ObjectRef pointer>"
120+
`: `{"caller":"test/output.go:<LINE>","msg":"marshaler nil","v":0,"objError":"PANIC=value method k8s.io/klog/v2.ObjectRef.MarshalLog called using nil *ObjectRef pointer"}
121+
`,
122+
123+
// zap replaces a panic for a nil object with <nil>.
124+
`E output.go:<LINE>] "error nil" err="<panic: runtime error: invalid memory address or nil pointer dereference>"
125+
`: `{"caller":"test/output.go:<LINE>","msg":"error nil","err":"<nil>"}
126+
`,
127+
128+
`I output.go:<LINE>] "stringer nil" stringer="<panic: runtime error: invalid memory address or nil pointer dereference>"
129+
`: `{"caller":"test/output.go:<LINE>","msg":"stringer nil","v":0,"stringer":"<nil>"}
130+
`,
131+
132+
`I output.go:<LINE>] "stringer panic" stringer="<panic: fake String panic>"
133+
`: `{"caller":"test/output.go:<LINE>","msg":"stringer panic","v":0,"stringerError":"PANIC=fake String panic"}
134+
`,
135+
136+
`E output.go:<LINE>] "error panic" err="<panic: fake Error panic>"
137+
`: `{"caller":"test/output.go:<LINE>","msg":"error panic","errError":"PANIC=fake Error panic"}
138+
`,
139+
140+
`I output.go:<LINE>] "marshaler panic" obj={}
141+
`: `{"caller":"test/output.go:<LINE>","msg":"marshaler panic","v":0,"objError":"PANIC=fake MarshalLog panic"}
117142
`,
118143

119144
// klog.Info

0 commit comments

Comments
 (0)