166 lines
4.7 KiB
Go
166 lines
4.7 KiB
Go
// Package zerologr defines an implementation of the github.com/go-logr/logr
|
|
// interfaces built on top of Zerolog (https://github.com/rs/zerolog).
|
|
//
|
|
// Usage
|
|
//
|
|
// A new logr.Logger can be constructed from an existing zerolog.Logger using
|
|
// the New function:
|
|
//
|
|
// log := zerologr.New(someZeroLogger)
|
|
//
|
|
// Implementation Details
|
|
//
|
|
// For the most part, concepts in Zerolog correspond directly with those in
|
|
// logr.
|
|
//
|
|
// Levels in logr correspond to custom debug levels in Zerolog. Any given level
|
|
// in logr is represents by `zerologLevel = 1 - logrLevel`.
|
|
// For example V(2) is equivalent to Zerolog's TraceLevel, while V(1) is
|
|
// equivalent to Zerolog's DebugLevel. Verbosity value is a number and is only
|
|
// logged on Info(), not Error().
|
|
package zerologr
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/go-logr/logr"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
var (
|
|
// NameFieldName is the field key for logr.WithName.
|
|
NameFieldName = "logger"
|
|
// NameSeparator separates names for logr.WithName.
|
|
NameSeparator = "/"
|
|
// VerbosityFieldName is the field key for logr.Info verbosity. If set to "",
|
|
// its value is not emitted.
|
|
VerbosityFieldName = "v"
|
|
|
|
// RenderArgsHook mutates the list of key-value pairs passed directly to
|
|
// logr.Info and logr.Error.
|
|
RenderArgsHook = DefaultRender
|
|
// RenderValuesHook mutates the list of key-value pairs saved via logr.WithValues.
|
|
RenderValuesHook = DefaultRender
|
|
)
|
|
|
|
// Logger is type alias of logr.Logger.
|
|
type Logger = logr.Logger
|
|
|
|
// LogSink implements logr.LogSink and logr.CallDepthLogSink.
|
|
type LogSink struct {
|
|
l *zerolog.Logger
|
|
name string
|
|
depth int
|
|
}
|
|
|
|
// Underlier exposes access to the underlying logging implementation. Since
|
|
// callers only have a logr.Logger, they have to know which implementation is
|
|
// in use, so this interface is less of an abstraction and more of way to test
|
|
// type conversion.
|
|
type Underlier interface {
|
|
GetUnderlying() *zerolog.Logger
|
|
}
|
|
|
|
var (
|
|
_ logr.LogSink = &LogSink{}
|
|
_ logr.CallDepthLogSink = &LogSink{}
|
|
)
|
|
|
|
// New returns a logr.Logger with logr.LogSink implemented by Zerolog.
|
|
func New(l *zerolog.Logger) Logger {
|
|
ls := NewLogSink(l)
|
|
return logr.New(ls)
|
|
}
|
|
|
|
// NewLogSink returns a logr.LogSink implemented by Zerolog.
|
|
func NewLogSink(l *zerolog.Logger) *LogSink {
|
|
return &LogSink{l: l}
|
|
}
|
|
|
|
// Init receives runtime info about the logr library.
|
|
func (ls *LogSink) Init(ri logr.RuntimeInfo) {
|
|
ls.depth = ri.CallDepth + 2
|
|
}
|
|
|
|
// Enabled tests whether this LogSink is enabled at the specified V-level.
|
|
func (ls *LogSink) Enabled(level int) bool {
|
|
// Optimization: Info() will check level internally.
|
|
const traceLevel = 1 - int(zerolog.TraceLevel)
|
|
return level <= traceLevel
|
|
}
|
|
|
|
// Info logs a non-error message at specified V-level with the given key/value pairs as context.
|
|
func (ls *LogSink) Info(level int, msg string, keysAndValues ...interface{}) {
|
|
e := ls.l.WithLevel(zerolog.Level(1 - level))
|
|
if VerbosityFieldName != "" {
|
|
e.Int(VerbosityFieldName, level)
|
|
}
|
|
ls.msg(e, msg, keysAndValues)
|
|
}
|
|
|
|
// Error logs an error, with the given message and key/value pairs as context.
|
|
func (ls *LogSink) Error(err error, msg string, keysAndValues ...interface{}) {
|
|
e := ls.l.Error().Err(err)
|
|
ls.msg(e, msg, keysAndValues)
|
|
}
|
|
|
|
func (ls *LogSink) msg(e *zerolog.Event, msg string, keysAndValues []interface{}) {
|
|
if e == nil {
|
|
return
|
|
}
|
|
if ls.name != "" {
|
|
e.Str(NameFieldName, ls.name)
|
|
}
|
|
if RenderArgsHook != nil {
|
|
keysAndValues = RenderArgsHook(keysAndValues)
|
|
}
|
|
e = e.Fields(keysAndValues)
|
|
e.CallerSkipFrame(ls.depth)
|
|
e.Msg(msg)
|
|
}
|
|
|
|
// WithValues returns a new LogSink with additional key/value pairs.
|
|
func (ls LogSink) WithValues(keysAndValues ...interface{}) logr.LogSink {
|
|
if RenderValuesHook != nil {
|
|
keysAndValues = RenderValuesHook(keysAndValues)
|
|
}
|
|
l := ls.l.With().Fields(keysAndValues).Logger()
|
|
ls.l = &l
|
|
return &ls
|
|
}
|
|
|
|
// WithName returns a new LogSink with the specified name appended in NameFieldName.
|
|
// Name elements are separated by NameSeparator.
|
|
func (ls LogSink) WithName(name string) logr.LogSink {
|
|
if ls.name != "" {
|
|
ls.name += NameSeparator + name
|
|
} else {
|
|
ls.name = name
|
|
}
|
|
return &ls
|
|
}
|
|
|
|
// WithCallDepth returns a new LogSink that offsets the call stack by adding specified depths.
|
|
func (ls LogSink) WithCallDepth(depth int) logr.LogSink {
|
|
ls.depth += depth
|
|
return &ls
|
|
}
|
|
|
|
// GetUnderlying returns the zerolog.Logger underneath this logSink.
|
|
func (ls *LogSink) GetUnderlying() *zerolog.Logger {
|
|
return ls.l
|
|
}
|
|
|
|
// DefaultRender supports logr.Marshaler and fmt.Stringer.
|
|
func DefaultRender(keysAndValues []interface{}) []interface{} {
|
|
for i, n := 1, len(keysAndValues); i < n; i += 2 {
|
|
value := keysAndValues[i]
|
|
switch v := value.(type) {
|
|
case logr.Marshaler:
|
|
keysAndValues[i] = v.MarshalLog()
|
|
case fmt.Stringer:
|
|
keysAndValues[i] = v.String()
|
|
}
|
|
}
|
|
return keysAndValues
|
|
}
|