fix time, duration, slice types on logx.Field (#1868)

* chore: refine tests

* fix #1866
master
Kevin Wan 3 years ago committed by GitHub
parent aa5118c2aa
commit a36d58aac9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -113,8 +113,36 @@ func Errorw(msg string, fields ...LogField) {
// Field returns a LogField for the given key and value. // Field returns a LogField for the given key and value.
func Field(key string, value interface{}) LogField { func Field(key string, value interface{}) LogField {
switch val := value.(type) { switch val := value.(type) {
case error:
return LogField{Key: key, Value: val.Error()}
case []error:
var errs []string
for _, err := range val {
errs = append(errs, err.Error())
}
return LogField{Key: key, Value: errs}
case time.Duration: case time.Duration:
return LogField{Key: key, Value: fmt.Sprint(val)} return LogField{Key: key, Value: fmt.Sprint(val)}
case []time.Duration:
var durs []string
for _, dur := range val {
durs = append(durs, fmt.Sprint(dur))
}
return LogField{Key: key, Value: durs}
case []time.Time:
var times []string
for _, t := range val {
times = append(times, fmt.Sprint(t))
}
return LogField{Key: key, Value: times}
case fmt.Stringer:
return LogField{Key: key, Value: val.String()}
case []fmt.Stringer:
var strs []string
for _, str := range val {
strs = append(strs, str.String())
}
return LogField{Key: key, Value: strs}
default: default:
return LogField{Key: key, Value: val} return LogField{Key: key, Value: val}
} }

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"reflect"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@ -92,6 +93,86 @@ func (mw *mockWriter) String() string {
return mw.builder.String() return mw.builder.String()
} }
func TestField(t *testing.T) {
tests := []struct {
name string
f LogField
want map[string]interface{}
}{
{
name: "error",
f: Field("foo", errors.New("bar")),
want: map[string]interface{}{
"foo": "bar",
},
},
{
name: "errors",
f: Field("foo", []error{errors.New("bar"), errors.New("baz")}),
want: map[string]interface{}{
"foo": []interface{}{"bar", "baz"},
},
},
{
name: "strings",
f: Field("foo", []string{"bar", "baz"}),
want: map[string]interface{}{
"foo": []interface{}{"bar", "baz"},
},
},
{
name: "duration",
f: Field("foo", time.Second),
want: map[string]interface{}{
"foo": "1s",
},
},
{
name: "durations",
f: Field("foo", []time.Duration{time.Second, 2 * time.Second}),
want: map[string]interface{}{
"foo": []interface{}{"1s", "2s"},
},
},
{
name: "times",
f: Field("foo", []time.Time{
time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC),
time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC),
}),
want: map[string]interface{}{
"foo": []interface{}{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
},
},
{
name: "stringer",
f: Field("foo", ValStringer{val: "bar"}),
want: map[string]interface{}{
"foo": "bar",
},
},
{
name: "stringers",
f: Field("foo", []fmt.Stringer{ValStringer{val: "bar"}, ValStringer{val: "baz"}}),
want: map[string]interface{}{
"foo": []interface{}{"bar", "baz"},
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
w := new(mockWriter)
old := writer.Swap(w)
defer writer.Store(old)
Infow("foo", test.f)
validateFields(t, w.String(), test.want)
})
}
}
func TestFileLineFileMode(t *testing.T) { func TestFileLineFileMode(t *testing.T) {
w := new(mockWriter) w := new(mockWriter)
old := writer.Swap(w) old := writer.Swap(w)
@ -675,3 +756,18 @@ type ValStringer struct {
func (v ValStringer) String() string { func (v ValStringer) String() string {
return v.val return v.val
} }
func validateFields(t *testing.T, content string, fields map[string]interface{}) {
var m map[string]interface{}
if err := json.Unmarshal([]byte(content), &m); err != nil {
t.Error(err)
}
for k, v := range fields {
if reflect.TypeOf(v).Kind() == reflect.Slice {
assert.EqualValues(t, v, m[k])
} else {
assert.Equal(t, v, m[k], content)
}
}
}

Loading…
Cancel
Save