Skip to content

Commit ff2b36a

Browse files
ReasnoGGXXLL
andauthored
perf: logger performance optimization (#219)
* perf: logger performance optimization Only pay the cost of formatting if the log is not filtered. * doc: add example * Update example_test.go ExampleWithLevel Co-authored-by: Trock <35254251+GGXXLL@users.noreply.github.com>
1 parent cd2a071 commit ff2b36a

4 files changed

Lines changed: 96 additions & 14 deletions

File tree

logging/example_test.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package logging_test
22

33
import (
44
"context"
5+
"fmt"
56

67
"github.com/DoNewsCode/core/ctxmeta"
78
"github.com/DoNewsCode/core/logging"
@@ -27,7 +28,23 @@ func ExampleWithLevel() {
2728
levelLogger := logging.WithLevel(logger)
2829
levelLogger.Info("hello")
2930
// Output:
30-
// {"caller":"example_test.go:28","level":"info","msg":"hello"}
31+
// {"caller":"example_test.go:29","level":"info","msg":"hello"}
32+
}
33+
34+
func Example_sprintf() {
35+
logger := logging.NewLogger("json")
36+
// Set log level to info
37+
logger = level.NewFilter(logger, level.AllowInfo())
38+
levelLogger := logging.WithLevel(logger)
39+
40+
// Let's try to log some debug messages. They are filtered by log level, so you should see no output.
41+
// The cost of fmt.Sprintf is paid event if the log is filtered out. This sometimes can be a huge performance downside.
42+
levelLogger.Debugw("record some data", "data", fmt.Sprintf("%+v", []int{1, 2, 3}))
43+
// Or better, we can use logging.Sprintf to avoid the cost if the log is not actually written to the output.
44+
levelLogger.Debugw("record some data", "data", logging.Sprintf("%+v", []int{1, 2, 3}))
45+
46+
// Output:
47+
//
3148
}
3249

3350
func ExampleWithContext() {

logging/log.go

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package logging
1919

2020
import (
2121
"context"
22-
"fmt"
2322
"os"
2423
"strings"
2524

@@ -141,23 +140,19 @@ type levelLogger struct {
141140
}
142141

143142
func (l levelLogger) Debugf(s string, i ...interface{}) {
144-
s = fmt.Sprintf(s, i...)
145-
_ = level.Debug(l).Log("msg", s)
143+
_ = level.Debug(l).Log("msg", Sprintf(s, i...))
146144
}
147145

148146
func (l levelLogger) Infof(s string, i ...interface{}) {
149-
s = fmt.Sprintf(s, i...)
150-
_ = level.Info(l).Log("msg", s)
147+
_ = level.Info(l).Log("msg", Sprintf(s, i...))
151148
}
152149

153150
func (l levelLogger) Warnf(s string, i ...interface{}) {
154-
s = fmt.Sprintf(s, i...)
155-
_ = level.Warn(l).Log("msg", s)
151+
_ = level.Warn(l).Log("msg", Sprintf(s, i...))
156152
}
157153

158154
func (l levelLogger) Errf(s string, i ...interface{}) {
159-
s = fmt.Sprintf(s, i...)
160-
_ = level.Error(l).Log("msg", s)
155+
_ = level.Error(l).Log("msg", Sprintf(s, i...))
161156
}
162157

163158
func (l levelLogger) Debugw(s string, fields ...interface{}) {
@@ -181,19 +176,19 @@ func (l levelLogger) Errw(s string, fields ...interface{}) {
181176
}
182177

183178
func (l levelLogger) Debug(args ...interface{}) {
184-
_ = level.Debug(l).Log("msg", fmt.Sprint(args...))
179+
_ = level.Debug(l).Log("msg", Sprint(args...))
185180
}
186181

187182
func (l levelLogger) Info(args ...interface{}) {
188-
_ = level.Info(l).Log("msg", fmt.Sprint(args...))
183+
_ = level.Info(l).Log("msg", Sprint(args...))
189184
}
190185

191186
func (l levelLogger) Warn(args ...interface{}) {
192-
_ = level.Warn(l).Log("msg", fmt.Sprint(args...))
187+
_ = level.Warn(l).Log("msg", Sprint(args...))
193188
}
194189

195190
func (l levelLogger) Err(args ...interface{}) {
196-
_ = level.Error(l).Log("msg", fmt.Sprint(args...))
191+
_ = level.Error(l).Log("msg", Sprint(args...))
197192
}
198193

199194
// WithLevel decorates the logger and returns a contract.LevelLogger.

logging/log_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,35 @@ func TestSpanLogger(t *testing.T) {
7070

7171
assert.Equal(t, []interface{}{"foo", "bar", "baz", "qux"}, mock.received)
7272
}
73+
74+
type mockValue struct{}
75+
76+
func (m mockValue) String() string {
77+
// potentially expensive, but the cost is avoided because we have set a higher log level.
78+
panic("should not reach here")
79+
}
80+
81+
func TestPerformanceOptimization(t *testing.T) {
82+
var buf bytes.Buffer
83+
for _, c := range []struct {
84+
name string
85+
logger log.Logger
86+
}{
87+
{
88+
"json",
89+
log.NewJSONLogger(&buf),
90+
},
91+
{
92+
"logfmt",
93+
log.NewLogfmtLogger(&buf),
94+
},
95+
} {
96+
t.Run(c.name, func(t *testing.T) {
97+
l := level.NewFilter(c.logger, LevelFilter("error"))
98+
ll := WithLevel(l)
99+
ll.Debug(mockValue{})
100+
ll.Debugw("bar", "foo", mockValue{})
101+
ll.Debugf("%s", mockValue{})
102+
})
103+
}
104+
}

logging/sprintf.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package logging
2+
3+
import "fmt"
4+
5+
type sprintf struct {
6+
format string
7+
args []interface{}
8+
}
9+
10+
// String returns the formatted string value using fmt.Sprintf.
11+
func (s sprintf) String() string {
12+
return fmt.Sprintf(s.format, s.args...)
13+
}
14+
15+
// Sprintf returns a log entry that is formatted using fmt.Sprintf just before
16+
// writing to the output. This is more desirable than using fmt.Sprintf from the
17+
// caller's end because the cost of formatting can be avoided if the log is
18+
// filtered, for example, by log level.
19+
func Sprintf(format string, args ...interface{}) fmt.Stringer {
20+
return sprintf{format: format, args: args}
21+
}
22+
23+
type sprint struct {
24+
args []interface{}
25+
}
26+
27+
// String returns the formatted string value using fmt.Sprint.
28+
func (s sprint) String() string {
29+
return fmt.Sprint(s.args...)
30+
}
31+
32+
// Sprint returns a log entry that is formatted using fmt.Sprint just before
33+
// writing to the output. This is more desirable than using fmt.Sprint from the
34+
// caller's end because the cost of formatting can be avoided if the log is
35+
// filtered, for example, by log level.
36+
func Sprint(args ...interface{}) fmt.Stringer {
37+
return sprint{args: args}
38+
}

0 commit comments

Comments
 (0)