github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/zlog/logr.go (about) 1 package zlog 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "github.com/go-logr/logr" 8 "go.uber.org/zap" 9 "go.uber.org/zap/zapcore" 10 ) 11 12 var _ logr.LogSink = (*logrImpl)(nil) 13 var _ logr.CallDepthLogSink = (*logrImpl)(nil) 14 15 // NewLogrLogger creates a new logr.Logger. 16 func NewLogrLogger(options ...func(*LogrOptions)) logr.Logger { 17 opts := newLogrOptions(options) 18 l := opts.Logger.WithOptions(zap.AddCallerSkip(1)) 19 sink := &logrImpl{opts: opts, l: l} 20 return logr.New(sink) 21 } 22 23 func newLogrOptions(options []func(*LogrOptions)) *LogrOptions { 24 opts := &LogrOptions{ 25 DPanicOnInvalidLog: true, 26 } 27 for _, f := range options { 28 f(opts) 29 } 30 // Set defaults. 31 if opts.ErrorKey == "" { 32 opts.ErrorKey = "error" 33 } 34 if opts.Logger == nil { 35 opts.Logger = L().Logger 36 } 37 return opts 38 } 39 40 // LogrOptions customizes the behavior of logr logger created by NewLogrLogger. 41 type LogrOptions struct { 42 // Logger optionally configures a zap.Logger to use instead of 43 // the default logger. 44 Logger *zap.Logger 45 46 // ErrorKey replaces the default "error" field name used for the error 47 // in Logger.Error calls. 48 ErrorKey string 49 50 // NumericLevelKey controls whether the numeric logr level is 51 // added to each Info log message and the field key to use. 52 NumericLevelKey string 53 54 // DPanicOnInvalidLog controls whether extra log messages are emitted 55 // for invalid log calls with zap's DPanic method. 56 // Depending on the configuration of the zap logger, the program then 57 // panics after emitting the log message which is useful in development 58 // because such invalid log calls are bugs in the program. 59 // The log messages explain why a call was invalid (for example, 60 // non-string key, mismatched key-values pairs, etc.). 61 // This is enabled by default. 62 DPanicOnInvalidLog bool 63 } 64 65 type logrImpl struct { 66 opts *LogrOptions 67 l *zap.Logger 68 } 69 70 func (r *logrImpl) Init(ri logr.RuntimeInfo) { 71 r.l = r.l.WithOptions(zap.AddCallerSkip(ri.CallDepth)) 72 } 73 74 const ( 75 // noLevel tells handleFields to not inject a numeric log level field. 76 noLevel = -1 77 ) 78 79 // handleFields is a slightly modified version of zap.SugaredLogger.sweetenFields. 80 func (r *logrImpl) handleFields(lv int, args []any, additional ...zap.Field) []zap.Field { 81 injectNumLevel := r.opts.NumericLevelKey != "" && lv != noLevel 82 83 if len(args) == 0 { 84 // fast-return if we have no sugared fields and no "v" field 85 if !injectNumLevel { 86 return additional 87 } 88 return append(additional, zap.Int(r.opts.NumericLevelKey, lv)) 89 } 90 91 // Unlike Zap, we can be pretty sure users aren't passing structured. 92 // fields (since logr has no concept of that), so guess that we need a 93 // little less space. 94 numFields := len(args)/2 + len(additional) 95 if injectNumLevel { 96 numFields++ 97 } 98 fields := make([]zap.Field, 0, numFields) 99 if injectNumLevel { 100 fields = append(fields, zap.Int(r.opts.NumericLevelKey, lv)) 101 } 102 for i := 0; i < len(args); { 103 // Check just in case for strongly-typed Zap fields, 104 // which might be illegal (since it breaks implementation agnosticism). 105 // If disabled, we can give a better error message. 106 if f, ok := args[i].(zap.Field); ok { 107 fields = append(fields, f) 108 i++ 109 continue 110 } 111 112 // Make sure this isn't a mismatched key. 113 if i == len(args)-1 { 114 if r.opts.DPanicOnInvalidLog { 115 r.l.WithOptions(zap.AddCallerSkip(1)). 116 DPanic("odd number of arguments passed as key-value pairs for logging", toZapField("ignoredKey", args[i])) 117 } 118 break 119 } 120 121 // Process a key-value pair, ensuring that the key is a string. 122 // If the key isn't a string, DPanic and stop checking the later arguments. 123 key, val := args[i], args[i+1] 124 rvKey := reflect.ValueOf(key) 125 if rvKey.Kind() != reflect.String { 126 if r.opts.DPanicOnInvalidLog { 127 r.l.WithOptions(zap.AddCallerSkip(1)). 128 DPanic("non-string key passed to logging, ignoring all later arguments", toZapField("invalidKey", key)) 129 } 130 break 131 } 132 133 keyStr := rvKey.String() 134 fields = append(fields, toZapField(keyStr, val)) 135 i += 2 136 } 137 138 return append(fields, additional...) 139 } 140 141 func toZapField(field string, val any) zap.Field { 142 // Handle types that implement logr.Marshaler: log the replacement 143 // object instead of the original one. 144 if m, ok := val.(logr.Marshaler); ok { 145 field, val = invokeLogrMarshaler(field, m) 146 } 147 return zap.Any(field, val) 148 } 149 150 func invokeLogrMarshaler(field string, m logr.Marshaler) (f string, ret any) { 151 defer func() { 152 if r := recover(); r != nil { 153 ret = fmt.Sprintf("PANIC=%s", r) 154 f = field + "Error" 155 } 156 }() 157 return field, m.MarshalLog() 158 } 159 160 // Zap levels are int8, make sure we stay in bounds. 161 // logr itself should ensure we never get negative values. 162 func logrToZapLevel(lv int) zapcore.Level { 163 if lv > 127 { 164 lv = 127 165 } 166 return 0 - zapcore.Level(lv) 167 } 168 169 func (r *logrImpl) Enabled(lv int) bool { 170 return r.l.Core().Enabled(logrToZapLevel(lv)) 171 } 172 173 func (r *logrImpl) Info(lv int, msg string, keyAndVals ...any) { 174 if ce := r.l.Check(logrToZapLevel(lv), msg); ce != nil { 175 ce.Write(r.handleFields(lv, keyAndVals)...) 176 } 177 } 178 179 func (r *logrImpl) Error(err error, msg string, keyAndVals ...any) { 180 if ce := r.l.Check(zap.ErrorLevel, msg); ce != nil { 181 ce.Write(r.handleFields(noLevel, keyAndVals, zap.NamedError(r.opts.ErrorKey, err))...) 182 } 183 } 184 185 func (r *logrImpl) WithValues(keyAndValues ...any) logr.LogSink { 186 clone := *r 187 clone.l = r.l.With(r.handleFields(noLevel, keyAndValues)...) 188 return &clone 189 } 190 191 func (r *logrImpl) WithName(name string) logr.LogSink { 192 clone := *r 193 clone.l = r.l.Named(name) 194 return &clone 195 } 196 197 func (r *logrImpl) WithCallDepth(depth int) logr.LogSink { 198 clone := *r 199 clone.l = r.l.WithOptions(zap.AddCallerSkip(depth)) 200 return &clone 201 } 202 203 // Underlier exposes access to the underlying logging implementation. 204 // Since callers only have a logr.Logger, they have to know which 205 // implementation is in use, so this interface is less of an abstraction 206 // and more of way to test type conversion. 207 type Underlier interface { 208 GetUnderlying() *zap.Logger 209 } 210 211 func (r *logrImpl) GetUnderlying() *zap.Logger { 212 return r.l 213 }