1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
// 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.
//
// V-levels in logr correspond to levels in Zerolog as `zerologLevel = 1 - logrV`.
// `logr.V(0)` is equivalent to `zerolog.InfoLevel` or 1; `logr.V(1)` is equivalent to
// `zerolog.DebugLevel` or 0 (default global level in Zerolog); `logr.V(2)` is equivalent
// to `zerolog.TraceLevel` or -1. Higher than 2 V-level is possible but misses some
// features in Zerolog, e.g. Hooks and Sampling. V-level 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. If set to nil, it is disabled.
RenderArgsHook = DefaultRender
// RenderValuesHook mutates the list of key-value pairs saved via logr.WithValues.
// If set to nil, it is disabled.
RenderValuesHook = DefaultRender
)
const (
minZerologLevel = -128 // zerolog.Level is int8
)
// 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. Local level
// is mutated to allow max V-level if not set explicitly, so SetMaxV alone can control
// Zerolog's level. Use NewLogSink directly if local level mutation is undesirable.
func New(l *zerolog.Logger) Logger {
if l.GetLevel() == zerolog.TraceLevel {
ll := l.Level(minZerologLevel)
l = &ll
}
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}
}
// SetMaxV updates Zerolog's global level. Default max V-level is 1 (DebugLevel).
// The range of max V-level is 0 through 129 inclusive, but higher than 2 V-level
// misses some features in Zerolog, e.g. Hooks and Sampling.
func SetMaxV(level int) {
if level < 0 {
level = 0
}
zlvl := 1 - level
if zlvl < minZerologLevel {
zlvl = minZerologLevel
}
zerolog.SetGlobalLevel(zerolog.Level(zlvl))
}
// 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 {
zlvl := zerolog.Level(1 - level)
return zlvl >= ls.l.GetLevel() && zlvl >= zerolog.GlobalLevel()
}
// 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
}
|