fix duplication of msg/time/level keys, add benchmarks.

This commit is contained in:
Antoine Grondin 2014-09-16 20:19:42 -04:00
parent 89b6d460f0
commit c4cc714937
5 changed files with 130 additions and 48 deletions

View File

@ -76,12 +76,12 @@ func (entry *Entry) log(level Level, msg string) string {
entry.Message = 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\n", err)
} }
reader, err := entry.Reader() reader, err := entry.Reader()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v", err) fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
} }
entry.Logger.mu.Lock() entry.Logger.mu.Lock()
@ -89,7 +89,7 @@ func (entry *Entry) log(level Level, msg string) string {
_, err = io.Copy(entry.Logger.Out, reader) _, err = io.Copy(entry.Logger.Out, reader)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v", err) fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
} }
return reader.String() return reader.String()

View File

@ -1,9 +1,5 @@
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:
// //
@ -36,19 +32,13 @@ func prefixFieldClashes(entry *Entry) {
entry.Data["fields.time"] = entry.Data["time"] entry.Data["fields.time"] = entry.Data["time"]
} }
entry.Data["time"] = entry.Time.Format(time.RFC3339)
_, ok = entry.Data["msg"] _, ok = entry.Data["msg"]
if ok { if ok {
entry.Data["fields.msg"] = entry.Data["msg"] entry.Data["fields.msg"] = entry.Data["msg"]
} }
entry.Data["msg"] = entry.Message
_, ok = entry.Data["level"] _, ok = entry.Data["level"]
if ok { if ok {
entry.Data["fields.level"] = entry.Data["level"] entry.Data["fields.level"] = entry.Data["level"]
} }
entry.Data["level"] = entry.Level.String()
} }

88
formatter_bench_test.go Normal file
View File

@ -0,0 +1,88 @@
package logrus
import (
"testing"
"time"
)
// smallFields is a small size data set for benchmarking
var smallFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
}
// largeFields is a large size data set for benchmarking
var largeFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
"five": "six",
"seven": "eight",
"nine": "ten",
"eleven": "twelve",
"thirteen": "fourteen",
"fifteen": "sixteen",
"seventeen": "eighteen",
"nineteen": "twenty",
"a": "b",
"c": "d",
"e": "f",
"g": "h",
"i": "j",
"k": "l",
"m": "n",
"o": "p",
"q": "r",
"s": "t",
"u": "v",
"w": "x",
"y": "z",
"this": "will",
"make": "thirty",
"entries": "yeah",
}
func BenchmarkSmallTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkLargeTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
}
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
}
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
}
func BenchmarkSmallJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, smallFields)
}
func BenchmarkLargeJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, largeFields)
}
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
entry := &Entry{
Time: time.Time{},
Level: InfoLevel,
Message: "message",
Data: fields,
}
var d []byte
var err error
for i := 0; i < b.N; i++ {
d, err = formatter.Format(entry)
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(len(d)))
}
}

View File

@ -3,13 +3,16 @@ package logrus
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time"
) )
type JSONFormatter struct { type JSONFormatter struct{}
}
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
prefixFieldClashes(entry) prefixFieldClashes(entry)
entry.Data["time"] = entry.Time.Format(time.RFC3339)
entry.Data["msg"] = entry.Message
entry.Data["level"] = entry.Level.String()
serialized, err := json.Marshal(entry.Data) serialized, err := json.Marshal(entry.Data)
if err != nil { if err != nil {

View File

@ -38,45 +38,26 @@ type TextFormatter struct {
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
var keys []string
for k := range entry.Data {
keys = append(keys, k)
}
sort.Strings(keys)
b := &bytes.Buffer{} b := &bytes.Buffer{}
prefixFieldClashes(entry) prefixFieldClashes(entry)
if (f.ForceColors || isTerminal) && !f.DisableColors { isColored := (f.ForceColors || isTerminal) && !f.DisableColors
levelText := strings.ToUpper(entry.Data["level"].(string))[0:4]
levelColor := blue if isColored {
printColored(b, entry, keys)
if entry.Data["level"] == "warning" {
levelColor = yellow
} else if entry.Data["level"] == "error" ||
entry.Data["level"] == "fatal" ||
entry.Data["level"] == "panic" {
levelColor = red
}
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Data["msg"])
var keys []string
for k := range entry.Data {
if k != "level" && k != "time" && k != "msg" {
keys = append(keys, k)
}
}
sort.Strings(keys)
for _, k := range keys {
v := entry.Data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
}
} else { } else {
f.AppendKeyValue(b, "time", entry.Data["time"].(string)) f.AppendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
f.AppendKeyValue(b, "level", entry.Data["level"].(string)) f.AppendKeyValue(b, "level", entry.Level.String())
f.AppendKeyValue(b, "msg", entry.Data["msg"].(string)) f.AppendKeyValue(b, "msg", entry.Message)
for _, key := range keys {
for key, value := range entry.Data { f.AppendKeyValue(b, key, entry.Data[key])
if key != "time" && key != "level" && key != "msg" {
f.AppendKeyValue(b, key, value)
}
} }
} }
@ -84,6 +65,26 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
return b.Bytes(), nil return b.Bytes(), nil
} }
func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
var levelColor int
switch entry.Level {
case WarnLevel:
levelColor = yellow
case ErrorLevel, FatalLevel, PanicLevel:
levelColor = red
default:
levelColor = blue
}
levelText := strings.ToUpper(entry.Level.String())[0:4]
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
for _, k := range keys {
v := entry.Data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
}
}
func (f *TextFormatter) AppendKeyValue(b *bytes.Buffer, key, value interface{}) { func (f *TextFormatter) AppendKeyValue(b *bytes.Buffer, key, value interface{}) {
if _, ok := value.(string); ok { if _, ok := value.(string); ok {
fmt.Fprintf(b, "%v=%q ", key, value) fmt.Fprintf(b, "%v=%q ", key, value)