diff --git a/core/logx/logs.go b/core/logx/logs.go index 780093eb..57649e65 100644 --- a/core/logx/logs.go +++ b/core/logx/logs.go @@ -6,6 +6,7 @@ import ( "log" "os" "path" + "reflect" "runtime/debug" "sync" "sync/atomic" @@ -153,11 +154,11 @@ func Errorw(msg string, fields ...LogField) { func Field(key string, value any) LogField { switch val := value.(type) { case error: - return LogField{Key: key, Value: val.Error()} + return LogField{Key: key, Value: encodeError(val)} case []error: var errs []string for _, err := range val { - errs = append(errs, err.Error()) + errs = append(errs, encodeError(err)) } return LogField{Key: key, Value: errs} case time.Duration: @@ -175,11 +176,11 @@ func Field(key string, value any) LogField { } return LogField{Key: key, Value: times} case fmt.Stringer: - return LogField{Key: key, Value: val.String()} + return LogField{Key: key, Value: encodeStringer(val)} case []fmt.Stringer: var strs []string for _, str := range val { - strs = append(strs, str.String()) + strs = append(strs, encodeStringer(str)) } return LogField{Key: key, Value: strs} default: @@ -414,6 +415,32 @@ func createOutput(path string) (io.WriteCloser, error) { return NewLogger(path, rule, options.gzipEnabled) } +func encodeError(err error) (ret string) { + return encodeWithRecover(err, func() string { + return err.Error() + }) +} + +func encodeStringer(v fmt.Stringer) (ret string) { + return encodeWithRecover(v, func() string { + return v.String() + }) +} + +func encodeWithRecover(arg any, fn func() string) (ret string) { + defer func() { + if err := recover(); err != nil { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Ptr && v.IsNil() { + ret = nilAngleString + } else { + panic(err) + } + } + }() + + return fn() +} + func getWriter() Writer { w := writer.Load() if w == nil { diff --git a/core/logx/logs_test.go b/core/logx/logs_test.go index 3780bc2f..7622453b 100644 --- a/core/logx/logs_test.go +++ b/core/logx/logs_test.go @@ -348,6 +348,25 @@ func TestStructedLogInfow(t *testing.T) { }) } +func TestStructedLogInfowNil(t *testing.T) { + w := new(mockWriter) + old := writer.Swap(w) + defer writer.Store(old) + + assert.Panics(t, func() { + var ps panicStringer + Infow("test", Field("bb", ps)) + }) + assert.NotPanics(t, func() { + var s *string + Infow("test", Field("bb", s)) + var d *nilStringer + Infow("test", Field("bb", d)) + var e *nilError + Errorw("test", Field("bb", e)) + }) +} + func TestStructedLogInfoConsoleAny(t *testing.T) { w := new(mockWriter) old := writer.Swap(w) @@ -859,3 +878,25 @@ func validateFields(t *testing.T, content string, fields map[string]any) { } } } + +type nilError struct { + Name string +} + +func (e *nilError) Error() string { + return e.Name +} + +type nilStringer struct { + Name string +} + +func (s *nilStringer) String() string { + return s.Name +} + +type panicStringer struct{} + +func (s panicStringer) String() string { + panic("panic") +} diff --git a/core/logx/vars.go b/core/logx/vars.go index 90ef3628..a2788d08 100644 --- a/core/logx/vars.go +++ b/core/logx/vars.go @@ -48,6 +48,7 @@ const ( levelDebug = "debug" backupFileDelimiter = "-" + nilAngleString = "" flags = 0x0 ) diff --git a/core/logx/writer.go b/core/logx/writer.go index 4395d5a6..09ae34b7 100644 --- a/core/logx/writer.go +++ b/core/logx/writer.go @@ -277,6 +277,20 @@ func combineGlobalFields(fields []LogField) []LogField { return ret } +func marshalJson(t interface{}) ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetEscapeHTML(false) + err := encoder.Encode(t) + // go 1.5+ will append a newline to the end of the json string + // https://github.com/golang/go/issues/13520 + if l := buf.Len(); l > 0 && buf.Bytes()[l-1] == '\n' { + buf.Truncate(l - 1) + } + + return buf.Bytes(), err +} + func output(writer io.Writer, level string, val any, fields ...LogField) { // only truncate string content, don't know how to truncate the values of other types. if v, ok := val.(string); ok { @@ -333,7 +347,7 @@ func wrapLevelWithColor(level string) string { } func writeJson(writer io.Writer, info any) { - if content, err := json.Marshal(info); err != nil { + if content, err := marshalJson(info); err != nil { log.Printf("err: %s\n\n%s", err.Error(), debug.Stack()) } else if writer == nil { log.Println(string(content))