Skip to content

Commit 945d619

Browse files
committed
funcr: Handle panic in Stringer, Marshaler, error
Produce a maybe-useful error rather than panic when we are in the middle of logging a value (similar to fmt.Printf).
1 parent af7b868 commit 945d619

File tree

2 files changed

+100
-9
lines changed

2 files changed

+100
-9
lines changed

funcr/funcr.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,15 +351,15 @@ func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) s
351351
if v, ok := value.(logr.Marshaler); ok {
352352
// Replace the value with what the type wants to get logged.
353353
// That then gets handled below via reflection.
354-
value = v.MarshalLog()
354+
value = invokeMarshaler(v)
355355
}
356356

357357
// Handle types that want to format themselves.
358358
switch v := value.(type) {
359359
case fmt.Stringer:
360-
value = v.String()
360+
value = invokeStringer(v)
361361
case error:
362-
value = v.Error()
362+
value = invokeError(v)
363363
}
364364

365365
// Handling the most common types without reflect is a small perf win.
@@ -597,6 +597,33 @@ func isEmpty(v reflect.Value) bool {
597597
return false
598598
}
599599

600+
func invokeMarshaler(m logr.Marshaler) (ret interface{}) {
601+
defer func() {
602+
if r := recover(); r != nil {
603+
ret = fmt.Sprintf("<panic: %s>", r)
604+
}
605+
}()
606+
return m.MarshalLog()
607+
}
608+
609+
func invokeStringer(s fmt.Stringer) (ret string) {
610+
defer func() {
611+
if r := recover(); r != nil {
612+
ret = fmt.Sprintf("<panic: %s>", r)
613+
}
614+
}()
615+
return s.String()
616+
}
617+
618+
func invokeError(e error) (ret string) {
619+
defer func() {
620+
if r := recover(); r != nil {
621+
ret = fmt.Sprintf("<panic: %s>", r)
622+
}
623+
}()
624+
return e.Error()
625+
}
626+
600627
// Caller represents the original call site for a log line, after considering
601628
// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and
602629
// Line fields will always be provided, while the Func field is optional.

funcr/funcr_test.go

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (p pointErr) MarshalText() ([]byte, error) {
5252
}
5353

5454
// Logging this should result in the MarshalLog() value.
55-
type Tmarshaler string
55+
type Tmarshaler struct{ val string }
5656

5757
func (t Tmarshaler) MarshalLog() interface{} {
5858
return struct{ Inner string }{"I am a logr.Marshaler"}
@@ -66,8 +66,15 @@ func (t Tmarshaler) Error() string {
6666
return "Error(): you should not see this"
6767
}
6868

69+
// Logging this should result in a panic.
70+
type Tmarshalerpanic struct{ val string }
71+
72+
func (t Tmarshalerpanic) MarshalLog() interface{} {
73+
panic("Tmarshalerpanic")
74+
}
75+
6976
// Logging this should result in the String() value.
70-
type Tstringer string
77+
type Tstringer struct{ val string }
7178

7279
func (t Tstringer) String() string {
7380
return "I am a fmt.Stringer"
@@ -77,6 +84,27 @@ func (t Tstringer) Error() string {
7784
return "Error(): you should not see this"
7885
}
7986

87+
// Logging this should result in a panic.
88+
type Tstringerpanic struct{ val string }
89+
90+
func (t Tstringerpanic) String() string {
91+
panic("Tstringerpanic")
92+
}
93+
94+
// Logging this should result in the Error() value.
95+
type Terror struct{ val string }
96+
97+
func (t Terror) Error() string {
98+
return "I am an error"
99+
}
100+
101+
// Logging this should result in a panic.
102+
type Terrorpanic struct{ val string }
103+
104+
func (t Terrorpanic) Error() string {
105+
panic("Terrorpanic")
106+
}
107+
80108
type TjsontagsString struct {
81109
String1 string `json:"string1"` // renamed
82110
String2 string `json:"-"` // ignored
@@ -351,16 +379,52 @@ func TestPretty(t *testing.T) {
351379
},
352380
},
353381
{
354-
val: Tmarshaler("foobar"),
382+
val: Tmarshaler{"foobar"},
355383
exp: `{"Inner":"I am a logr.Marshaler"}`,
356384
},
357385
{
358-
val: Tstringer("foobar"),
386+
val: &Tmarshaler{"foobar"},
387+
exp: `{"Inner":"I am a logr.Marshaler"}`,
388+
},
389+
{
390+
val: (*Tmarshaler)(nil),
391+
exp: `"<panic: value method github.com/go-logr/logr/funcr.Tmarshaler.MarshalLog called using nil *Tmarshaler pointer>"`,
392+
},
393+
{
394+
val: Tmarshalerpanic{"foobar"},
395+
exp: `"<panic: Tmarshalerpanic>"`,
396+
},
397+
{
398+
val: Tstringer{"foobar"},
399+
exp: `"I am a fmt.Stringer"`,
400+
},
401+
{
402+
val: &Tstringer{"foobar"},
359403
exp: `"I am a fmt.Stringer"`,
360404
},
361405
{
362-
val: fmt.Errorf("error"),
363-
exp: `"error"`,
406+
val: (*Tstringer)(nil),
407+
exp: `"<panic: value method github.com/go-logr/logr/funcr.Tstringer.String called using nil *Tstringer pointer>"`,
408+
},
409+
{
410+
val: Tstringerpanic{"foobar"},
411+
exp: `"<panic: Tstringerpanic>"`,
412+
},
413+
{
414+
val: Terror{"foobar"},
415+
exp: `"I am an error"`,
416+
},
417+
{
418+
val: &Terror{"foobar"},
419+
exp: `"I am an error"`,
420+
},
421+
{
422+
val: (*Terror)(nil),
423+
exp: `"<panic: value method github.com/go-logr/logr/funcr.Terror.Error called using nil *Terror pointer>"`,
424+
},
425+
{
426+
val: Terrorpanic{"foobar"},
427+
exp: `"<panic: Terrorpanic>"`,
364428
},
365429
{
366430
val: TjsontagsString{

0 commit comments

Comments
 (0)