Skip to content

Commit df10f47

Browse files
pohlythockin
authored andcommitted
handle panics in logr.Marshaler.MarshalLog
The function might panic. The conclusion in go-logr/logr#130 was that the logger should record that. zapr only needs to do that when it calls MarshalLog. Strings and errors are handled by zap. For the sake of simplicity no attempt is made to detect the reason for the panic. In case of a panic, the key is replaced by "<key>Error" and the value with "PANIC=<panic reason>". This is consistent with how zap handles panics.
1 parent 517e434 commit df10f47

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

zapr.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ limitations under the License.
5454
package zapr
5555

5656
import (
57+
"fmt"
58+
5759
"github.com/go-logr/logr"
5860
"go.uber.org/zap"
5961
"go.uber.org/zap/zapcore"
@@ -170,11 +172,21 @@ func zapIt(field string, val interface{}) zap.Field {
170172
// Handle types that implement logr.Marshaler: log the replacement
171173
// object instead of the original one.
172174
if marshaler, ok := val.(logr.Marshaler); ok {
173-
val = marshaler.MarshalLog()
175+
field, val = invokeMarshaler(field, marshaler)
174176
}
175177
return zap.Any(field, val)
176178
}
177179

180+
func invokeMarshaler(field string, m logr.Marshaler) (f string, ret interface{}) {
181+
defer func() {
182+
if r := recover(); r != nil {
183+
ret = fmt.Sprintf("PANIC=%s", r)
184+
f = field + "Error"
185+
}
186+
}()
187+
return field, m.MarshalLog()
188+
}
189+
178190
func (zl *zapLogger) Init(ri logr.RuntimeInfo) {
179191
zl.l = zl.l.WithOptions(zap.AddCallerSkip(ri.CallDepth))
180192
}

zapr_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,40 @@ type discard struct{}
4747

4848
func (d discard) Write(p []byte) (n int, err error) { return n, nil }
4949

50+
type marshaler struct {
51+
msg string
52+
}
53+
54+
func (m *marshaler) String() string {
55+
return m.msg
56+
}
57+
58+
func (m *marshaler) MarshalLog() interface{} {
59+
return "msg=" + m.msg
60+
}
61+
62+
var _ fmt.Stringer = &marshaler{}
63+
var _ logr.Marshaler = &marshaler{}
64+
65+
type stringer struct {
66+
msg string
67+
}
68+
69+
func (s *stringer) String() string {
70+
return s.msg
71+
}
72+
73+
var _ fmt.Stringer = &stringer{}
74+
75+
type stringerPanic struct {
76+
}
77+
78+
func (s *stringerPanic) String() string {
79+
panic("fake panic")
80+
}
81+
82+
var _ fmt.Stringer = &stringerPanic{}
83+
5084
func newZapLogger(lvl zapcore.Level, w zapcore.WriteSyncer) *zap.Logger {
5185
if w == nil {
5286
w = zapcore.AddSync(discard{})
@@ -164,6 +198,33 @@ func TestInfo(t *testing.T) {
164198
`,
165199
keysValues: []interface{}{"duration", time.Duration(5 * time.Second)},
166200
},
201+
{
202+
msg: "valid marshaler",
203+
format: `{"ts":%f,"caller":"zapr/zapr_test.go:%d","msg":"valid marshaler","v":0,"obj":"msg=hello"}
204+
`,
205+
keysValues: []interface{}{"obj", &marshaler{msg: "hello"}},
206+
},
207+
{
208+
msg: "nil marshaler",
209+
// Handled by our code: it just formats the error.
210+
format: `{"ts":%f,"caller":"zapr/zapr_test.go:%d","msg":"nil marshaler","v":0,"objError":"PANIC=runtime error: invalid memory address or nil pointer dereference"}
211+
`,
212+
keysValues: []interface{}{"obj", (*marshaler)(nil)},
213+
},
214+
{
215+
msg: "nil stringer",
216+
// Handled by zap: it detects a nil pointer.
217+
format: `{"ts":%f,"caller":"zapr/zapr_test.go:%d","msg":"nil stringer","v":0,"obj":"<nil>"}
218+
`,
219+
keysValues: []interface{}{"obj", (*stringer)(nil)},
220+
},
221+
{
222+
msg: "panic stringer",
223+
// Handled by zap: it prints the panic, but using a different key and format than funcr.
224+
format: `{"ts":%f,"caller":"zapr/zapr_test.go:%d","msg":"panic stringer","v":0,"objError":"PANIC=fake panic"}
225+
`,
226+
keysValues: []interface{}{"obj", &stringerPanic{}},
227+
},
167228
}
168229

169230
test := func(t *testing.T, logNumeric *string, enablePanics *bool, allowZapFields *bool, data testCase) {

0 commit comments

Comments
 (0)