package log import ( "context" "fmt" "log" "sync" "cynking.com/pkg/log/klog" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // InfoLogger represents the ability to log non-error messages, at a particular verbosity. type InfoLogger interface { // Info logs a non-error message with the given key/value pairs as context. // // The msg argument should be used to add some constant description to // the log line. The key/value pairs can then be used to add additional // variable information. The key/value pairs should alternate string // keys and arbitrary values. Info(msg string, fields ...Field) Infof(format string, v ...interface{}) Infow(msg string, keysAndValues ...interface{}) // Enabled tests whether this InfoLogger is enabled. For example, // commandline flags might be used to set the logging verbosity and disable // some info logs. Enabled() bool } // Logger represents the ability to log messages, both errors and not. type Logger interface { // All Loggers implement InfoLogger. Calling InfoLogger methods directly on // a Logger value is equivalent to calling them on a V(0) InfoLogger. For // example, logger.Info() produces the same result as logger.V(0).Info. InfoLogger Debug(msg string, fields ...Field) Debugf(format string, v ...interface{}) Debugw(msg string, keysAndValues ...interface{}) Warn(msg string, fields ...Field) Warnf(format string, v ...interface{}) Warnw(msg string, keysAndValues ...interface{}) Error(msg string, fields ...Field) Errorf(format string, v ...interface{}) Errorw(msg string, keysAndValues ...interface{}) Panic(msg string, fields ...Field) Panicf(format string, v ...interface{}) Panicw(msg string, keysAndValues ...interface{}) Fatal(msg string, fields ...Field) Fatalf(format string, v ...interface{}) Fatalw(msg string, keysAndValues ...interface{}) // V returns an InfoLogger value for a specific verbosity level. A higher // verbosity level means a log message is less important. It's illegal to // pass a log level less than zero. V(level Level) InfoLogger Write(p []byte) (n int, err error) // WithValues adds some key-value pairs of context to a logger. // See Info for documentation on how key/value pairs work. WithValues(keysAndValues ...interface{}) Logger // WithName adds a new element to the logger's name. // Successive calls with WithName continue to append // suffixes to the logger's name. It's strongly recommended // that name segments contain only letters, digits, and hyphens // (see the package documentation for more information). WithName(name string) Logger // WithContext returns a copy of context in which the log value is set. WithContext(ctx context.Context) context.Context // Flush calls the underlying Core's Sync method, flushing any buffered // log entries. Applications should take care to call Sync before exiting. Flush() } var _ Logger = &zapLogger{} // 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, _ ...Field) {} func (l *noopInfoLogger) Infof(_ string, _ ...interface{}) {} func (l *noopInfoLogger) Infow(_ 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 { level zapcore.Level log *zap.Logger } func (l *infoLogger) Enabled() bool { return true } func (l *infoLogger) Info(msg string, fields ...Field) { if checkedEntry := l.log.Check(l.level, msg); checkedEntry != nil { checkedEntry.Write(fields...) } } func (l *infoLogger) Infof(format string, args ...interface{}) { if checkedEntry := l.log.Check(l.level, fmt.Sprintf(format, args...)); checkedEntry != nil { checkedEntry.Write() } } func (l *infoLogger) Infow(msg string, keysAndValues ...interface{}) { if checkedEntry := l.log.Check(l.level, msg); checkedEntry != nil { checkedEntry.Write(handleFields(l.log, keysAndValues)...) } } // 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. zapLogger *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...) } var ( std = New(NewOptions()) mu sync.Mutex ) // Init initializes logger with specified options. func Init(opts *Options) { mu.Lock() defer mu.Unlock() std = New(opts) } // New create logger by opts which can custmoized by command arguments. func New(opts *Options) *zapLogger { if opts == nil { opts = NewOptions() } var zapLevel zapcore.Level if err := zapLevel.UnmarshalText([]byte(opts.Level)); err != nil { zapLevel = zapcore.InfoLevel } encodeLevel := zapcore.CapitalLevelEncoder // when output to local path, with color is forbidden if opts.Format == ConsoleFormat && opts.EnableColor { encodeLevel = zapcore.CapitalColorLevelEncoder } encoderConfig := zapcore.EncoderConfig{ MessageKey: "message", LevelKey: "level", TimeKey: "timestamp", NameKey: "logger", CallerKey: "caller", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: encodeLevel, EncodeTime: timeEncoder, EncodeDuration: autoDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } loggerConfig := &zap.Config{ Level: zap.NewAtomicLevelAt(zapLevel), Development: opts.Development, DisableCaller: opts.DisableCaller, DisableStacktrace: opts.DisableStacktrace, Sampling: &zap.SamplingConfig{ Initial: 100, Thereafter: 100, }, Encoding: opts.Format, EncoderConfig: encoderConfig, OutputPaths: opts.OutputPaths, ErrorOutputPaths: opts.ErrorOutputPaths, } var err error l, err := loggerConfig.Build(zap.AddStacktrace(zapcore.PanicLevel), zap.AddCallerSkip(1)) if err != nil { panic(err) } logger := &zapLogger{ zapLogger: l.Named(opts.Name), infoLogger: infoLogger{ log: l, level: zap.InfoLevel, }, } klog.InitLogger(l) zap.RedirectStdLog(l) return logger } // SugaredLogger returns global sugared logger. func SugaredLogger() *zap.SugaredLogger { return std.zapLogger.Sugar() } // StdErrLogger returns logger of standard library which writes to supplied zap // logger at error level. func StdErrLogger() *log.Logger { if std == nil { return nil } if l, err := zap.NewStdLogAt(std.zapLogger, zapcore.ErrorLevel); err == nil { return l } return nil } // StdInfoLogger returns logger of standard library which writes to supplied zap // logger at info level. func StdInfoLogger() *log.Logger { if std == nil { return nil } if l, err := zap.NewStdLogAt(std.zapLogger, zapcore.InfoLevel); err == nil { return l } return nil } // V return a leveled InfoLogger. func V(level Level) InfoLogger { return std.V(level) } func (l *zapLogger) V(level Level) InfoLogger { if l.zapLogger.Core().Enabled(level) { return &infoLogger{ level: level, log: l.zapLogger, } } return disabledInfoLogger } func (l *zapLogger) Write(p []byte) (n int, err error) { l.zapLogger.Info(string(p)) return len(p), nil } // WithValues creates a child logger and adds adds Zap fields to it. func WithValues(keysAndValues ...interface{}) Logger { return std.WithValues(keysAndValues...) } func (l *zapLogger) WithValues(keysAndValues ...interface{}) Logger { newLogger := l.zapLogger.With(handleFields(l.zapLogger, keysAndValues)...) return NewLogger(newLogger) } // WithName adds a new path segment to the logger's name. Segments are joined by // periods. By default, Loggers are unnamed. func WithName(s string) Logger { return std.WithName(s) } func (l *zapLogger) WithName(name string) Logger { newLogger := l.zapLogger.Named(name) return NewLogger(newLogger) } // Flush calls the underlying Core's Sync method, flushing any buffered // log entries. Applications should take care to call Sync before exiting. func Flush() { std.Flush() } func (l *zapLogger) Flush() { _ = l.zapLogger.Sync() } // NewLogger creates a new logr.Logger using the given Zap Logger to log. func NewLogger(l *zap.Logger) Logger { return &zapLogger{ zapLogger: l, infoLogger: infoLogger{ log: l, level: zap.InfoLevel, }, } } // ZapLogger used for other log wrapper such as klog. func ZapLogger() *zap.Logger { return std.zapLogger } // CheckIntLevel used for other log wrapper such as klog which return if logging a // message at the specified level is enabled. func CheckIntLevel(level int32) bool { var lvl zapcore.Level if level < 5 { lvl = zapcore.InfoLevel } else { lvl = zapcore.DebugLevel } checkEntry := std.zapLogger.Check(lvl, "") return checkEntry != nil } // Debug method output debug level log. func Debug(msg string, fields ...Field) { std.zapLogger.Debug(msg, fields...) } func (l *zapLogger) Debug(msg string, fields ...Field) { l.zapLogger.Debug(msg, fields...) } // Debugf method output debug level log. func Debugf(format string, v ...interface{}) { std.zapLogger.Sugar().Debugf(format, v...) } func (l *zapLogger) Debugf(format string, v ...interface{}) { l.zapLogger.Sugar().Debugf(format, v...) } // Debugw method output debug level log. func Debugw(msg string, keysAndValues ...interface{}) { std.zapLogger.Sugar().Debugw(msg, keysAndValues...) } func (l *zapLogger) Debugw(msg string, keysAndValues ...interface{}) { l.zapLogger.Sugar().Debugw(msg, keysAndValues...) } // Info method output info level log. func Info(msg string, fields ...Field) { std.zapLogger.Info(msg, fields...) } func (l *zapLogger) Info(msg string, fields ...Field) { l.zapLogger.Info(msg, fields...) } // Infof method output info level log. func Infof(format string, v ...interface{}) { std.zapLogger.Sugar().Infof(format, v...) } func (l *zapLogger) Infof(format string, v ...interface{}) { l.zapLogger.Sugar().Infof(format, v...) } // Infow method output info level log. func Infow(msg string, keysAndValues ...interface{}) { std.zapLogger.Sugar().Infow(msg, keysAndValues...) } func (l *zapLogger) Infow(msg string, keysAndValues ...interface{}) { l.zapLogger.Sugar().Infow(msg, keysAndValues...) } // Warn method output warning level log. func Warn(msg string, fields ...Field) { std.zapLogger.Warn(msg, fields...) } func (l *zapLogger) Warn(msg string, fields ...Field) { l.zapLogger.Warn(msg, fields...) } // Warnf method output warning level log. func Warnf(format string, v ...interface{}) { std.zapLogger.Sugar().Warnf(format, v...) } func (l *zapLogger) Warnf(format string, v ...interface{}) { l.zapLogger.Sugar().Warnf(format, v...) } // Warnw method output warning level log. func Warnw(msg string, keysAndValues ...interface{}) { std.zapLogger.Sugar().Warnw(msg, keysAndValues...) } func (l *zapLogger) Warnw(msg string, keysAndValues ...interface{}) { l.zapLogger.Sugar().Warnw(msg, keysAndValues...) } // Error method output error level log. func Error(msg string, fields ...Field) { std.zapLogger.Error(msg, fields...) } func (l *zapLogger) Error(msg string, fields ...Field) { l.zapLogger.Error(msg, fields...) } // Errorf method output error level log. func Errorf(format string, v ...interface{}) { std.zapLogger.Sugar().Errorf(format, v...) } func (l *zapLogger) Errorf(format string, v ...interface{}) { l.zapLogger.Sugar().Errorf(format, v...) } // Errorw method output error level log. func Errorw(msg string, keysAndValues ...interface{}) { std.zapLogger.Sugar().Errorw(msg, keysAndValues...) } func (l *zapLogger) Errorw(msg string, keysAndValues ...interface{}) { l.zapLogger.Sugar().Errorw(msg, keysAndValues...) } // Panic method output panic level log and shutdown application. func Panic(msg string, fields ...Field) { std.zapLogger.Panic(msg, fields...) } func (l *zapLogger) Panic(msg string, fields ...Field) { l.zapLogger.Panic(msg, fields...) } // Panicf method output panic level log and shutdown application. func Panicf(format string, v ...interface{}) { std.zapLogger.Sugar().Panicf(format, v...) } func (l *zapLogger) Panicf(format string, v ...interface{}) { l.zapLogger.Sugar().Panicf(format, v...) } // Panicw method output panic level log. func Panicw(msg string, keysAndValues ...interface{}) { std.zapLogger.Sugar().Panicw(msg, keysAndValues...) } func (l *zapLogger) Panicw(msg string, keysAndValues ...interface{}) { l.zapLogger.Sugar().Panicw(msg, keysAndValues...) } // Fatal method output fatal level log. func Fatal(msg string, fields ...Field) { std.zapLogger.Fatal(msg, fields...) } func (l *zapLogger) Fatal(msg string, fields ...Field) { l.zapLogger.Fatal(msg, fields...) } // Fatalf method output fatal level log. func Fatalf(format string, v ...interface{}) { std.zapLogger.Sugar().Fatalf(format, v...) } func (l *zapLogger) Fatalf(format string, v ...interface{}) { l.zapLogger.Sugar().Fatalf(format, v...) } // Fatalw method output Fatalw level log. func Fatalw(msg string, keysAndValues ...interface{}) { std.zapLogger.Sugar().Fatalw(msg, keysAndValues...) } func (l *zapLogger) Fatalw(msg string, keysAndValues ...interface{}) { l.zapLogger.Sugar().Fatalw(msg, keysAndValues...) } // L method output with specified context value. func L(ctx context.Context) *zapLogger { return std.L(ctx) } func (l *zapLogger) L(ctx context.Context) *zapLogger { lg := l.clone() if requestID := ctx.Value(KeyRequestID); requestID != nil { lg.zapLogger = lg.zapLogger.With(zap.Any(KeyRequestID, requestID)) } if username := ctx.Value(KeyUsername); username != nil { lg.zapLogger = lg.zapLogger.With(zap.Any(KeyUsername, username)) } if watcherName := ctx.Value(KeyWatcherName); watcherName != nil { lg.zapLogger = lg.zapLogger.With(zap.Any(KeyWatcherName, watcherName)) } return lg } //nolint:predeclared func (l *zapLogger) clone() *zapLogger { clone := *l return &clone }