Skip to content

Commit

Permalink
Optimize the performance of the json formatter.
Browse files Browse the repository at this point in the history
  • Loading branch information
edoger committed Apr 29, 2021
1 parent c1de7f9 commit b9b2d5a
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 9 deletions.
48 changes: 41 additions & 7 deletions json_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,24 @@ func NewJSONFormatter(keys map[string]string, full bool) (Formatter, error) {
"name": "name", "time": "time", "level": "level", "message": "message",
"fields": "fields", "caller": "caller",
}

changed := true
if len(keys) > 0 {
for key, value := range keys {
if m[key] == "" {
return nil, fmt.Errorf("invalid json formatter key %q", key)
}
// We ignore the case where all fields are mapped as empty, which is more practical.
if value != "" {
if value != "" && m[key] != value {
changed = false
m[key] = value
}
}
}
f := &jsonFormatter{
name: m["name"], time: m["time"], level: m["level"], message: m["message"],
fields: m["fields"], caller: m["caller"],
full: full,
full: full, changed: changed,
}
return f, nil
}
Expand All @@ -76,10 +79,45 @@ type jsonFormatter struct {
fields string
caller string
full bool
changed bool
}

// Special built-in structure for json serialization.
// The order of fields cannot be changed.
type jsonFormatterObject struct {
Caller *string `json:"caller,omitempty"`
Fields interface{} `json:"fields,omitempty"` // map[string]interface{} or struct{}
Level string `json:"level"`
Message string `json:"message"`
Name string `json:"name"`
Time string `json:"time"`
}

// Format formats the given log entity into character data and writes it to the given buffer.
func (f *jsonFormatter) Format(e Entity, b *bytes.Buffer) error {
// In most cases, the performance of json serialization of structure is higher than
// that of json serialization of map. When the json field name has not changed, we
// try to use structure for json serialization.
if f.changed {
o := &jsonFormatterObject{
Level: e.Level().String(),
Message: e.Message(),
Name: e.Name(),
Time: e.TimeString(),
}
if fields := e.Fields(); len(fields) > 0 {
o.Fields = internal.StandardiseFieldsForJSONEncoder(fields)
} else {
if f.full {
o.Fields = struct{}{}
}
}
if caller := e.Caller(); f.full || caller != "" {
o.Caller = &caller
}
return json.NewEncoder(b).Encode(o)
}

kv := map[string]interface{}{
f.name: e.Name(),
f.time: e.TimeString(),
Expand All @@ -93,12 +131,8 @@ func (f *jsonFormatter) Format(e Entity, b *bytes.Buffer) error {
kv[f.fields] = struct{}{}
}
}
if caller := e.Caller(); caller != "" {
if caller := e.Caller(); f.full || caller != "" {
kv[f.caller] = caller
} else {
if f.full {
kv[f.caller] = ""
}
}
// The json.Encoder.Encode method automatically adds line breaks.
return json.NewEncoder(b).Encode(kv)
Expand Down
33 changes: 31 additions & 2 deletions json_formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,39 @@ func TestJSONFormatter_Format(t *testing.T) {
l.SetOutput(buf)
l.SetDefaultTimeFormat("test")

l.Info("test")
l.WithField("foo", 1).Info("test")

got := buf.String()
want := `{"caller":"","fields":{},"level":"info","msg":"test","name":"test","time":"test"}` + "\n"
want := `{"caller":"","fields":{"foo":1},"level":"info","msg":"test","name":"test","time":"test"}` + "\n"
if got != want {
t.Fatalf("JSONFormatter.Format(): want %q, got %q", want, got)
}

buf.Reset()
l.Info("test")

got = buf.String()
want = `{"caller":"","fields":{},"level":"info","msg":"test","name":"test","time":"test"}` + "\n"
if got != want {
t.Fatalf("JSONFormatter.Format(): want %q, got %q", want, got)
}

l.SetFormatter(MustNewJSONFormatter(nil, true))

buf.Reset()
l.WithField("foo", 1).Info("test")

got = buf.String()
want = `{"caller":"","fields":{"foo":1},"level":"info","message":"test","name":"test","time":"test"}` + "\n"
if got != want {
t.Fatalf("JSONFormatter.Format(): want %q, got %q", want, got)
}

buf.Reset()
l.Info("test")

got = buf.String()
want = `{"caller":"","fields":{},"level":"info","message":"test","name":"test","time":"test"}` + "\n"
if got != want {
t.Fatalf("JSONFormatter.Format(): want %q, got %q", want, got)
}
Expand Down

0 comments on commit b9b2d5a

Please sign in to comment.