Recover from panics caused by dereferencing a nil pointer to a value receiver implementation of the fmt.Stringer or error interfaces.
Chris Hines
8 years ago
3 | 3 |
"encoding/json"
|
4 | 4 |
"fmt"
|
5 | 5 |
"io"
|
|
6 |
"reflect"
|
6 | 7 |
)
|
7 | 8 |
|
8 | 9 |
type jsonLogger struct {
|
|
29 | 30 |
return json.NewEncoder(l.Writer).Encode(m)
|
30 | 31 |
}
|
31 | 32 |
|
32 | |
func merge(dst map[string]interface{}, k, v interface{}) map[string]interface{} {
|
|
33 |
func merge(dst map[string]interface{}, k, v interface{}) {
|
33 | 34 |
var key string
|
34 | 35 |
switch x := k.(type) {
|
35 | 36 |
case string:
|
36 | 37 |
key = x
|
37 | 38 |
case fmt.Stringer:
|
38 | |
key = x.String()
|
|
39 |
key = safeString(x)
|
39 | 40 |
default:
|
40 | |
key = fmt.Sprintf("%v", x)
|
|
41 |
key = fmt.Sprint(x)
|
41 | 42 |
}
|
42 | 43 |
if x, ok := v.(error); ok {
|
43 | |
v = x.Error()
|
|
44 |
v = safeError(x)
|
44 | 45 |
}
|
45 | 46 |
dst[key] = v
|
46 | |
return dst
|
47 | 47 |
}
|
|
48 |
|
|
49 |
func safeString(str fmt.Stringer) (s string) {
|
|
50 |
defer func() {
|
|
51 |
if panicVal := recover(); panicVal != nil {
|
|
52 |
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
|
|
53 |
s = "NULL"
|
|
54 |
} else {
|
|
55 |
panic(panicVal)
|
|
56 |
}
|
|
57 |
}
|
|
58 |
}()
|
|
59 |
s = str.String()
|
|
60 |
return
|
|
61 |
}
|
|
62 |
|
|
63 |
func safeError(err error) (s interface{}) {
|
|
64 |
defer func() {
|
|
65 |
if panicVal := recover(); panicVal != nil {
|
|
66 |
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
|
|
67 |
s = nil
|
|
68 |
} else {
|
|
69 |
panic(panicVal)
|
|
70 |
}
|
|
71 |
}
|
|
72 |
}()
|
|
73 |
s = err.Error()
|
|
74 |
return
|
|
75 |
}
|
32 | 32 |
}
|
33 | 33 |
}
|
34 | 34 |
|
|
35 |
func TestJSONLoggerNilStringerKey(t *testing.T) {
|
|
36 |
t.Parallel()
|
|
37 |
|
|
38 |
buf := &bytes.Buffer{}
|
|
39 |
logger := log.NewJSONLogger(buf)
|
|
40 |
if err := logger.Log((*stringer)(nil), "v"); err != nil {
|
|
41 |
t.Fatal(err)
|
|
42 |
}
|
|
43 |
if want, have := `{"NULL":"v"}`+"\n", buf.String(); want != have {
|
|
44 |
t.Errorf("want %#v, have %#v", want, have)
|
|
45 |
}
|
|
46 |
}
|
|
47 |
|
|
48 |
func TestJSONLoggerNilErrorValue(t *testing.T) {
|
|
49 |
t.Parallel()
|
|
50 |
|
|
51 |
buf := &bytes.Buffer{}
|
|
52 |
logger := log.NewJSONLogger(buf)
|
|
53 |
if err := logger.Log("err", (*stringError)(nil)); err != nil {
|
|
54 |
t.Fatal(err)
|
|
55 |
}
|
|
56 |
if want, have := `{"err":null}`+"\n", buf.String(); want != have {
|
|
57 |
t.Errorf("want %#v, have %#v", want, have)
|
|
58 |
}
|
|
59 |
}
|
|
60 |
|
|
61 |
type stringer string
|
|
62 |
|
|
63 |
func (s stringer) String() string {
|
|
64 |
return string(s)
|
|
65 |
}
|
|
66 |
|
|
67 |
type stringError string
|
|
68 |
|
|
69 |
func (s stringError) Error() string {
|
|
70 |
return string(s)
|
|
71 |
}
|
|
72 |
|
35 | 73 |
func BenchmarkJSONLoggerSimple(b *testing.B) {
|
36 | 74 |
benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), baseMessage)
|
37 | 75 |
}
|