Skip to content

Commit d21f58c

Browse files
authored
feat(log): wire logger filtering (#15601)
1 parent f3110e9 commit d21f58c

18 files changed

Lines changed: 170 additions & 76 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
cosmossdk.io/core v0.6.1
99
cosmossdk.io/depinject v1.0.0-alpha.3
1010
cosmossdk.io/errors v1.0.0-beta.7
11-
cosmossdk.io/log v0.1.0
11+
cosmossdk.io/log v0.1.1-0.20230329182155-367f0dc1194d
1212
cosmossdk.io/math v1.0.0
1313
cosmossdk.io/store v0.1.0-alpha.1.0.20230328185921-37ba88872dbc
1414
cosmossdk.io/x/tx v0.5.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z
4545
cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
4646
cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w=
4747
cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE=
48-
cosmossdk.io/log v0.1.0 h1:Vnexi+KzUCjmqq/m93teAxjt5biWFfZ5PI1imx2IJw8=
49-
cosmossdk.io/log v0.1.0/go.mod h1:p95Wq6mDY3SREMc4y7+QU9Uwy3nyvfpWGD1iSaFkVFs=
48+
cosmossdk.io/log v0.1.1-0.20230329182155-367f0dc1194d h1:vRbhjJNv2/GuzU9/PX61Nl/+uyy3gmd5DKoNkQpMzSs=
49+
cosmossdk.io/log v0.1.1-0.20230329182155-367f0dc1194d/go.mod h1:CwX9BLiBruZb7lzLlRr3R231d/fVPUXk8gAdV4LQap0=
5050
cosmossdk.io/math v1.0.0 h1:ro9w7eKx23om2tZz/VM2Pf+z2WAbGX1yDQQOJ6iGeJw=
5151
cosmossdk.io/math v1.0.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k=
5252
cosmossdk.io/store v0.1.0-alpha.1.0.20230328185921-37ba88872dbc h1:9piuA+NYmhe+SyMPtMoboLw/djgDbrI3dD5TG020Tnk=

log/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
3131

3232
## [Unreleased]
3333

34-
<!-- ## [v1.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/log/v1.0.0) - 2023-0X-XX -->
35-
<!-- Wait for https://github.com/rs/zerolog/pull/527 to be merged -->
34+
## [v1.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/log/v1.0.0) - 2023-03-30
35+
36+
* [#15601](https://github.com/cosmos/cosmos-sdk/pull/15601) Introduce logger options. These options allow to configure the logger with filters, different level and output format.
3637

3738
## [v0.1.0](https://github.com/cosmos/cosmos-sdk/releases/tag/log/v0.1.0) - 2023-03-13
3839

log/level.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import (
88
"github.com/rs/zerolog"
99
)
1010

11+
const defaultLogLevelKey = "*"
12+
1113
// FilterFunc is a function that returns true if the log level is filtered for the given key
1214
// When the filter returns true, the log entry is discarded.
1315
type FilterFunc func(key, level string) bool
1416

15-
const defaultLogLevelKey = "*"
16-
1717
// ParseLogLevel parses complex log level
1818
// A comma-separated list of module:level pairs with an optional *:level pair
1919
// (* means all other modules).

log/logger.go

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,36 @@ type zeroLogWrapper struct {
4242
*zerolog.Logger
4343
}
4444

45-
// NewNopLogger returns a new logger that does nothing.
46-
func NewNopLogger() Logger {
47-
// The custom nopLogger is about 3x faster than a zeroLogWrapper with zerolog.Nop().
48-
return nopLogger{}
49-
}
50-
5145
// NewLogger returns a new logger that writes to the given destination.
5246
//
5347
// Typical usage from a main function is:
54-
// logger := log.NewLogger(os.Stderr)
48+
//
49+
// logger := log.NewLogger(os.Stderr)
5550
//
5651
// Stderr is the typical destination for logs,
5752
// so that any output from your application can still be piped to other processes.
58-
func NewLogger(dst io.Writer) Logger {
59-
output := zerolog.ConsoleWriter{Out: dst, TimeFormat: time.Kitchen}
53+
func NewLogger(dst io.Writer, options ...Option) Logger {
54+
logCfg := defaultConfig
55+
for _, opt := range options {
56+
opt(&logCfg)
57+
}
58+
59+
output := dst
60+
if !logCfg.OutputJSON {
61+
output = zerolog.ConsoleWriter{Out: dst, TimeFormat: time.Kitchen}
62+
}
63+
64+
if logCfg.Filter != nil {
65+
output = NewFilterWriter(output, logCfg.Filter)
66+
}
67+
6068
logger := zerolog.New(output).With().Timestamp().Logger()
61-
return zeroLogWrapper{&logger}
62-
}
6369

64-
// NewLoggerWithKV is shorthand for NewLogger(dst).With(key, value).
65-
func NewLoggerWithKV(dst io.Writer, key, value string) Logger {
66-
return NewLogger(dst).With(key, value)
70+
if logCfg.Level != zerolog.NoLevel {
71+
logger = logger.Level(logCfg.Level)
72+
}
73+
74+
return zeroLogWrapper{&logger}
6775
}
6876

6977
// NewCustomLogger returns a new logger with the given zerolog logger.
@@ -101,33 +109,10 @@ func (l zeroLogWrapper) Impl() interface{} {
101109
return l.Logger
102110
}
103111

104-
// FilterKeys returns a new logger that filters out all key/value pairs that do not match the filter.
105-
// This functions assumes that the logger is a zerolog.Logger, which is the case for the logger returned by log.NewLogger().
106-
// NOTE: filtering has a performance impact on the logger.
107-
func FilterKeys(logger Logger, filter FilterFunc) Logger {
108-
zl, ok := logger.Impl().(*zerolog.Logger)
109-
if !ok {
110-
panic("logger is not a zerolog.Logger")
111-
}
112-
113-
filteredLogger := zl.Hook(zerolog.HookFunc(func(e *zerolog.Event, lvl zerolog.Level, _ string) {
114-
// TODO(@julienrbrt) wait for https://github.com/rs/zerolog/pull/527 to be merged
115-
// keys, err := e.GetKeys()
116-
// if err != nil {
117-
// panic(err)
118-
// }
119-
120-
keys := []string{}
121-
122-
for _, key := range keys {
123-
if filter(key, lvl.String()) {
124-
e.Discard()
125-
break
126-
}
127-
}
128-
}))
129-
130-
return NewCustomLogger(filteredLogger)
112+
// NewNopLogger returns a new logger that does nothing.
113+
func NewNopLogger() Logger {
114+
// The custom nopLogger is about 3x faster than a zeroLogWrapper with zerolog.Nop().
115+
return nopLogger{}
131116
}
132117

133118
// nopLogger is a Logger that does nothing when called.

log/options.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package log
2+
3+
import "github.com/rs/zerolog"
4+
5+
// defaultConfig has all the options disabled.
6+
var defaultConfig = Config{
7+
Level: zerolog.NoLevel,
8+
Filter: nil,
9+
OutputJSON: false,
10+
}
11+
12+
// LoggerConfig defines configuration for the logger.
13+
type Config struct {
14+
Level zerolog.Level
15+
Filter FilterFunc
16+
OutputJSON bool
17+
}
18+
19+
type Option func(*Config)
20+
21+
// FilterOption sets the filter for the Logger.
22+
func FilterOption(filter FilterFunc) Option {
23+
return func(cfg *Config) {
24+
cfg.Filter = filter
25+
}
26+
}
27+
28+
// LevelOption sets the level for the Logger.
29+
// Messages with a lower level will be discarded.
30+
func LevelOption(level zerolog.Level) Option {
31+
return func(cfg *Config) {
32+
cfg.Level = level
33+
}
34+
}
35+
36+
// OutputJSONOption sets the output of the logger to JSON.
37+
// By default, the logger outputs to a human-readable format.
38+
func OutputJSONOption() Option {
39+
return func(cfg *Config) {
40+
cfg.OutputJSON = true
41+
}
42+
}

log/writer.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package log
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
)
8+
9+
// NewFilterWriter returns a writer that filters out all key/value pairs that do not match the filter.
10+
// If the filter is nil, the writer will pass all events through.
11+
// The filter function is called with the module and level of the event.
12+
func NewFilterWriter(parent io.Writer, filter FilterFunc) io.Writer {
13+
return &filterWriter{parent, filter}
14+
}
15+
16+
type filterWriter struct {
17+
parent io.Writer
18+
filter FilterFunc
19+
}
20+
21+
func (fw *filterWriter) Write(p []byte) (n int, err error) {
22+
if fw.filter == nil {
23+
return fw.parent.Write(p)
24+
}
25+
26+
var event struct {
27+
Level string `json:"level"`
28+
Module string `json:"module"`
29+
}
30+
31+
if err := json.Unmarshal(p, &event); err != nil {
32+
return 0, fmt.Errorf("failed to unmarshal event: %w", err)
33+
}
34+
35+
// only filter module keys
36+
if fw.filter(event.Module, event.Level) {
37+
return len(p), nil
38+
}
39+
40+
return fw.parent.Write(p)
41+
}

log/writer_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package log_test
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"cosmossdk.io/log"
9+
"gotest.tools/v3/assert"
10+
)
11+
12+
func TestFilteredWriter(t *testing.T) {
13+
buf := new(bytes.Buffer)
14+
15+
level := "consensus:debug,mempool:debug,*:error"
16+
filter, err := log.ParseLogLevel(level)
17+
assert.NilError(t, err)
18+
19+
logger := log.NewLogger(buf, log.FilterOption(filter))
20+
logger.Debug("this log line should be displayed", log.ModuleKey, "consensus")
21+
assert.Check(t, strings.Contains(buf.String(), "this log line should be displayed"))
22+
buf.Reset()
23+
24+
logger.Debug("this log line should be filtered", log.ModuleKey, "server")
25+
assert.Check(t, buf.Len() == 0)
26+
}

server/util.go

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -169,37 +169,36 @@ func InterceptConfigsAndCreateContext(cmd *cobra.Command, customAppConfigTemplat
169169
// CreateSDKLogger creates a the default SDK logger.
170170
// It reads the log level and format from the server context.
171171
func CreateSDKLogger(ctx *Context, out io.Writer) (log.Logger, error) {
172-
var logger log.Logger
172+
var opts []log.Option
173173
if ctx.Viper.GetString(flags.FlagLogFormat) == cmtcfg.LogFormatJSON {
174-
zl := zerolog.New(out).With().Timestamp().Logger()
175-
logger = log.NewCustomLogger(zl)
176-
} else {
177-
logger = log.NewLogger(out)
174+
opts = append(opts, log.OutputJSONOption())
178175
}
179176

180-
// set filter level or keys for the logger if any
177+
// check and set filter level or keys for the logger if any
181178
logLvlStr := ctx.Viper.GetString(flags.FlagLogLevel)
179+
if logLvlStr == "" {
180+
return log.NewLogger(out, opts...), nil
181+
}
182+
182183
logLvl, err := zerolog.ParseLevel(logLvlStr)
183184
if err != nil {
184185
// If the log level is not a valid zerolog level, then we try to parse it as a key filter.
185186
filterFunc, err := log.ParseLogLevel(logLvlStr)
186187
if err != nil {
187-
return nil, fmt.Errorf("failed to parse log level (%s): %w", logLvlStr, err)
188+
return nil, err
188189
}
189190

190-
logger = log.FilterKeys(logger, filterFunc)
191-
} else {
192-
zl := logger.Impl().(*zerolog.Logger)
191+
opts = append(opts, log.FilterOption(filterFunc))
192+
193+
} else if ctx.Viper.GetBool(cmtcli.TraceFlag) {
193194
// Check if the CometBFT flag for trace logging is set if it is then setup a tracing logger in this app as well.
194195
// Note it overrides log level passed in `log_levels`.
195-
if ctx.Viper.GetBool(cmtcli.TraceFlag) {
196-
logger = log.NewCustomLogger(zl.Level(zerolog.TraceLevel))
197-
} else {
198-
logger = log.NewCustomLogger(zl.Level(logLvl))
199-
}
196+
opts = append(opts, log.LevelOption(zerolog.TraceLevel))
197+
} else {
198+
opts = append(opts, log.LevelOption(logLvl))
200199
}
201200

202-
return logger, nil
201+
return log.NewLogger(out, opts...), nil
203202
}
204203

205204
// GetServerContextFromCmd returns a Context from a command or an empty Context

simapp/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
cosmossdk.io/client/v2 v2.0.0-20230309163709-87da587416ba
88
cosmossdk.io/core v0.6.1
99
cosmossdk.io/depinject v1.0.0-alpha.3
10-
cosmossdk.io/log v0.1.0
10+
cosmossdk.io/log v0.1.1-0.20230329182155-367f0dc1194d
1111
cosmossdk.io/math v1.0.0
1212
cosmossdk.io/store v0.1.0-alpha.1.0.20230328185921-37ba88872dbc
1313
cosmossdk.io/tools/confix v0.0.0-20230120150717-4f6f6c00021f

0 commit comments

Comments
 (0)