diff --git a/entry.go b/entry.go index 4efeddd..ca634a6 100644 --- a/entry.go +++ b/entry.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "reflect" "sync" "time" ) @@ -43,6 +44,9 @@ type Entry struct { // When formatter is called in entry.log(), a Buffer may be set to entry Buffer *bytes.Buffer + + // err may contain a field formatting error + err string } func NewEntry(logger *Logger) *Entry { @@ -80,10 +84,18 @@ func (entry *Entry) WithFields(fields Fields) *Entry { for k, v := range entry.Data { data[k] = v } + var field_err string for k, v := range fields { - data[k] = v + if t := reflect.TypeOf(v); t != nil && t.Kind() == reflect.Func { + field_err = fmt.Sprintf("can not add field %q", k) + if entry.err != "" { + field_err = entry.err + ", " + field_err + } + } else { + data[k] = v + } } - return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time} + return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: field_err} } // Overrides the time of the Entry. diff --git a/formatter.go b/formatter.go index 83c7494..be2f3fc 100644 --- a/formatter.go +++ b/formatter.go @@ -2,7 +2,14 @@ package logrus import "time" -const defaultTimestampFormat = time.RFC3339 +// Default key names for the default fields +const ( + defaultTimestampFormat = time.RFC3339 + FieldKeyMsg = "msg" + FieldKeyLevel = "level" + FieldKeyTime = "time" + FieldKeyLogrusError = "logrus_error" +) // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: @@ -48,4 +55,10 @@ func prefixFieldClashes(data Fields, fieldMap FieldMap) { data["fields."+levelKey] = l delete(data, levelKey) } + + logrusErrKey := fieldMap.resolve(FieldKeyLogrusError) + if l, ok := data[logrusErrKey]; ok { + data["fields."+logrusErrKey] = l + delete(data, logrusErrKey) + } } diff --git a/json_formatter.go b/json_formatter.go index d3dadef..ef8d074 100644 --- a/json_formatter.go +++ b/json_formatter.go @@ -11,13 +11,6 @@ type fieldKey string // FieldMap allows customization of the key names for default fields. type FieldMap map[fieldKey]string -// Default key names for the default fields -const ( - FieldKeyMsg = "msg" - FieldKeyLevel = "level" - FieldKeyTime = "time" -) - func (f FieldMap) resolve(key fieldKey) string { if k, ok := f[key]; ok { return k @@ -79,6 +72,9 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { timestampFormat = defaultTimestampFormat } + if entry.err != "" { + data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err + } if !f.DisableTimestamp { data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat) } diff --git a/logger_test.go b/logger_test.go new file mode 100644 index 0000000..73ba450 --- /dev/null +++ b/logger_test.go @@ -0,0 +1,42 @@ +package logrus + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFieldValueError(t *testing.T) { + buf := &bytes.Buffer{} + l := &Logger{ + Out: buf, + Formatter: new(JSONFormatter), + Hooks: make(LevelHooks), + Level: DebugLevel, + } + l.WithField("func", func() {}).Info("test") + fmt.Println(buf.String()) + var data map[string]interface{} + json.Unmarshal(buf.Bytes(), &data) + _, ok := data[FieldKeyLogrusError] + require.True(t, ok) +} + +func TestNoFieldValueError(t *testing.T) { + buf := &bytes.Buffer{} + l := &Logger{ + Out: buf, + Formatter: new(JSONFormatter), + Hooks: make(LevelHooks), + Level: DebugLevel, + } + l.WithField("str", "str").Info("test") + fmt.Println(buf.String()) + var data map[string]interface{} + json.Unmarshal(buf.Bytes(), &data) + _, ok := data[FieldKeyLogrusError] + require.False(t, ok) +} diff --git a/text_formatter.go b/text_formatter.go index 67fb686..d4663b8 100644 --- a/text_formatter.go +++ b/text_formatter.go @@ -114,7 +114,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { keys = append(keys, k) } - fixedKeys := make([]string, 0, 3+len(entry.Data)) + fixedKeys := make([]string, 0, 4+len(entry.Data)) if !f.DisableTimestamp { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime)) } @@ -122,6 +122,9 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { if entry.Message != "" { fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg)) } + if entry.err != "" { + fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError)) + } if !f.DisableSorting { if f.SortingFunc == nil { @@ -164,6 +167,8 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { value = entry.Level.String() case f.FieldMap.resolve(FieldKeyMsg): value = entry.Message + case f.FieldMap.resolve(FieldKeyLogrusError): + value = entry.err default: value = entry.Data[key] }