Skip to content

Commit 28755ae

Browse files
authored
Merge pull request #130 from thockin/nil-stringer
funcr: Handle nil Stringer, Marshaler, error
2 parents ec7c16c + 945d619 commit 28755ae

3 files changed

Lines changed: 154 additions & 9 deletions

File tree

benchmark/benchmark_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,45 @@ func doWithCallDepth(b *testing.B, log logr.Logger) {
102102
}
103103
}
104104

105+
type Tstringer struct{ s string }
106+
107+
func (t Tstringer) String() string {
108+
return t.s
109+
}
110+
111+
//go:noinline
112+
func doStringerValue(b *testing.B, log logr.Logger) {
113+
for i := 0; i < b.N; i++ {
114+
log.Info("this is", "a", Tstringer{"stringer"})
115+
}
116+
}
117+
118+
type Terror struct{ s string }
119+
120+
func (t Terror) Error() string {
121+
return t.s
122+
}
123+
124+
//go:noinline
125+
func doErrorValue(b *testing.B, log logr.Logger) {
126+
for i := 0; i < b.N; i++ {
127+
log.Info("this is", "an", Terror{"error"})
128+
}
129+
}
130+
131+
type Tmarshaler struct{ s string }
132+
133+
func (t Tmarshaler) MarshalLog() interface{} {
134+
return t.s
135+
}
136+
137+
//go:noinline
138+
func doMarshalerValue(b *testing.B, log logr.Logger) {
139+
for i := 0; i < b.N; i++ {
140+
log.Info("this is", "a", Tmarshaler{"marshaler"})
141+
}
142+
}
143+
105144
func BenchmarkDiscardLogInfoOneArg(b *testing.B) {
106145
var log logr.Logger = logr.Discard()
107146
doInfoOneArg(b, log)
@@ -219,3 +258,18 @@ func BenchmarkFuncrWithCallDepth(b *testing.B) {
219258
var log logr.Logger = funcr.New(noopKV, funcr.Options{})
220259
doWithCallDepth(b, log)
221260
}
261+
262+
func BenchmarkFuncrJSONLogInfoStringerValue(b *testing.B) {
263+
var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{})
264+
doStringerValue(b, log)
265+
}
266+
267+
func BenchmarkFuncrJSONLogInfoErrorValue(b *testing.B) {
268+
var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{})
269+
doErrorValue(b, log)
270+
}
271+
272+
func BenchmarkFuncrJSONLogInfoMarshalerValue(b *testing.B) {
273+
var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{})
274+
doMarshalerValue(b, log)
275+
}

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)