diff --git a/core/logx/logs.go b/core/logx/logs.go index 23ec33f2..b663c7df 100644 --- a/core/logx/logs.go +++ b/core/logx/logs.go @@ -113,8 +113,36 @@ func Errorw(msg string, fields ...LogField) { // Field returns a LogField for the given key and value. func Field(key string, value interface{}) LogField { 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: 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: return LogField{Key: key, Value: val} } diff --git a/core/logx/logs_test.go b/core/logx/logs_test.go index 36cfef2d..a0f20710 100644 --- a/core/logx/logs_test.go +++ b/core/logx/logs_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "log" "os" + "reflect" "runtime" "strings" "sync" @@ -92,6 +93,86 @@ func (mw *mockWriter) String() 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) { w := new(mockWriter) old := writer.Swap(w) @@ -675,3 +756,18 @@ type ValStringer struct { func (v ValStringer) String() string { 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) + } + } +}