163 lines
5.2 KiB
Go
163 lines
5.2 KiB
Go
// 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,
|
|
},
|
|
}
|
|
}
|