feat: verbose logging during upgrades (#24720)
Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io>
This commit is contained in:
co-authored by
Alex | Interchain Labs
parent
3d777ad46a
commit
be955efe25
@@ -22,6 +22,8 @@ Each entry must include the Github issue reference in the following format:
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
* [#24720](https://github.com/cosmos/cosmos-sdk/pull/24720) add `VerboseModeLogger` extension interface and `VerboseLevel` configuration option for increasing log verbosity during sensitive operations such as upgrades.
|
||||
|
||||
## [v1.5.1](https://github.com/cosmos/cosmos-sdk/releases/tag/log/v1.5.1) - 2025-03-07
|
||||
|
||||
* [#23928](https://github.com/cosmos/cosmos-sdk/pull/23928) Bump sonic json library to [v1.3.1](https://github.com/bytedance/sonic/releases/tag/v1.13.1) for Go 1.24 compatibility.
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package log_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
)
|
||||
|
||||
@@ -88,3 +92,128 @@ func TestParseLogLevel(t *testing.T) {
|
||||
t.Errorf("expected filter to return true for state:debug")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerboseMode(t *testing.T) {
|
||||
logMessages := []struct {
|
||||
level zerolog.Level
|
||||
module string
|
||||
message string
|
||||
}{
|
||||
{
|
||||
zerolog.InfoLevel,
|
||||
"foo",
|
||||
"msg 1",
|
||||
},
|
||||
{
|
||||
zerolog.WarnLevel,
|
||||
"foo",
|
||||
"msg 2",
|
||||
},
|
||||
{
|
||||
zerolog.ErrorLevel,
|
||||
"bar",
|
||||
"msg 3",
|
||||
},
|
||||
{
|
||||
zerolog.DebugLevel,
|
||||
"foo",
|
||||
"msg 4",
|
||||
},
|
||||
}
|
||||
tt := []struct {
|
||||
name string
|
||||
level zerolog.Level
|
||||
verboseLevel zerolog.Level
|
||||
filter string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "verbose mode simple case",
|
||||
level: zerolog.WarnLevel,
|
||||
verboseLevel: zerolog.DebugLevel,
|
||||
expected: `* WRN msg 2 module=foo
|
||||
* ERR msg 3 module=bar
|
||||
* ERR Start Verbose Mode
|
||||
* INF msg 1 module=foo
|
||||
* WRN msg 2 module=foo
|
||||
* ERR msg 3 module=bar
|
||||
* DBG msg 4 module=foo
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "verbose mode with filter",
|
||||
level: zerolog.WarnLevel,
|
||||
verboseLevel: zerolog.InfoLevel,
|
||||
filter: "foo:error",
|
||||
expected: `* ERR msg 3 module=bar
|
||||
* ERR Start Verbose Mode
|
||||
* INF msg 1 module=foo
|
||||
* WRN msg 2 module=foo
|
||||
* ERR msg 3 module=bar
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no verbose mode",
|
||||
level: zerolog.WarnLevel,
|
||||
verboseLevel: zerolog.NoLevel,
|
||||
expected: `* WRN msg 2 module=foo
|
||||
* ERR msg 3 module=bar
|
||||
* ERR Start Verbose Mode
|
||||
* WRN msg 2 module=foo
|
||||
* ERR msg 3 module=bar
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no verbose mode with filter",
|
||||
level: zerolog.WarnLevel,
|
||||
verboseLevel: zerolog.NoLevel,
|
||||
filter: "foo:error",
|
||||
expected: `* ERR msg 3 module=bar
|
||||
* ERR Start Verbose Mode
|
||||
* ERR msg 3 module=bar
|
||||
`,
|
||||
},
|
||||
}
|
||||
for i, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%d: %s", i, tc.name), func(t *testing.T) {
|
||||
out := new(bytes.Buffer)
|
||||
opts := []log.Option{
|
||||
log.LevelOption(tc.level),
|
||||
log.VerboseLevelOption(tc.verboseLevel),
|
||||
log.ColorOption(false),
|
||||
log.TimeFormatOption("*"), // disable non-deterministic time format
|
||||
}
|
||||
if tc.filter != "" {
|
||||
filter, err := log.ParseLogLevel(tc.filter)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse log level: %v", err)
|
||||
}
|
||||
opts = append(opts, log.FilterOption(filter))
|
||||
}
|
||||
logger := log.NewLogger(out, opts...)
|
||||
writeMsgs := func() {
|
||||
for _, msg := range logMessages {
|
||||
switch msg.level {
|
||||
case zerolog.InfoLevel:
|
||||
logger.Info(msg.message, log.ModuleKey, msg.module)
|
||||
case zerolog.WarnLevel:
|
||||
logger.Warn(msg.message, log.ModuleKey, msg.module)
|
||||
case zerolog.DebugLevel:
|
||||
logger.Debug(msg.message, log.ModuleKey, msg.module)
|
||||
case zerolog.ErrorLevel:
|
||||
logger.Error(msg.message, log.ModuleKey, msg.module)
|
||||
default:
|
||||
t.Fatalf("unexpected level: %v", msg.level)
|
||||
}
|
||||
}
|
||||
}
|
||||
writeMsgs()
|
||||
logger.Error("Start Verbose Mode")
|
||||
logger.(log.VerboseModeLogger).SetVerboseMode(true)
|
||||
writeMsgs()
|
||||
if tc.expected != out.String() {
|
||||
t.Fatalf("expected:\n%s\ngot:\n%s", tc.expected, out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+54
-9
@@ -62,6 +62,15 @@ type Logger interface {
|
||||
Impl() any
|
||||
}
|
||||
|
||||
// VerboseModeLogger is an extension interface of Logger which allows verbosity to be configured.
|
||||
type VerboseModeLogger interface {
|
||||
Logger
|
||||
// SetVerboseMode configures whether the logger enters verbose mode or not for
|
||||
// special operations where increased observability of log messages is desired
|
||||
// (such as chain upgrades).
|
||||
SetVerboseMode(bool)
|
||||
}
|
||||
|
||||
// WithJSONMarshal configures zerolog global json encoding.
|
||||
func WithJSONMarshal(marshaler func(v any) ([]byte, error)) {
|
||||
zerolog.InterfaceMarshalFunc = func(i any) ([]byte, error) {
|
||||
@@ -80,6 +89,11 @@ func WithJSONMarshal(marshaler func(v any) ([]byte, error)) {
|
||||
|
||||
type zeroLogWrapper struct {
|
||||
*zerolog.Logger
|
||||
regularLevel zerolog.Level
|
||||
verboseLevel zerolog.Level
|
||||
// this field is used to disable filtering during verbose logging
|
||||
// and will only be non-nil when we have a filterWriter
|
||||
filterWriter *filterWriter
|
||||
}
|
||||
|
||||
// NewLogger returns a new logger that writes to the given destination.
|
||||
@@ -105,8 +119,13 @@ func NewLogger(dst io.Writer, options ...Option) Logger {
|
||||
}
|
||||
}
|
||||
|
||||
var fltWtr *filterWriter
|
||||
if logCfg.Filter != nil {
|
||||
output = NewFilterWriter(output, logCfg.Filter)
|
||||
fltWtr = &filterWriter{
|
||||
parent: output,
|
||||
filter: logCfg.Filter,
|
||||
}
|
||||
output = fltWtr
|
||||
}
|
||||
|
||||
logger := zerolog.New(output)
|
||||
@@ -123,18 +142,25 @@ func NewLogger(dst io.Writer, options ...Option) Logger {
|
||||
logger = logger.With().Timestamp().Logger()
|
||||
}
|
||||
|
||||
if logCfg.Level != zerolog.NoLevel {
|
||||
logger = logger.Level(logCfg.Level)
|
||||
}
|
||||
|
||||
logger = logger.Level(logCfg.Level)
|
||||
logger = logger.Hook(logCfg.Hooks...)
|
||||
|
||||
return zeroLogWrapper{&logger}
|
||||
return zeroLogWrapper{
|
||||
Logger: &logger,
|
||||
regularLevel: logCfg.Level,
|
||||
verboseLevel: logCfg.VerboseLevel,
|
||||
filterWriter: fltWtr,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCustomLogger returns a new logger with the given zerolog logger.
|
||||
func NewCustomLogger(logger zerolog.Logger) Logger {
|
||||
return zeroLogWrapper{&logger}
|
||||
return zeroLogWrapper{
|
||||
Logger: &logger,
|
||||
regularLevel: logger.GetLevel(),
|
||||
verboseLevel: zerolog.NoLevel,
|
||||
filterWriter: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Info takes a message and a set of key/value pairs and logs with level INFO.
|
||||
@@ -164,13 +190,15 @@ func (l zeroLogWrapper) Debug(msg string, keyVals ...interface{}) {
|
||||
// With returns a new wrapped logger with additional context provided by a set.
|
||||
func (l zeroLogWrapper) With(keyVals ...interface{}) Logger {
|
||||
logger := l.Logger.With().Fields(keyVals).Logger()
|
||||
return zeroLogWrapper{&logger}
|
||||
l.Logger = &logger
|
||||
return l
|
||||
}
|
||||
|
||||
// WithContext returns a new wrapped logger with additional context provided by a set.
|
||||
func (l zeroLogWrapper) WithContext(keyVals ...interface{}) any {
|
||||
logger := l.Logger.With().Fields(keyVals).Logger()
|
||||
return zeroLogWrapper{&logger}
|
||||
l.Logger = &logger
|
||||
return l
|
||||
}
|
||||
|
||||
// Impl returns the underlying zerolog logger.
|
||||
@@ -179,6 +207,23 @@ func (l zeroLogWrapper) Impl() interface{} {
|
||||
return l.Logger
|
||||
}
|
||||
|
||||
// SetVerboseMode implements VerboseModeLogger interface.
|
||||
func (l zeroLogWrapper) SetVerboseMode(enable bool) {
|
||||
if enable && l.verboseLevel != zerolog.NoLevel {
|
||||
*l.Logger = l.Level(l.verboseLevel)
|
||||
if l.filterWriter != nil {
|
||||
l.filterWriter.disableFilter = true
|
||||
}
|
||||
} else {
|
||||
*l.Logger = l.Level(l.regularLevel)
|
||||
if l.filterWriter != nil {
|
||||
l.filterWriter.disableFilter = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ VerboseModeLogger = zeroLogWrapper{}
|
||||
|
||||
// NewNopLogger returns a new logger that does nothing.
|
||||
func NewNopLogger() Logger {
|
||||
// The custom nopLogger is about 3x faster than a zeroLogWrapper with zerolog.Nop().
|
||||
|
||||
+19
-2
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// defaultConfig has all the options disabled, except Color and TimeFormat
|
||||
var defaultConfig = Config{
|
||||
Level: zerolog.NoLevel,
|
||||
Level: zerolog.TraceLevel, // this is the default level that zerolog initializes new Logger's with
|
||||
Filter: nil,
|
||||
OutputJSON: false,
|
||||
Color: true,
|
||||
@@ -19,7 +19,16 @@ var defaultConfig = Config{
|
||||
|
||||
// Config defines configuration for the logger.
|
||||
type Config struct {
|
||||
Level zerolog.Level
|
||||
// Level is the default logging level.
|
||||
Level zerolog.Level
|
||||
// VerboseLevel is the logging level to use when verbose mode is enabled.
|
||||
// If there is a filter enabled, it will be disabled when verbose mode is enabled
|
||||
// and all log messages will be emitted at the VerboseLevel.
|
||||
// If this is set to NoLevel, then no changes to the logging level or filter will be made
|
||||
// when verbose mode is enabled.
|
||||
VerboseLevel zerolog.Level
|
||||
// Filter is the filter function to use that allows for filtering by key and level.
|
||||
// When verbose mode is enabled, the filter will be disabled unless VerboseLevel is set to NoLevel.
|
||||
Filter FilterFunc
|
||||
OutputJSON bool
|
||||
Color bool
|
||||
@@ -45,6 +54,14 @@ func LevelOption(level zerolog.Level) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// VerboseLevelOption sets the verbose level for the Logger.
|
||||
// When verbose mode is enabled, the logger will be switched to this level.
|
||||
func VerboseLevelOption(level zerolog.Level) Option {
|
||||
return func(cfg *Config) {
|
||||
cfg.VerboseLevel = level
|
||||
}
|
||||
}
|
||||
|
||||
// OutputJSONOption sets the output of the logger to JSON.
|
||||
// By default, the logger outputs to a human-readable format.
|
||||
func OutputJSONOption() Option {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// this test ensures that when the With and WithContext methods are called,
|
||||
// that the log wrapper is properly copied with all of its associated options
|
||||
// otherwise, verbose mode will fail
|
||||
func TestLoggerWith(t *testing.T) {
|
||||
logger := zerolog.New(&bytes.Buffer{})
|
||||
regularLevel := zerolog.WarnLevel
|
||||
verboseLevel := zerolog.InfoLevel
|
||||
filterWriter := &filterWriter{}
|
||||
wrapper := zeroLogWrapper{
|
||||
Logger: &logger,
|
||||
regularLevel: regularLevel,
|
||||
verboseLevel: verboseLevel,
|
||||
filterWriter: filterWriter,
|
||||
}
|
||||
|
||||
wrapper2 := wrapper.With("x", "y").(zeroLogWrapper)
|
||||
if wrapper2.filterWriter != filterWriter {
|
||||
t.Fatalf("expected filterWriter to be copied, but it was not")
|
||||
}
|
||||
if wrapper2.regularLevel != regularLevel {
|
||||
t.Fatalf("expected regularLevel to be copied, but it was not")
|
||||
}
|
||||
if wrapper2.verboseLevel != verboseLevel {
|
||||
t.Fatalf("expected verboseLevel to be copied, but it was not")
|
||||
}
|
||||
|
||||
wrapper3 := wrapper.WithContext("a", "b").(zeroLogWrapper)
|
||||
if wrapper3.filterWriter != filterWriter {
|
||||
t.Fatalf("expected filterWriter to be copied, but it was not")
|
||||
}
|
||||
if wrapper3.regularLevel != regularLevel {
|
||||
t.Fatalf("expected regularLevel to be copied, but it was not")
|
||||
}
|
||||
if wrapper3.verboseLevel != verboseLevel {
|
||||
t.Fatalf("expected verboseLevel to be copied, but it was not")
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -11,16 +11,17 @@ import (
|
||||
// If the filter is nil, the writer will pass all events through.
|
||||
// The filter function is called with the module and level of the event.
|
||||
func NewFilterWriter(parent io.Writer, filter FilterFunc) io.Writer {
|
||||
return &filterWriter{parent, filter}
|
||||
return &filterWriter{parent: parent, filter: filter}
|
||||
}
|
||||
|
||||
type filterWriter struct {
|
||||
parent io.Writer
|
||||
filter FilterFunc
|
||||
parent io.Writer
|
||||
filter FilterFunc
|
||||
disableFilter bool
|
||||
}
|
||||
|
||||
func (fw *filterWriter) Write(p []byte) (n int, err error) {
|
||||
if fw.filter == nil {
|
||||
if fw.filter == nil || fw.disableFilter {
|
||||
return fw.parent.Write(p)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user