Repository: zeromicro/go-zero (go)

Issue:
use logx.Field question
`logx.Field` 试用发现一个`err`不会自动调用`err.Error()`或者显示`err`信息,这有可能会出现一个问题是忘记写`err.Error()`的时候就会丢失错误信息

示例代码:
```
err := errors.New("xxx")
	logx.Infow("xx",
		logx.Field("key", "value"),
		logx.Field("err", err.Error()),
		logx.Field("err1", err),
	)

```


![image](https://user-images.githubusercontent.com/30316411/166941150-504819aa-bcb2-498a-bbd3-1a3f66a8f397.png)


Pull request:
fix time, duration, slice types on logx.Field
fix #1866

Patch:
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}
 	}


Test patch:
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)
+		}
+	}
+}