Skip to content

Commit f6cfaea

Browse files
committed
Avoid expensive log.Valuer evaluation for disallowed levels
Signed-off-by: Xiaochao Dong (@damnever) <[email protected]>
1 parent 8e36c84 commit f6cfaea

File tree

3 files changed

+57
-15
lines changed

3 files changed

+57
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## master / unreleased
44
* [CHANGE] Alertmanager: Validating new fields on the PagerDuty AM config. #5290
5+
* [ENHANCEMENT] Log: Avoid expensive log.Valuer evaluation for disallowed levels. #5297
56
* [BUGFIX] Ruler: Validate if rule group can be safely converted back to rule group yaml from protobuf message #5265
67
* [BUGFIX] Querier: Convert gRPC `ResourceExhausted` status code from store gateway to 422 limit error. #5286
78

pkg/util/log/log.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,16 @@ func init() {
4949
// InitLogger initialises the global gokit logger (util_log.Logger) and overrides the
5050
// default logger for the server.
5151
func InitLogger(cfg *server.Config) {
52-
l, err := NewPrometheusLogger(cfg.LogLevel, cfg.LogFormat)
53-
if err != nil {
54-
panic(err)
55-
}
52+
l := newLoggerWithFormat(cfg.LogFormat)
5653

57-
// when use util_log.Logger, skip 3 stack frames.
58-
Logger = log.With(l, "caller", log.Caller(3))
54+
// when use util_log.Logger, skip 6 stack frames.
55+
Logger = newPrometheusLoggerFrom(l, cfg.LogLevel, "caller", log.Caller(6))
5956

60-
// cfg.Log wraps log function, skip 4 stack frames to get caller information.
57+
// cfg.Log wraps log function, skip 7 stack frames to get caller information.
6158
// this works in go 1.12, but doesn't work in versions earlier.
6259
// it will always shows the wrapper function generated by compiler
6360
// marked <autogenerated> in old versions.
64-
cfg.Log = logging.GoKit(log.With(l, "caller", log.Caller(4)))
61+
cfg.Log = logging.GoKit(newPrometheusLoggerFrom(l, cfg.LogLevel, "caller", log.Caller(7)))
6562
}
6663

6764
// PrometheusLogger exposes Prometheus counters for each of go-kit's log levels.
@@ -72,24 +69,32 @@ type PrometheusLogger struct {
7269
// NewPrometheusLogger creates a new instance of PrometheusLogger which exposes
7370
// Prometheus counters for various log levels.
7471
func NewPrometheusLogger(l logging.Level, format logging.Format) (log.Logger, error) {
72+
logger := newLoggerWithFormat(format)
73+
return newPrometheusLoggerFrom(logger, l), nil
74+
}
75+
76+
func newLoggerWithFormat(format logging.Format) log.Logger {
7577
logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
7678
if format.String() == "json" {
7779
logger = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
7880
}
79-
logger = level.NewFilter(logger, LevelFilter(l.String()))
81+
return logger
82+
}
83+
84+
func newPrometheusLoggerFrom(logger log.Logger, logLevel logging.Level, keyvals ...interface{}) log.Logger {
85+
// Sort the logger chain to avoid expensive log.Valuer evaluation for disallowed level.
86+
// Ref: https://github.com/go-kit/log/issues/14#issuecomment-945038252
87+
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
88+
logger = log.With(logger, keyvals...)
89+
logger = level.NewFilter(logger, LevelFilter(logLevel.String()))
8090

8191
// Initialise counters for all supported levels:
8292
for _, level := range supportedLevels {
8393
logMessages.WithLabelValues(level.String())
8494
}
85-
86-
logger = &PrometheusLogger{
95+
return &PrometheusLogger{
8796
logger: logger,
8897
}
89-
90-
// return a Logger without caller information, shouldn't use directly
91-
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
92-
return logger, nil
9398
}
9499

95100
// Log increments the appropriate Prometheus counter depending on the log level.

pkg/util/log/log_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package log
22

33
import (
44
"context"
5+
"io"
56
"net/http"
7+
"os"
68
"testing"
79

10+
"github.com/go-kit/log/level"
811
"github.com/stretchr/testify/require"
12+
"github.com/weaveworks/common/server"
913
"google.golang.org/grpc/metadata"
1014
)
1115

@@ -63,3 +67,35 @@ func TestInjectHeadersIntoHTTPRequest(t *testing.T) {
6367
require.Equal(t, "ContentsOfTestHeader2", header2[0])
6468

6569
}
70+
71+
func TestInitLogger(t *testing.T) {
72+
stderr := os.Stderr
73+
r, w, err := os.Pipe()
74+
require.NoError(t, err)
75+
os.Stderr = w
76+
defer func() { os.Stderr = stderr }()
77+
78+
cfg := &server.Config{}
79+
require.NoError(t, cfg.LogLevel.Set("debug"))
80+
InitLogger(cfg)
81+
82+
level.Debug(Logger).Log("hello", "world")
83+
cfg.Log.Debugf("%s %s", "hello", "world")
84+
85+
require.NoError(t, w.Close())
86+
logs, err := io.ReadAll(r)
87+
require.NoError(t, err)
88+
require.Contains(t, string(logs), "caller=log_test.go:82 level=debug hello=world")
89+
require.Contains(t, string(logs), "caller=log_test.go:83 level=debug msg=\"hello world\"")
90+
}
91+
92+
func BenchmarkDisallowedLogLevels(b *testing.B) {
93+
cfg := &server.Config{}
94+
require.NoError(b, cfg.LogLevel.Set("warn"))
95+
InitLogger(cfg)
96+
97+
for i := 0; i < b.N; i++ {
98+
level.Info(Logger).Log("hello", "world", "number", i)
99+
level.Debug(Logger).Log("hello", "world", "number", i)
100+
}
101+
}

0 commit comments

Comments
 (0)