Files
opencloud/vendor/github.com/bombsimon/logrusr/v3/logrusr.go
2023-04-19 20:24:34 +02:00

238 lines
7.0 KiB
Go

package logrusr
import (
"encoding/json"
"fmt"
"path/filepath"
"runtime"
"strings"
"github.com/go-logr/logr"
"github.com/sirupsen/logrus"
)
// According to the specification of the Logger interface calling the InfoLogger
// directly on the logger should be the same as calling them on V(0). Since
// logrus level 0 is PanicLevel and Infolevel doesn't start until V(4) we use
// this constant to be able to calculate what V(n) values should mean.
const logrusDiffToInfo = 4
// FormatFunc is the function to format log values with for non primitive data.
// By default, this is empty and the data will be JSON marshaled.
type FormatFunc func(interface{}) string
// Option is options to give when construction a logrusr logger.
type Option func(l *logrusr)
// WithFormatter will set the FormatFunc to use.
func WithFormatter(f FormatFunc) Option {
return func(l *logrusr) {
l.defaultFormatter = f
}
}
// WithReportCaller will enable reporting of the caller.
func WithReportCaller() Option {
return func(l *logrusr) {
l.reportCaller = true
}
}
// WithName will set an initial name instead of having to call `WithName` on the
// logger itself after constructing it.
func WithName(name ...string) Option {
return func(l *logrusr) {
l.name = name
l.logger = l.logger.WithField(
"logger", strings.Join(l.name, "."),
)
}
}
type logrusr struct {
name []string
depth int
reportCaller bool
logger *logrus.Entry
defaultFormatter FormatFunc
}
// New will return a new logr.Logger created from a logrus.FieldLogger.
func New(l logrus.FieldLogger, opts ...Option) logr.Logger {
// Immediately convert the FieldLogger to an Entry so we don't have to type
// cast and can use methods that exist on the Entry but not the FieldLogger
// interface.
logger := &logrusr{
depth: 0,
logger: l.WithFields(logrus.Fields{}),
}
for _, o := range opts {
o(logger)
}
return logr.New(logger)
}
// Init receives optional information about the library.
func (l *logrusr) Init(ri logr.RuntimeInfo) {
// By default `CallDepth` is set to 1 which means one of the frames is
// skipped by default. This was originally missed in this library making the
// default behavior and `WithCallDepth(0)` behave differently.
// To be backwards compatible without affecting anyone manually setting the
// call depth we reduce 1 from the default depth instead of not adding it.
// See https://github.com/bombsimon/logrusr/issues/19 for more info.
l.depth = ri.CallDepth - 1
}
// Enabled tests whether this Logger is enabled. It will return true if the
// logrus.Logger has a level set to logrus.InfoLevel or higher (Warn/Panic).
// According to the documentation, level V(0) should be equivalent as calling
// Info() directly on the logger. To ensure this the constant `logrusDiffToInfo`
// will be added to all passed values so that V(0) creates a logger with level
// logrus.InfoLevel and V(2) would create a logger with level logrus.TraceLevel.
// This menas that if logrus is set to logrus.InfoLevel or **higher** this
// method will return true, otherwise false.
func (l *logrusr) Enabled(level int) bool {
// logrus.InfoLevel has value 4 so if the level on the logger is set to 0 we
// should only be seen as enabled if the logrus logger has a severity of
// info or higher.
return l.logger.Logger.IsLevelEnabled(logrus.Level(level + logrusDiffToInfo))
}
// Info logs info messages if the logger is enabled, that is if the level on the
// logger is set to logrus.InfoLevel or less.
func (l *logrusr) Info(level int, msg string, keysAndValues ...interface{}) {
log := l.logger
if c := l.caller(); c != "" {
log = log.WithField("caller", c)
}
log.
WithFields(listToLogrusFields(l.defaultFormatter, keysAndValues...)).
Log(logrus.Level(level+logrusDiffToInfo), msg)
}
// Error logs error messages. Since the log will be written with `Error` level
// it won't show if the severity of the underlying logrus logger is less than
// Error.
func (l *logrusr) Error(err error, msg string, keysAndValues ...interface{}) {
log := l.logger
if c := l.caller(); c != "" {
log = log.WithField("caller", c)
}
log.
WithFields(listToLogrusFields(l.defaultFormatter, keysAndValues...)).
WithError(err).
Error(msg)
}
// WithValues returns a new logger with additional key/values pairs. This is
// equivalent to logrus WithFields() but takes a list of even arguments
// (key/value pairs) instead of a map as input. If an odd number of arguments
// are sent all values will be discarded.
func (l *logrusr) WithValues(keysAndValues ...interface{}) logr.LogSink {
newLogger := l.copyLogger()
newLogger.logger = newLogger.logger.WithFields(
listToLogrusFields(l.defaultFormatter, keysAndValues...),
)
return newLogger
}
// WithName is a part of the Logger interface. This will set the key "logger" as
// a logrus field to identify the instance.
func (l *logrusr) WithName(name string) logr.LogSink {
newLogger := l.copyLogger()
newLogger.name = append(newLogger.name, name)
newLogger.logger = newLogger.logger.WithField(
"logger", strings.Join(newLogger.name, "."),
)
return newLogger
}
// listToLogrusFields converts a list of arbitrary length to key/value paris.
func listToLogrusFields(formatter func(interface{}) string, keysAndValues ...interface{}) logrus.Fields {
f := make(logrus.Fields)
// Skip all fields if it's not an even length list.
if len(keysAndValues)%2 != 0 {
return f
}
for i := 0; i < len(keysAndValues); i += 2 {
k, v := keysAndValues[i], keysAndValues[i+1]
if s, ok := k.(string); ok {
// Try to avoid marshaling known types.
switch vVal := v.(type) {
case int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64, complex64, complex128,
string, bool:
f[s] = vVal
case []byte:
f[s] = string(vVal)
default:
if formatter != nil {
f[s] = formatter(v)
} else {
j, _ := json.Marshal(vVal)
f[s] = string(j)
}
}
}
}
return f
}
// copyLogger copies the logger creating a new slice of the name but preserving
// the formatter and actual logrus logger.
func (l *logrusr) copyLogger() *logrusr {
newLogger := &logrusr{
name: make([]string, len(l.name)),
depth: l.depth,
reportCaller: l.reportCaller,
logger: l.logger.Dup(),
defaultFormatter: l.defaultFormatter,
}
copy(newLogger.name, l.name)
return newLogger
}
// WithCallDepth implements the optional WithCallDepth to offset the call stack
// when reporting caller.
func (l *logrusr) WithCallDepth(depth int) logr.LogSink {
newLogger := l.copyLogger()
newLogger.depth = depth
return newLogger
}
// caller will return the caller of the logging method.
func (l *logrusr) caller() string {
// Check if we should even report the caller.
if !l.reportCaller {
return ""
}
// +1 for this frame.
// +1 for frame calling here (Info/Error)
// +1 for logr frame
_, file, line, ok := runtime.Caller(l.depth + 3)
if !ok {
return ""
}
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
}