1 // Copyright 2018 Solly Ross
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 // package zapr defines an implementation of the github.com/go-logr/logr
16 // interfaces built on top of Zap (go.uber.org/zap).
20 // A new logr.Logger can be constructed from an existing zap.Logger using
21 // the NewLogger function:
23 // log := zapr.NewLogger(someZapLogger)
25 // Implementation Details
27 // For the most part, concepts in Zap correspond directly with those in
30 // Unlike Zap, all fields *must* be in the form of suggared fields --
31 // it's illegal to pass a strongly-typed Zap field in a key position
32 // to any of the log methods.
34 // Levels in logr correspond to custom debug levels in Zap. Any given level
35 // in logr is represents by its inverse in zap (`zapLevel = -1*logrLevel`).
36 // For example V(2) is equivalent to log level -2 in Zap, while V(1) is
37 // equivalent to Zap's DebugLevel.
41 "github.com/go-logr/logr"
43 "go.uber.org/zap/zapcore"
46 // noopInfoLogger is a logr.InfoLogger that's always disabled, and does nothing.
47 type noopInfoLogger struct{}
49 func (l *noopInfoLogger) Enabled() bool { return false }
50 func (l *noopInfoLogger) Info(_ string, _ ...interface{}) {}
52 var disabledInfoLogger = &noopInfoLogger{}
54 // NB: right now, we always use the equivalent of sugared logging.
55 // This is necessary, since logr doesn't define non-suggared types,
56 // and using zap-specific non-suggared types would make uses tied
59 // infoLogger is a logr.InfoLogger that uses Zap to log at a particular
60 // level. The level has already been converted to a Zap level, which
61 // is to say that `logrLevel = -1*zapLevel`.
62 type infoLogger struct {
67 func (l *infoLogger) Enabled() bool { return true }
68 func (l *infoLogger) Info(msg string, keysAndVals ...interface{}) {
69 if checkedEntry := l.l.Check(l.lvl, msg); checkedEntry != nil {
70 checkedEntry.Write(handleFields(l.l, keysAndVals)...)
74 // zapLogger is a logr.Logger that uses Zap to log.
75 type zapLogger struct {
76 // NB: this looks very similar to zap.SugaredLogger, but
77 // deals with our desire to have multiple verbosity levels.
82 // handleFields converts a bunch of arbitrary key-value pairs into Zap fields. It takes
83 // additional pre-converted Zap fields, for use with automatically attached fields, like
85 func handleFields(l *zap.Logger, args []interface{}, additional ...zap.Field) []zap.Field {
86 // a slightly modified version of zap.SugaredLogger.sweetenFields
88 // fast-return if we have no suggared fields.
92 // unlike Zap, we can be pretty sure users aren't passing structured
93 // fields (since logr has no concept of that), so guess that we need a
95 fields := make([]zap.Field, 0, len(args)/2+len(additional))
96 for i := 0; i < len(args); {
97 // check just in case for strongly-typed Zap fields, which is illegal (since
98 // it breaks implementation agnosticism), so we can give a better error message.
99 if _, ok := args[i].(zap.Field); ok {
100 l.DPanic("strongly-typed Zap Field passed to logr", zap.Any("zap field", args[i]))
104 // make sure this isn't a mismatched key
105 if i == len(args)-1 {
106 l.DPanic("odd number of arguments passed as key-value pairs for logging", zap.Any("ignored key", args[i]))
110 // process a key-value pair,
111 // ensuring that the key is a string
112 key, val := args[i], args[i+1]
113 keyStr, isString := key.(string)
115 // if the key isn't a string, DPanic and stop logging
116 l.DPanic("non-string key argument passed to logging, ignoring all later arguments", zap.Any("invalid key", key))
120 fields = append(fields, zap.Any(keyStr, val))
124 return append(fields, additional...)
127 func (l *zapLogger) Error(err error, msg string, keysAndVals ...interface{}) {
128 if checkedEntry := l.l.Check(zap.ErrorLevel, msg); checkedEntry != nil {
129 checkedEntry.Write(handleFields(l.l, keysAndVals, zap.Error(err))...)
133 func (l *zapLogger) V(level int) logr.InfoLogger {
134 lvl := zapcore.Level(-1 * level)
135 if l.l.Core().Enabled(lvl) {
141 return disabledInfoLogger
144 func (l *zapLogger) WithValues(keysAndValues ...interface{}) logr.Logger {
145 newLogger := l.l.With(handleFields(l.l, keysAndValues)...)
146 return NewLogger(newLogger)
149 func (l *zapLogger) WithName(name string) logr.Logger {
150 newLogger := l.l.Named(name)
151 return NewLogger(newLogger)
154 // NewLogger creates a new logr.Logger using the given Zap Logger to log.
155 func NewLogger(l *zap.Logger) logr.Logger {
158 infoLogger: infoLogger{