cosmos-sdk/log/logger.go
Aaron Craelius 5c90246b3f
feat(log): remove core dependency and update core interface to be dependency free (#21045)
Co-authored-by: marbar3778 <marbar3778@yahoo.com>
Co-authored-by: Julien Robert <julien@rbrt.fr>
2024-07-26 11:00:27 +00:00

199 lines
5.9 KiB
Go

package log
import (
"encoding"
"encoding/json"
"fmt"
"io"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/pkgerrors"
)
func init() {
zerolog.InterfaceMarshalFunc = func(i any) ([]byte, error) {
switch v := i.(type) {
case json.Marshaler:
return json.Marshal(i)
case encoding.TextMarshaler:
return json.Marshal(i)
case fmt.Stringer:
return json.Marshal(v.String())
default:
return json.Marshal(i)
}
}
}
// ModuleKey defines a module logging key.
const ModuleKey = "module"
// ContextKey is used to store the logger in the context.
var ContextKey struct{}
// Logger is the Cosmos SDK logger interface.
// It extends cosmossdk.io/core/log.Logger to return a child logger.
// Use cosmossdk.io/core/log.Logger instead in modules.
type Logger interface {
// Info takes a message and a set of key/value pairs and logs with level INFO.
// The key of the tuple must be a string.
Info(msg string, keyVals ...any)
// Warn takes a message and a set of key/value pairs and logs with level WARN.
// The key of the tuple must be a string.
Warn(msg string, keyVals ...any)
// Error takes a message and a set of key/value pairs and logs with level ERR.
// The key of the tuple must be a string.
Error(msg string, keyVals ...any)
// Debug takes a message and a set of key/value pairs and logs with level DEBUG.
// The key of the tuple must be a string.
Debug(msg string, keyVals ...any)
// With returns a new wrapped logger with additional context provided by a set.
With(keyVals ...any) Logger
// Impl returns the underlying logger implementation.
// It is used to access the full functionalities of the underlying logger.
// Advanced users can type cast the returned value to the actual logger.
Impl() any
}
// WithJSONMarshal configures zerolog global json encoding.
func WithJSONMarshal(marshaler func(v any) ([]byte, error)) {
zerolog.InterfaceMarshalFunc = func(i any) ([]byte, error) {
switch v := i.(type) {
case json.Marshaler:
return marshaler(i)
case encoding.TextMarshaler:
return marshaler(i)
case fmt.Stringer:
return marshaler(v.String())
default:
return marshaler(i)
}
}
}
type zeroLogWrapper struct {
*zerolog.Logger
}
// NewLogger returns a new logger that writes to the given destination.
//
// Typical usage from a main function is:
//
// logger := log.NewLogger(os.Stderr)
//
// Stderr is the typical destination for logs,
// so that any output from your application can still be piped to other processes.
// The returned value can be safely cast to cosmossdk.io/core/log.Logger.
func NewLogger(dst io.Writer, options ...Option) Logger {
logCfg := defaultConfig
for _, opt := range options {
opt(&logCfg)
}
output := dst
if !logCfg.OutputJSON {
output = zerolog.ConsoleWriter{
Out: dst,
NoColor: !logCfg.Color,
TimeFormat: logCfg.TimeFormat,
}
}
if logCfg.Filter != nil {
output = NewFilterWriter(output, logCfg.Filter)
}
logger := zerolog.New(output)
if logCfg.StackTrace {
zerolog.ErrorStackMarshaler = func(err error) interface{} {
return pkgerrors.MarshalStack(errors.WithStack(err))
}
logger = logger.With().Stack().Logger()
}
if logCfg.TimeFormat != "" {
logger = logger.With().Timestamp().Logger()
}
if logCfg.Level != zerolog.NoLevel {
logger = logger.Level(logCfg.Level)
}
logger = logger.Hook(logCfg.Hooks...)
return zeroLogWrapper{&logger}
}
// NewCustomLogger returns a new logger with the given zerolog logger.
func NewCustomLogger(logger zerolog.Logger) Logger {
return zeroLogWrapper{&logger}
}
// Info takes a message and a set of key/value pairs and logs with level INFO.
// The key of the tuple must be a string.
func (l zeroLogWrapper) Info(msg string, keyVals ...interface{}) {
l.Logger.Info().Fields(keyVals).Msg(msg)
}
// Warn takes a message and a set of key/value pairs and logs with level WARN.
// The key of the tuple must be a string.
func (l zeroLogWrapper) Warn(msg string, keyVals ...interface{}) {
l.Logger.Warn().Fields(keyVals).Msg(msg)
}
// Error takes a message and a set of key/value pairs and logs with level ERROR.
// The key of the tuple must be a string.
func (l zeroLogWrapper) Error(msg string, keyVals ...interface{}) {
l.Logger.Error().Fields(keyVals).Msg(msg)
}
// Debug takes a message and a set of key/value pairs and logs with level DEBUG.
// The key of the tuple must be a string.
func (l zeroLogWrapper) Debug(msg string, keyVals ...interface{}) {
l.Logger.Debug().Fields(keyVals).Msg(msg)
}
// 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}
}
// 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}
}
// Impl returns the underlying zerolog logger.
// It can be used to used zerolog structured API directly instead of the wrapper.
func (l zeroLogWrapper) Impl() interface{} {
return l.Logger
}
// NewNopLogger returns a new logger that does nothing.
func NewNopLogger() Logger {
// The custom nopLogger is about 3x faster than a zeroLogWrapper with zerolog.Nop().
return nopLogger{}
}
// nopLogger is a Logger that does nothing when called.
// See the "specialized nop logger" benchmark and compare with the "zerolog nop logger" benchmark.
// The custom implementation is about 3x faster.
type nopLogger struct{}
func (nopLogger) Info(string, ...any) {}
func (nopLogger) Warn(string, ...any) {}
func (nopLogger) Error(string, ...any) {}
func (nopLogger) Debug(string, ...any) {}
func (nopLogger) With(...any) Logger { return nopLogger{} }
func (nopLogger) WithContext(...any) any { return nopLogger{} }
func (nopLogger) Impl() any { return nopLogger{} }