log: Reuse encoders and buffers for Logfmt logger
This uses sync.Pool to avoid always allocating new encoders and buffers.
Reduces the number of allocations percall to Log by 2, if a value exists
in the pool.
Justin Nuß
7 years ago
0 | 0 | package log |
1 | 1 | |
2 | 2 | import ( |
3 | "bytes" | |
3 | 4 | "io" |
5 | "sync" | |
4 | 6 | |
5 | 7 | "github.com/go-logfmt/logfmt" |
6 | 8 | ) |
9 | ||
10 | type logfmtEncoder struct { | |
11 | *logfmt.Encoder | |
12 | buf bytes.Buffer | |
13 | } | |
14 | ||
15 | func (l *logfmtEncoder) Reset() { | |
16 | l.Encoder.Reset() | |
17 | l.buf.Reset() | |
18 | } | |
19 | ||
20 | var logfmtEncoderPool = sync.Pool{ | |
21 | New: func() interface{} { | |
22 | var enc logfmtEncoder | |
23 | enc.Encoder = logfmt.NewEncoder(&enc.buf) | |
24 | return &enc | |
25 | }, | |
26 | } | |
7 | 27 | |
8 | 28 | type logfmtLogger struct { |
9 | 29 | w io.Writer |
17 | 37 | } |
18 | 38 | |
19 | 39 | func (l logfmtLogger) Log(keyvals ...interface{}) error { |
40 | enc := logfmtEncoderPool.Get().(*logfmtEncoder) | |
41 | enc.Reset() | |
42 | defer logfmtEncoderPool.Put(enc) | |
43 | ||
44 | if err := enc.EncodeKeyvals(keyvals...); err != nil { | |
45 | return err | |
46 | } | |
47 | ||
48 | // Add newline to the end of the buffer | |
49 | if err := enc.EndRecord(); err != nil { | |
50 | return err | |
51 | } | |
52 | ||
20 | 53 | // The Logger interface requires implementations to be safe for concurrent |
21 | 54 | // use by multiple goroutines. For this implementation that means making |
22 | // only one call to l.w.Write() for each call to Log. We first collect all | |
23 | // of the bytes into b, and then call l.w.Write(b). | |
24 | b, err := logfmt.MarshalKeyvals(keyvals...) | |
25 | if err != nil { | |
26 | return err | |
27 | } | |
28 | b = append(b, '\n') | |
29 | if _, err := l.w.Write(b); err != nil { | |
55 | // only one call to l.w.Write() for each call to Log. | |
56 | if _, err := l.w.Write(enc.buf.Bytes()); err != nil { | |
30 | 57 | return err |
31 | 58 | } |
32 | 59 | return nil |