| // Copyright 2018 Solly Ross |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // package zapr defines an implementation of the github.com/go-logr/logr |
| // interfaces built on top of Zap (go.uber.org/zap). |
| // |
| // Usage |
| // |
| // A new logr.Logger can be constructed from an existing zap.Logger using |
| // the NewLogger function: |
| // |
| // log := zapr.NewLogger(someZapLogger) |
| // |
| // Implementation Details |
| // |
| // For the most part, concepts in Zap correspond directly with those in |
| // logr. |
| // |
| // Unlike Zap, all fields *must* be in the form of suggared fields -- |
| // it's illegal to pass a strongly-typed Zap field in a key position |
| // to any of the log methods. |
| // |
| // Levels in logr correspond to custom debug levels in Zap. Any given level |
| // in logr is represents by its inverse in zap (`zapLevel = -1*logrLevel`). |
| // For example V(2) is equivalent to log level -2 in Zap, while V(1) is |
| // equivalent to Zap's DebugLevel. |
| package zapr |
| |
| import ( |
| "github.com/go-logr/logr" |
| "go.uber.org/zap" |
| "go.uber.org/zap/zapcore" |
| ) |
| |
| // noopInfoLogger is a logr.InfoLogger that's always disabled, and does nothing. |
| type noopInfoLogger struct{} |
| |
| func (l *noopInfoLogger) Enabled() bool { return false } |
| func (l *noopInfoLogger) Info(_ string, _ ...interface{}) {} |
| |
| var disabledInfoLogger = &noopInfoLogger{} |
| |
| // NB: right now, we always use the equivalent of sugared logging. |
| // This is necessary, since logr doesn't define non-suggared types, |
| // and using zap-specific non-suggared types would make uses tied |
| // directly to Zap. |
| |
| // infoLogger is a logr.InfoLogger that uses Zap to log at a particular |
| // level. The level has already been converted to a Zap level, which |
| // is to say that `logrLevel = -1*zapLevel`. |
| type infoLogger struct { |
| lvl zapcore.Level |
| l *zap.Logger |
| } |
| |
| func (l *infoLogger) Enabled() bool { return true } |
| func (l *infoLogger) Info(msg string, keysAndVals ...interface{}) { |
| if checkedEntry := l.l.Check(l.lvl, msg); checkedEntry != nil { |
| checkedEntry.Write(handleFields(l.l, keysAndVals)...) |
| } |
| } |
| |
| // zapLogger is a logr.Logger that uses Zap to log. |
| type zapLogger struct { |
| // NB: this looks very similar to zap.SugaredLogger, but |
| // deals with our desire to have multiple verbosity levels. |
| l *zap.Logger |
| infoLogger |
| } |
| |
| // handleFields converts a bunch of arbitrary key-value pairs into Zap fields. It takes |
| // additional pre-converted Zap fields, for use with automatically attached fields, like |
| // `error`. |
| func handleFields(l *zap.Logger, args []interface{}, additional ...zap.Field) []zap.Field { |
| // a slightly modified version of zap.SugaredLogger.sweetenFields |
| if len(args) == 0 { |
| // fast-return if we have no suggared fields. |
| return additional |
| } |
| |
| // unlike Zap, we can be pretty sure users aren't passing structured |
| // fields (since logr has no concept of that), so guess that we need a |
| // little less space. |
| fields := make([]zap.Field, 0, len(args)/2+len(additional)) |
| for i := 0; i < len(args); { |
| // check just in case for strongly-typed Zap fields, which is illegal (since |
| // it breaks implementation agnosticism), so we can give a better error message. |
| if _, ok := args[i].(zap.Field); ok { |
| l.DPanic("strongly-typed Zap Field passed to logr", zap.Any("zap field", args[i])) |
| break |
| } |
| |
| // make sure this isn't a mismatched key |
| if i == len(args)-1 { |
| l.DPanic("odd number of arguments passed as key-value pairs for logging", zap.Any("ignored key", args[i])) |
| break |
| } |
| |
| // process a key-value pair, |
| // ensuring that the key is a string |
| key, val := args[i], args[i+1] |
| keyStr, isString := key.(string) |
| if !isString { |
| // if the key isn't a string, DPanic and stop logging |
| l.DPanic("non-string key argument passed to logging, ignoring all later arguments", zap.Any("invalid key", key)) |
| break |
| } |
| |
| fields = append(fields, zap.Any(keyStr, val)) |
| i += 2 |
| } |
| |
| return append(fields, additional...) |
| } |
| |
| func (l *zapLogger) Error(err error, msg string, keysAndVals ...interface{}) { |
| if checkedEntry := l.l.Check(zap.ErrorLevel, msg); checkedEntry != nil { |
| checkedEntry.Write(handleFields(l.l, keysAndVals, zap.Error(err))...) |
| } |
| } |
| |
| func (l *zapLogger) V(level int) logr.InfoLogger { |
| lvl := zapcore.Level(-1 * level) |
| if l.l.Core().Enabled(lvl) { |
| return &infoLogger{ |
| lvl: lvl, |
| l: l.l, |
| } |
| } |
| return disabledInfoLogger |
| } |
| |
| func (l *zapLogger) WithValues(keysAndValues ...interface{}) logr.Logger { |
| newLogger := l.l.With(handleFields(l.l, keysAndValues)...) |
| return NewLogger(newLogger) |
| } |
| |
| func (l *zapLogger) WithName(name string) logr.Logger { |
| newLogger := l.l.Named(name) |
| return NewLogger(newLogger) |
| } |
| |
| // NewLogger creates a new logr.Logger using the given Zap Logger to log. |
| func NewLogger(l *zap.Logger) logr.Logger { |
| return &zapLogger{ |
| l: l, |
| infoLogger: infoLogger{ |
| l: l, |
| lvl: zap.InfoLevel, |
| }, |
| } |
| } |