From 9dac38b1e578cd1ec9af031592a9105a3ff2d1c7 Mon Sep 17 00:00:00 2001 From: JellyTony Date: Tue, 10 Oct 2023 13:50:26 +0800 Subject: [PATCH] fix --- .gitignore | 2 + level.go | 92 ++++++++++++++++++++++++ logger.go | 6 ++ logging.go | 185 ++++++++++++++++++++++++++++++++++++++---------- logging_test.go | 33 ++++++--- options.go | 57 ++++++--------- 6 files changed, 289 insertions(+), 86 deletions(-) create mode 100644 level.go diff --git a/.gitignore b/.gitignore index 7b60c26..996e3e5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,7 @@ # Go workspace file go.work +/logs/* + # idea .idea \ No newline at end of file diff --git a/level.go b/level.go new file mode 100644 index 0000000..c4e40f5 --- /dev/null +++ b/level.go @@ -0,0 +1,92 @@ +package logger + +import ( + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Level int8 + +const ( + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel = iota + 1 + // InfoLevel is the default logging priority. + // General operational entries about what's going on inside the application. + InfoLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. + ErrorLevel + // FatalLevel level. Logs and then calls `logger.Exit(1)`. highest level of severity. + FatalLevel +) + +func (l Level) String() string { + switch l { + case DebugLevel: + return "DEBUG" + case InfoLevel: + return "INFO" + case WarnLevel: + return "WARN" + case ErrorLevel: + return "ERROR" + case FatalLevel: + return "FATAL" + } + return "" +} + +// ParseLevel parses a level string into a logger Level value. +func ParseLevel(s string) Level { + switch strings.ToUpper(s) { + case "DEBUG": + return DebugLevel + case "INFO": + return InfoLevel + case "WARN": + return WarnLevel + case "ERROR": + return ErrorLevel + case "FATAL": + return FatalLevel + } + return InfoLevel +} + +func (l Level) unmarshalZapLevel() zapcore.Level { + switch l { + case DebugLevel: + return zap.DebugLevel + case InfoLevel: + return zap.InfoLevel + case WarnLevel: + return zap.WarnLevel + case ErrorLevel: + return zap.ErrorLevel + case FatalLevel: + return zap.FatalLevel + default: + return zap.InfoLevel + } +} + +// Enabled returns true if the given level is at or above this level. +func (l Level) Enabled(lvl Level) bool { + return lvl >= l +} + +// LevelEnablerFunc is a convenient way to implement zapcore.LevelEnabler with +// an anonymous function. +// +// It's particularly useful when splitting log output between different +// outputs (e.g., standard error and standard out). For sample code, see the +// package-level AdvancedConfiguration example. +type LevelEnablerFunc func(zapcore.Level) bool + +// Enabled calls the wrapped function. +func (f LevelEnablerFunc) Enabled(lvl zapcore.Level) bool { + return f(lvl) +} diff --git a/logger.go b/logger.go index 321da16..853bdc4 100644 --- a/logger.go +++ b/logger.go @@ -2,6 +2,12 @@ package logger import ( "context" + "errors" +) + +var ( + // ErrLogPathNotSet is an error that indicates the log path is not set. + ErrLogPathNotSet = errors.New("log path must be set") ) // Logger is the interface for Logger types diff --git a/logging.go b/logging.go index 20dbb24..163ef18 100644 --- a/logging.go +++ b/logging.go @@ -4,6 +4,7 @@ import ( "context" "io" "os" + "path" "github.com/mattn/go-colorable" "go.uber.org/zap" @@ -19,6 +20,8 @@ type Logging struct { opt Options atomicLevel zap.AtomicLevel lg *zap.SugaredLogger + + _rollingFiles []io.Writer } // WrappedWriteSyncer is a helper struct implementing zapcore.WriteSyncer to @@ -41,7 +44,7 @@ func New(opts ...Option) *Logging { opt := newOptions(opts...) l := &Logging{ opt: opt, - atomicLevel: zap.NewAtomicLevelAt(opt.level.Level()), + atomicLevel: zap.NewAtomicLevelAt(opt.level.unmarshalZapLevel()), } if err := l.build(); err != nil { panic(err) @@ -49,27 +52,80 @@ func New(opts ...Option) *Logging { return l } +func (l *Logging) LevelEnablerFunc(level zapcore.Level) LevelEnablerFunc { + return func(lvl zapcore.Level) bool { + if level == zapcore.FatalLevel { + return l.atomicLevel.Level() <= level && lvl >= level + } + return l.atomicLevel.Level() <= level && lvl == level + } +} + func (l *Logging) build() error { var ( - sync []zapcore.WriteSyncer + cores []zapcore.Core ) switch l.opt.mode { - case fileMode: + case FileMode: + var _cores []zapcore.Core if l.opt.writer != nil { - sync = append(sync, zapcore.AddSync(l.opt.writer)) + _cores = l.buildCustomWriter() + } else if l.opt.filename != "" { + _cores = l.buildFile() } else { - file := l.buildFile() - sync = append(sync, zapcore.AddSync(colorable.NewNonColorable(file))) + _cores = l.buildFiles() + } + if len(_cores) > 0 { + cores = append(cores, _cores...) } default: - if l.opt.writer != nil { - sync = append(sync, zapcore.AddSync(l.opt.writer)) - } else { - sync = append(sync, zapcore.AddSync(WrappedWriteSyncer{os.Stdout})) + _cores := l.buildConsole() + if len(_cores) > 0 { + cores = append(cores, _cores...) } } + zapLog := zap.New(zapcore.NewTee(cores...), zap.AddCaller(), zap.AddCallerSkip(l.opt.callerSkip)).Sugar() + if len(l.opt.fields) > 0 { + zapLog = zapLog.With(CopyFields(l.opt.fields)...) + } + if l.opt.namespace != "" { + zapLog = zapLog.With(zap.Namespace(l.opt.namespace)) + } + + l.lg = zapLog + return nil +} + +// buildConsole build console. +func (l *Logging) buildConsole() []zapcore.Core { + var ( + sync zapcore.WriteSyncer + enc zapcore.Encoder + ) + + if l.opt.encoder.IsConsole() { + enc = zapcore.NewConsoleEncoder(l.opt.encoderConfig) + } else { + enc = zapcore.NewJSONEncoder(l.opt.encoderConfig) + } + + if l.opt.writer != nil { + sync = zapcore.AddSync(l.opt.writer) + } else { + sync = zapcore.AddSync(WrappedWriteSyncer{os.Stdout}) + } + return []zapcore.Core{zapcore.NewCore(enc, sync, l.atomicLevel)} +} + +// buildCustomWriter build custom writer. +func (l *Logging) buildCustomWriter() []zapcore.Core { + syncer := l.opt.writer + if syncer == nil { + syncer = zapcore.AddSync(WrappedWriteSyncer{os.Stdout}) + } + var enc zapcore.Encoder if l.opt.encoder.IsConsole() { enc = zapcore.NewConsoleEncoder(l.opt.encoderConfig) @@ -77,30 +133,75 @@ func (l *Logging) build() error { enc = zapcore.NewJSONEncoder(l.opt.encoderConfig) } - zapLog := zap.New(zapcore.NewCore(enc, zapcore.NewMultiWriteSyncer(sync...), l.atomicLevel), - zap.AddCaller(), zap.AddCallerSkip(l.opt.callerSkip)).Sugar() - if len(l.opt.fields) > 0 { - zapLog = zapLog.With(CopyFields(l.opt.fields)...) + return []zapcore.Core{zapcore.NewCore(enc, zapcore.AddSync(syncer), l.atomicLevel)} +} + +// buildFile build rolling file. +func (l *Logging) buildFile() []zapcore.Core { + _ = l.Sync() + var enc zapcore.Encoder + if l.opt.encoder.IsConsole() { + enc = zapcore.NewConsoleEncoder(l.opt.encoderConfig) + } else { + enc = zapcore.NewJSONEncoder(l.opt.encoderConfig) } - if l.opt.namespace != "" { - zapLog = zapLog.With(zap.Namespace(l.opt.namespace)) + + syncerRolling := l.createOutput(path.Join(l.opt.path, l.opt.filename)) + l._rollingFiles = append(l._rollingFiles, []io.Writer{syncerRolling}...) + return []zapcore.Core{zapcore.NewCore(enc, syncerRolling, l.atomicLevel)} +} + +// buildFiles build rolling files. +func (l *Logging) buildFiles() []zapcore.Core { + var ( + cores = make([]zapcore.Core, 0, 5) + syncerRollingDebug, syncerRollingInfo, syncerRollingWarn, + syncerRollingError, syncerRollingFatal zapcore.WriteSyncer + ) + + var enc zapcore.Encoder + if l.opt.encoder.IsConsole() { + enc = zapcore.NewConsoleEncoder(l.opt.encoderConfig) + } else { + enc = zapcore.NewJSONEncoder(l.opt.encoderConfig) } - l.lg = zapLog + if err := l.Sync(); err != nil { + return nil + } - return nil + syncerRollingDebug = l.createOutput(path.Join(l.opt.path, debugFilename)) + + syncerRollingInfo = l.createOutput(path.Join(l.opt.path, infoFilename)) + + syncerRollingWarn = l.createOutput(path.Join(l.opt.path, warnFilename)) + + syncerRollingError = l.createOutput(path.Join(l.opt.path, errorFilename)) + + syncerRollingFatal = l.createOutput(path.Join(l.opt.path, fatalFilename)) + + cores = append(cores, + zapcore.NewCore(enc, syncerRollingDebug, l.LevelEnablerFunc(zap.DebugLevel)), + zapcore.NewCore(enc, syncerRollingInfo, l.LevelEnablerFunc(zap.InfoLevel)), + zapcore.NewCore(enc, syncerRollingWarn, l.LevelEnablerFunc(zap.WarnLevel)), + zapcore.NewCore(enc, syncerRollingError, l.LevelEnablerFunc(zap.ErrorLevel)), + zapcore.NewCore(enc, syncerRollingFatal, l.LevelEnablerFunc(zap.FatalLevel)), + ) + + l._rollingFiles = append(l._rollingFiles, []io.Writer{syncerRollingDebug, syncerRollingInfo, syncerRollingWarn, syncerRollingError, syncerRollingFatal}...) + return cores } -func (l *Logging) buildFile() *RollingFile { - return NewRollingFile( - l.opt.filename, +func (l *Logging) createOutput(filename string) zapcore.WriteSyncer { + return zapcore.AddSync(colorable.NewNonColorable(NewRollingFile( + filename, HourlyRolling, l.opt.maxSize, l.opt.maxBackups, l.opt.maxAge, l.opt.localTime, l.opt.compress, - ) + ))) } func CopyFields(fields map[string]interface{}) []interface{} { @@ -151,7 +252,8 @@ func (l *Logging) Options() Options { } func (l *Logging) SetLevel(lv Level) { - l.atomicLevel.SetLevel(lv.Level()) + l.opt.level = lv + l.atomicLevel.SetLevel(lv.unmarshalZapLevel()) } func (l *Logging) Clone() *Logging { @@ -224,6 +326,13 @@ func (l *Logging) Sync() error { return nil } + for _, w := range l._rollingFiles { + r, ok := w.(*RollingFile) + if ok { + r.Close() + } + } + return l.lg.Sync() } @@ -249,63 +358,63 @@ func SetLevel(lv Level) { } func Debug(args ...interface{}) { - DefaultLogger.Debug(args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Debug(args...) } func Info(args ...interface{}) { - DefaultLogger.Info(args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Info(args...) } func Warn(args ...interface{}) { - DefaultLogger.Warn(args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Warn(args...) } func Error(args ...interface{}) { - DefaultLogger.Error(args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Error(args...) } func Fatal(args ...interface{}) { - DefaultLogger.Fatal(args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Fatal(args...) } func Debugf(template string, args ...interface{}) { - DefaultLogger.Debugf(template, args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Debugf(template, args...) } func Infof(template string, args ...interface{}) { - DefaultLogger.Infof(template, args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Infof(template, args...) } func Warnf(template string, args ...interface{}) { - DefaultLogger.Warnf(template, args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Warnf(template, args...) } func Errorf(template string, args ...interface{}) { - DefaultLogger.Errorf(template, args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Errorf(template, args...) } func Fatalf(template string, args ...interface{}) { - DefaultLogger.Fatalf(template, args...) + DefaultLogger.WithCallDepth(callerSkipOffset).Fatalf(template, args...) } func Debugw(msg string, keysAndValues ...interface{}) { - DefaultLogger.Debugw(msg, keysAndValues...) + DefaultLogger.WithCallDepth(callerSkipOffset).Debugw(msg, keysAndValues...) } func Infow(msg string, keysAndValues ...interface{}) { - DefaultLogger.Infow(msg, keysAndValues...) + DefaultLogger.WithCallDepth(callerSkipOffset).Infow(msg, keysAndValues...) } func Warnw(msg string, keysAndValues ...interface{}) { - DefaultLogger.Warnw(msg, keysAndValues...) + DefaultLogger.WithCallDepth(callerSkipOffset).Warnw(msg, keysAndValues...) } func Errorw(msg string, keysAndValues ...interface{}) { - DefaultLogger.Errorw(msg, keysAndValues...) + DefaultLogger.WithCallDepth(callerSkipOffset).Errorw(msg, keysAndValues...) } func Fatalw(msg string, keysAndValues ...interface{}) { - DefaultLogger.Fatalw(msg, keysAndValues...) + DefaultLogger.WithCallDepth(callerSkipOffset).Fatalw(msg, keysAndValues...) } func Sync() error { diff --git a/logging_test.go b/logging_test.go index c25624e..a73a8f2 100644 --- a/logging_test.go +++ b/logging_test.go @@ -3,6 +3,7 @@ package logger_test import ( "context" "fmt" + "strings" "testing" "github.com/nextmicro/logger" @@ -42,7 +43,9 @@ func TestLogging_WithContext(t *testing.T) { t.Logf("trace_id: %s", spanContext.TraceID().String()) t.Logf("span_id: %s", spanContext.SpanID().String()) - logging.WithContext(ctx).Info("TestDefault_WithContext") + logging.WithContext(ctx).WithFields(map[string]any{ + "11": 222, + }).Info("TestDefault_WithContext") } func TestLogging_WithFields(t *testing.T) { @@ -141,19 +144,17 @@ func TestLoggerWithFields(t *testing.T) { func TestFilename(t *testing.T) { logger.DefaultLogger = logger.New( - logger.WithMode("file"), - logger.WithMaxSize(0), - logger.WithMaxBackups(1), - logger.WithMaxAge(3), - logger.WithLocalTime(true), - logger.WithCompress(false), - logger.WithFilename("./logs/test"), + logger.WithLevel(logger.DebugLevel), + logger.WithMode(logger.FileMode), ) defer logger.Sync() - for i := 0; i < 1000; i++ { + for i := 0; i < 100; i++ { + logger.Debug("test msg") logger.Info("test msg") + logger.Warn("test msg") + logger.Error("test msg") } } @@ -161,8 +162,14 @@ type CustomOutput struct { } func (c *CustomOutput) Write(p []byte) (n int, err error) { - fmt.Println(string(p)) - return 0, nil + msg := strings.Replace(string(p), "\n", "", 1) + fmt.Println(msg) + return len(p), nil +} + +func (c *CustomOutput) Sync() error { + fmt.Println("sync") + return nil } // 自定义输出源 @@ -178,3 +185,7 @@ func TestCustomOutput(t *testing.T) { logger.Info("test msg") } } + +func TestInfo(t *testing.T) { + logger.Info("test msg") +} diff --git a/options.go b/options.go index 8161e36..d3cf37d 100644 --- a/options.go +++ b/options.go @@ -10,47 +10,20 @@ const ( spanKey = "span_id" traceKey = "trace_id" - callerSkipOffset = 2 + callerSkipOffset = 1 - fileMode = "file" - consoleMode = "console" + FileMode = "file" + ConsoleMode = "console" ) -type Level zapcore.Level - const ( - // DebugLevel logs are typically voluminous, and are usually disabled in - // production. - DebugLevel Level = iota - 1 - // InfoLevel is the default logging priority. - InfoLevel - // WarnLevel logs are more important than Info, but don't need individual - // human review. - WarnLevel - // ErrorLevel logs are high-priority. If an application is running smoothly, - // it shouldn't generate any error-level logs. - ErrorLevel - // DPanicLevel logs are particularly important errors. In development the - // logger panics after writing the message. - DPanicLevel - // PanicLevel logs a message, then panics. - PanicLevel - // FatalLevel logs a message, then calls os.Exit(1). - FatalLevel - - _minLevel = DebugLevel - _maxLevel = FatalLevel - - // InvalidLevel is an invalid value for Level. - // - // Core implementations may panic if they see messages of this level. - InvalidLevel = _maxLevel + 1 + debugFilename = "debug" + infoFilename = "info" + warnFilename = "warn" + errorFilename = "error" + fatalFilename = "fatal" ) -func (l Level) Level() zapcore.Level { - return zapcore.Level(l) -} - type Option func(o *Options) type Options struct { @@ -73,6 +46,8 @@ type Options struct { type fileOptions struct { // mode is the logging mode. default is `consoleMode` mode string + // basePath is the base path of log file. default is `""` + path string // filename is the log filename. default is `""` filename string // maxAge is the maximum number of days to retain old log files based on the @@ -93,9 +68,10 @@ type fileOptions struct { func newOptions(opts ...Option) Options { opt := Options{ - level: Level(zapcore.InfoLevel), + level: InfoLevel, fileOptions: fileOptions{ - mode: consoleMode, + mode: ConsoleMode, + path: "./logs", }, callerSkip: callerSkipOffset, encoderConfig: zapcore.EncoderConfig{ @@ -158,6 +134,13 @@ func WithMode(mode string) Option { } } +// WithPath Setter function to set the log path. +func WithPath(path string) Option { + return func(o *Options) { + o.path = path + } +} + // WithFilename Setter function to set the log filename. func WithFilename(filename string) Option { return func(o *Options) {