Skip to content

Commit 1fd9085

Browse files
Improve formatter performance (#80)
1 parent df43be8 commit 1fd9085

File tree

18 files changed

+186
-167
lines changed

18 files changed

+186
-167
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
test:
2-
go test --race -gcflags=-l ./...
2+
go test --race -count=1 -v -gcflags=-l ./...
33

44
bench:
5-
go test -bench=. -gcflags=-l ./...
5+
go test -bench=. -count=1 -v -gcflags=-l ./...

benchmark_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func getEntries() []logger.Entry {
6868
}
6969
}
7070

71-
func BenchmarkMiddleware(b *testing.B) {
71+
func BenchmarkLoggerWithMiddleware(b *testing.B) {
7272
for _, entry := range getEntries() {
7373
for _, m := range loggersToBench() {
7474
b.Run(m.name+"_"+entry.Message, func(b *testing.B) {

context.go

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package logger
22

33
import (
4-
"strings"
4+
"github.com/valyala/bytebufferpool"
55
)
66

77
// Context contain all contextual log data
@@ -59,22 +59,22 @@ func (c *Context) ByteString(name string, value []byte) *Context {
5959
return c.SetField(ByteString(name, value))
6060
}
6161

62-
func (c *Context) stringTo(builder *strings.Builder) *Context {
62+
func (c *Context) StringTo(byteBuffer *bytebufferpool.ByteBuffer) *Context {
6363
if len(*c) == 0 {
64-
builder.WriteString("nil")
64+
byteBuffer.WriteString("<nil>")
6565
return c
6666
}
67-
i := 0
67+
first := true
6868
for name, field := range *c {
69-
if i != 0 {
70-
builder.WriteString(" ")
69+
if !first {
70+
byteBuffer.WriteString(" ")
7171
}
72-
builder.WriteString("<")
73-
builder.WriteString(name)
74-
builder.WriteString(":")
75-
builder.WriteString(field.String())
76-
builder.WriteString(">")
77-
i++
72+
byteBuffer.WriteString("<")
73+
byteBuffer.WriteString(name)
74+
byteBuffer.WriteString(":")
75+
byteBuffer.WriteString(field.String())
76+
byteBuffer.WriteString(">")
77+
first = false
7878
}
7979
return c
8080
}
@@ -93,19 +93,22 @@ func (c *Context) String() string {
9393
if c == nil || len(*c) == 0 {
9494
return "<nil>"
9595
}
96-
builder := &strings.Builder{}
97-
c.stringTo(builder)
98-
return builder.String()
96+
byteBuffer := bytebufferpool.Get()
97+
defer bytebufferpool.Put(byteBuffer)
98+
c.StringTo(byteBuffer)
99+
return byteBuffer.String()
99100
}
100101

101102
// GoString was called by fmt.Printf("%#v", context)
102103
// fmt GoStringer interface
103104
func (c *Context) GoString() string {
104-
builder := &strings.Builder{}
105-
builder.WriteString("logger.context[")
106-
c.stringTo(builder)
107-
builder.WriteString("]")
108-
return builder.String()
105+
byteBuffer := bytebufferpool.Get()
106+
defer bytebufferpool.Put(byteBuffer)
107+
108+
byteBuffer.WriteString("logger.context[")
109+
c.StringTo(byteBuffer)
110+
byteBuffer.WriteString("]")
111+
return byteBuffer.String()
109112
}
110113

111114
// Ctx will create a new context with given value

entry.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package logger
22

33
import (
4-
"strings"
4+
"github.com/valyala/bytebufferpool"
55
)
66

77
// Entry represents a log in its entirety
@@ -14,20 +14,22 @@ type Entry struct {
1414

1515
// String will return Entry as string
1616
func (e *Entry) String() string {
17-
builder := &strings.Builder{}
18-
EntryToString(*e, builder)
19-
return builder.String()
17+
byteBuffer := bytebufferpool.Get()
18+
defer bytebufferpool.Put(byteBuffer)
19+
20+
EntryToString(*e, byteBuffer)
21+
return byteBuffer.String()
2022
}
2123

22-
// EntryToString will write entry as string in builder
23-
func EntryToString(entry Entry, builder *strings.Builder) {
24-
builder.WriteString("<")
25-
builder.WriteString(entry.Level.String())
26-
builder.WriteString("> ")
27-
builder.WriteString(entry.Message)
24+
// EntryToString will write entry as string in byteBuffer
25+
func EntryToString(entry Entry, byteBuffer *bytebufferpool.ByteBuffer) {
26+
byteBuffer.WriteString(`<`)
27+
entry.Level.StringTo(byteBuffer)
28+
byteBuffer.WriteString(`> `)
29+
byteBuffer.WriteString(entry.Message)
2830
if entry.Context != nil {
29-
builder.WriteString(" [ ")
30-
builder.WriteString(entry.Context.String())
31-
builder.WriteString(" ]")
31+
byteBuffer.WriteString(` [ `)
32+
entry.Context.StringTo(byteBuffer)
33+
byteBuffer.WriteString(` ]`)
3234
}
3335
}

entry_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package logger_test
22

33
import (
4-
"strings"
4+
"github.com/valyala/bytebufferpool"
55
"testing"
66

77
"github.com/stretchr/testify/assert"
@@ -43,10 +43,12 @@ func TestEntryToString(t *testing.T) {
4343

4444
for _, tt := range tests {
4545
t.Run(tt.name, func(t *testing.T) {
46-
builder := &strings.Builder{}
47-
logger.EntryToString(tt.entry, builder)
46+
byteBuffer := bytebufferpool.Get()
47+
defer bytebufferpool.Put(byteBuffer)
48+
49+
logger.EntryToString(tt.entry, byteBuffer)
4850
for _, s := range tt.strings {
49-
assert.Contains(t, builder.String(), s)
51+
assert.Contains(t, byteBuffer.String(), s)
5052
}
5153
})
5254
}

field.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package logger
33
import (
44
"encoding/json"
55
"fmt"
6+
"github.com/valyala/bytebufferpool"
67
"strconv"
7-
"strings"
88
"time"
99
)
1010

@@ -126,15 +126,17 @@ func (f *Field) MarshalJSON() ([]byte, error) {
126126
// GoString was called by fmt.Printf("%#v", Fields)
127127
// fmt GoStringer interface
128128
func (f *Field) GoString() string {
129-
builder := &strings.Builder{}
130-
builder.WriteString("logger.Field{Name: ")
131-
builder.WriteString(f.Name)
132-
builder.WriteString(", Value: ")
133-
builder.WriteString(f.String())
134-
builder.WriteString(", Type: ")
135-
builder.WriteString(strconv.FormatUint(uint64(f.Type), 10))
136-
builder.WriteString("}")
137-
return builder.String()
129+
byteBuffer := bytebufferpool.Get()
130+
defer bytebufferpool.Put(byteBuffer)
131+
132+
byteBuffer.WriteString("logger.Field{Name: ")
133+
byteBuffer.WriteString(f.Name)
134+
byteBuffer.WriteString(", Value: ")
135+
byteBuffer.WriteString(f.String())
136+
byteBuffer.WriteString(", Type: ")
137+
byteBuffer.WriteString(strconv.FormatUint(uint64(f.Type), 10))
138+
byteBuffer.WriteString("}")
139+
return byteBuffer.String()
138140
}
139141

140142
// Skip will create Skip Field

fields.go

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package logger
22

33
import (
4-
"strings"
4+
"github.com/valyala/bytebufferpool"
55
)
66

77
// Fields was slice format of Fields
@@ -33,42 +33,45 @@ func (f *Fields) ByteString(name string, value []byte) *Fields {
3333
return f.SetField(ByteString(name, value))
3434
}
3535

36-
func (f *Fields) stringTo(builder *strings.Builder) *Fields {
36+
func (f *Fields) StringTo(byteBuffer *bytebufferpool.ByteBuffer) {
3737
if len(*f) == 0 {
38-
builder.WriteString(" ")
39-
return f
38+
byteBuffer.WriteString(" ")
39+
return
4040
}
41-
i := 0
41+
first := true
4242
for _, field := range *f {
43-
if i != 0 {
44-
builder.WriteString(" ")
43+
if !first {
44+
byteBuffer.WriteString(" ")
4545
}
46-
builder.WriteString("<")
47-
builder.WriteString(field.Name)
48-
builder.WriteString(":")
49-
builder.WriteString(field.String())
50-
builder.WriteString(">")
51-
i++
46+
byteBuffer.WriteString("<")
47+
byteBuffer.WriteString(field.Name)
48+
byteBuffer.WriteString(":")
49+
byteBuffer.WriteString(field.String())
50+
byteBuffer.WriteString(">")
51+
first = false
5252
}
53-
return f
54-
53+
return
5554
}
5655

5756
// String will return Fields as string
5857
func (f *Fields) String() string {
59-
builder := &strings.Builder{}
60-
f.stringTo(builder)
61-
return builder.String()
58+
byteBuffer := bytebufferpool.Get()
59+
defer bytebufferpool.Put(byteBuffer)
60+
61+
f.StringTo(byteBuffer)
62+
return byteBuffer.String()
6263
}
6364

6465
// GoString was called by fmt.Printf("%#v", Fields)
6566
// fmt GoStringer interface
6667
func (f *Fields) GoString() string {
67-
builder := &strings.Builder{}
68-
builder.WriteString("logger.Fields[")
69-
f.stringTo(builder)
70-
builder.WriteString("]")
71-
return builder.String()
68+
byteBuffer := bytebufferpool.Get()
69+
defer bytebufferpool.Put(byteBuffer)
70+
71+
byteBuffer.WriteString("logger.Fields[")
72+
f.StringTo(byteBuffer)
73+
byteBuffer.WriteString("]")
74+
return byteBuffer.String()
7275
}
7376

7477
// NewFields will create a Fields collection with initial value

formatter/default.go

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package formatter
22

33
import (
4-
"strings"
5-
64
"github.com/gol4ng/logger"
5+
"github.com/valyala/bytebufferpool"
76
)
87

98
var falseCondition = func(entry logger.Entry) bool {
@@ -18,60 +17,54 @@ type DefaultFormatter struct {
1817

1918
// Format will return Entry as string
2019
func (n *DefaultFormatter) Format(entry logger.Entry) string {
21-
n.init()
22-
builder := &strings.Builder{}
20+
byteBuffer := bytebufferpool.Get()
21+
defer bytebufferpool.Put(byteBuffer)
2322

2423
colored := n.colored(entry)
2524
if colored {
2625
switch entry.Level {
2726
case logger.DebugLevel:
28-
builder.WriteString("\x1b[1;36m")
27+
byteBuffer.WriteString("\x1b[1;36m")
2928
case logger.InfoLevel:
30-
builder.WriteString("\x1b[1;32m")
29+
byteBuffer.WriteString("\x1b[1;32m")
3130
case logger.NoticeLevel:
32-
builder.WriteString("\x1b[1;34m")
31+
byteBuffer.WriteString("\x1b[1;34m")
3332
case logger.WarningLevel:
34-
builder.WriteString("\x1b[1;33m")
33+
byteBuffer.WriteString("\x1b[1;33m")
3534
case logger.ErrorLevel:
36-
builder.WriteString("\x1b[1;31m")
35+
byteBuffer.WriteString("\x1b[1;31m")
3736
case logger.CriticalLevel:
38-
builder.WriteString("\x1b[1;30;47m")
37+
byteBuffer.WriteString("\x1b[1;30;47m")
3938
case logger.AlertLevel:
40-
builder.WriteString("\x1b[1;30;43m")
39+
byteBuffer.WriteString("\x1b[1;30;43m")
4140
case logger.EmergencyLevel:
42-
builder.WriteString("\x1b[1;37;41m")
41+
byteBuffer.WriteString("\x1b[1;37;41m")
4342
}
4443
}
4544

46-
builder.WriteString("<")
47-
builder.WriteString(entry.Level.String())
48-
builder.WriteString(">")
45+
byteBuffer.WriteString(`<`)
46+
byteBuffer.WriteString(entry.Level.String())
47+
byteBuffer.WriteString(`>`)
4948
if colored {
50-
builder.WriteString("\x1b[m")
49+
byteBuffer.WriteString("\x1b[m")
5150
}
5251
if entry.Message != "" {
53-
builder.WriteString(" ")
54-
builder.WriteString(entry.Message)
52+
byteBuffer.WriteString(` `)
53+
byteBuffer.WriteString(entry.Message)
5554
}
5655
if entry.Context != nil && n.displayContext(entry) {
57-
builder.WriteString(" ")
58-
ContextToJSON(entry.Context, builder)
59-
}
60-
return builder.String()
61-
}
62-
63-
func (n *DefaultFormatter) init() {
64-
if n.colored == nil {
65-
n.colored = falseCondition
66-
}
67-
if n.displayContext == nil {
68-
n.displayContext = falseCondition
56+
byteBuffer.WriteString(` `)
57+
ContextToJSON(entry.Context, byteBuffer)
6958
}
59+
return byteBuffer.String()
7060
}
7161

7262
// NewDefaultFormatter will create a new DefaultFormatter
7363
func NewDefaultFormatter(options ...Option) *DefaultFormatter {
74-
f := &DefaultFormatter{}
64+
f := &DefaultFormatter{
65+
colored: falseCondition,
66+
displayContext: falseCondition,
67+
}
7568
for _, option := range options {
7669
option(f)
7770
}

formatter/default_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ func TestDefaultFormatter_Format(t *testing.T) {
1616
entry logger.Entry
1717
expected string
1818
}{
19-
{name: "test default formatter struct", formatter: &formatter.DefaultFormatter{}, entry: logger.Entry{}, expected: "<emergency>"},
2019
{name: "test NewDefaultFormatter()", formatter: formatter.NewDefaultFormatter(), entry: logger.Entry{}, expected: "<emergency>"},
2120
{
2221
name: "test NewDefaultFormatter(formatter.WithContext(true)",

0 commit comments

Comments
 (0)