forked from mirror/logrus
Merge pull request #48 from Sirupsen/break-out-specials
entry: break out time, level and message from data
This commit is contained in:
commit
da97142f2a
|
@ -191,10 +191,10 @@ that severity or anything above it:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||||
log.Level = logrus.Info
|
log.Level = logrus.InfoLevel
|
||||||
```
|
```
|
||||||
|
|
||||||
It may be useful to set `log.Level = logrus.Debug` in a debug or verbose
|
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||||
environment if your application has that.
|
environment if your application has that.
|
||||||
|
|
||||||
#### Entries
|
#### Entries
|
||||||
|
@ -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)
|
||||||
|
|
26
entry.go
26
entry.go
|
@ -8,9 +8,24 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An entry is the final or intermediate Logrus logging entry. It containts all
|
||||||
|
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||||
|
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||||
|
// passed around as much as you wish to avoid field duplication.
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
Logger *Logger
|
Logger *Logger
|
||||||
|
|
||||||
|
// Contains all the fields set by the user.
|
||||||
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
|
||||||
|
@ -23,11 +38,14 @@ func NewEntry(logger *Logger) *Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a reader for the entry, which is a proxy to the formatter.
|
||||||
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
func (entry *Entry) Reader() (*bytes.Buffer, error) {
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
return bytes.NewBuffer(serialized), err
|
return bytes.NewBuffer(serialized), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the string representation from the reader and ultimately the
|
||||||
|
// formatter.
|
||||||
func (entry *Entry) String() (string, error) {
|
func (entry *Entry) String() (string, error) {
|
||||||
reader, err := entry.Reader()
|
reader, err := entry.Reader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,10 +55,12 @@ func (entry *Entry) String() (string, error) {
|
||||||
return reader.String(), err
|
return reader.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a single field to the Entry.
|
||||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||||
return entry.WithFields(Fields{key: value})
|
return entry.WithFields(Fields{key: value})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a map of fields to the Entry.
|
||||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
data := Fields{}
|
data := Fields{}
|
||||||
for k, v := range entry.Data {
|
for k, v := range entry.Data {
|
||||||
|
@ -53,9 +73,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)
|
||||||
|
|
39
formatter.go
39
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,38 @@ package logrus
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Format(*Entry) ([]byte, error)
|
Format(*Entry) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 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()
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ type JSONFormatter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
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())
|
||||||
|
|
|
@ -32,6 +32,8 @@ type TextFormatter struct {
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
|
|
||||||
|
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