Skip to content

Commit 4c2383e

Browse files
committed
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. As zapr cannot replace the key, it uses the same replacement value as funcr because that stands out a bit more compared to the value used by zap.
1 parent 517e434 commit 4c2383e

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

zapr.go

+12-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,20 @@ 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+
val = invokeMarshaler(marshaler)
174176
}
175177
return zap.Any(field, val)
176178
}
177179

180+
func invokeMarshaler(m logr.Marshaler) (ret interface{}) {
181+
defer func() {
182+
if r := recover(); r != nil {
183+
ret = fmt.Sprintf("<panic: %s>", r)
184+
}
185+
}()
186+
return m.MarshalLog()
187+
}
188+
178189
func (zl *zapLogger) Init(ri logr.RuntimeInfo) {
179190
zl.l = zl.l.WithOptions(zap.AddCallerSkip(ri.CallDepth))
180191
}

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,"obj":"<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)