mirror of https://github.com/sirupsen/logrus.git
entry: break out time, level and message from data
This commit is contained in:
parent
f0cb18fc85
commit
40069a98d6
|
@ -261,6 +261,9 @@ type MyJSONFormatter struct {
|
||||||
log.Formatter = new(MyJSONFormatter)
|
log.Formatter = new(MyJSONFormatter)
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
// Note this doesn't include Time, Level and Message which are available on
|
||||||
|
// the Entry. Consult `godoc` on information about those fields or read the
|
||||||
|
// source of the official loggers.
|
||||||
serialized, err := json.Marshal(entry.Data)
|
serialized, err := json.Marshal(entry.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
|
15
entry.go
15
entry.go
|
@ -11,6 +11,15 @@ import (
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
Logger *Logger
|
Logger *Logger
|
||||||
Data Fields
|
Data Fields
|
||||||
|
|
||||||
|
// Time at which the log entry was created
|
||||||
|
Time time.Time
|
||||||
|
|
||||||
|
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseTimestamp time.Time
|
var baseTimestamp time.Time
|
||||||
|
@ -53,9 +62,9 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) log(level Level, msg string) string {
|
func (entry *Entry) log(level Level, msg string) string {
|
||||||
entry.Data["time"] = time.Now().String()
|
entry.Time = time.Now()
|
||||||
entry.Data["level"] = level.String()
|
entry.Level = level
|
||||||
entry.Data["msg"] = msg
|
entry.Message = msg
|
||||||
|
|
||||||
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook", err)
|
fmt.Fprintf(os.Stderr, "Failed to fire hook", err)
|
||||||
|
|
42
formatter.go
42
formatter.go
|
@ -1,5 +1,9 @@
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
//
|
//
|
||||||
|
@ -13,3 +17,41 @@ package logrus
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Format(*Entry) ([]byte, error)
|
Format(*Entry) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type internalFormatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||||
|
// dumping it. If this code wasn't there doing:
|
||||||
|
//
|
||||||
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
|
//
|
||||||
|
// Would just silently drop the user provided level. Instead with this code
|
||||||
|
// it'll logged as:
|
||||||
|
//
|
||||||
|
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||||
|
//
|
||||||
|
// It's not exported because it's still using Data in an opionated way. It's to
|
||||||
|
// avoid code duplication between the two default formatters.
|
||||||
|
func (f *internalFormatter) prefixFieldClashes(entry *Entry) {
|
||||||
|
_, ok := entry.Data["time"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.time"] = entry.Data["time"]
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Data["time"] = entry.Time.Format(time.RFC3339)
|
||||||
|
|
||||||
|
_, ok = entry.Data["msg"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.msg"] = entry.Data["msg"]
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Data["msg"] = entry.Message
|
||||||
|
|
||||||
|
_, ok = entry.Data["level"]
|
||||||
|
if ok {
|
||||||
|
entry.Data["fields.level"] = entry.Data["level"]
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Data["level"] = entry.Level.String()
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type JSONFormatter struct {
|
type JSONFormatter struct {
|
||||||
|
*internalFormatter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
f.prefixFieldClashes(entry)
|
||||||
|
|
||||||
serialized, err := json.Marshal(entry.Data)
|
serialized, err := json.Marshal(entry.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||||
|
|
|
@ -129,6 +129,40 @@ func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
||||||
assert.Equal(t, "value1", fields["key1"])
|
assert.Equal(t, "value1", fields["key1"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("msg", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["msg"], "test")
|
||||||
|
assert.Equal(t, fields["fields.msg"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("time", "hello").Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["fields.time"], "hello")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
||||||
|
LogAndAssertJSON(t, func(log *Logger) {
|
||||||
|
log.WithField("level", 1).Info("test")
|
||||||
|
}, func(fields Fields) {
|
||||||
|
assert.Equal(t, fields["level"], "info")
|
||||||
|
assert.Equal(t, fields["fields.level"], 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertLevelToString(t *testing.T) {
|
func TestConvertLevelToString(t *testing.T) {
|
||||||
assert.Equal(t, "debug", DebugLevel.String())
|
assert.Equal(t, "debug", DebugLevel.String())
|
||||||
assert.Equal(t, "info", InfoLevel.String())
|
assert.Equal(t, "info", InfoLevel.String())
|
||||||
|
|
|
@ -27,11 +27,15 @@ func miniTS() int {
|
||||||
type TextFormatter struct {
|
type TextFormatter struct {
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
ForceColors bool
|
ForceColors bool
|
||||||
|
|
||||||
|
*internalFormatter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
|
|
||||||
|
f.prefixFieldClashes(entry)
|
||||||
|
|
||||||
if f.ForceColors || IsTerminal() {
|
if f.ForceColors || IsTerminal() {
|
||||||
levelText := strings.ToUpper(entry.Data["level"].(string))[0:4]
|
levelText := strings.ToUpper(entry.Data["level"].(string))[0:4]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue