package fslog import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" "io" "sync" ) // 默认配置,准备了控制台、json文件两种输出方式 var consoleEncoder zapcore.Encoder var jsonEncoder zapcore.Encoder var consoleSync zapcore.WriteSyncer var outputSync zapcore.WriteSyncer type outFileConfig struct { filename string maxSize int maxAge int maxBackups int localTime bool Compress bool } type Logger struct { // 互斥量 // 用于内部不可并发逻辑使用 lock sync.Mutex // 日志打印用 logger zap.SugaredLogger // 当前日志打印级别 // 借助zap内部级别设置机制,该机制 // 内部使用乐观锁,协程安全 lv zap.AtomicLevel // zapcore.Core 映射,存储不同来源/用途的core创建 cores map[coreType][]CoreContext } func NewLogger() *Logger { logger := new(Logger) logger.lv = zap.NewAtomicLevelAt(zap.DebugLevel) logger.setCore(console, CoreContext{ Core: zapcore.NewCore(consoleEncoder, consoleSync, logger.lv), Writer: consoleSync, }) logger.setCore(output, CoreContext{ Core: zapcore.NewCore(jsonEncoder, outputSync, logger.lv), Writer: outputSync, }) logger.flush() return logger } // With 拼接自定义信息 // 主要用于打印当前环境快照信息(变量或其他自定义信息) // 打印后,该信息会跟随日志一起打印 func (l *Logger) With(k string, v any) *Logger { newL := l.clone() newL.logger = *newL.logger.With(zap.Any(k, v)) return newL } // Debug 格式化打印调试级别日志 // 不同于zap内部可变参数逻辑,该可变参数是用于,字符串格式化的 func (l *Logger) Debug(msg string, vs ...any) { if len(vs) == 0 { l.logger.Debug(msg) return } l.logger.Debugf(fmt.Sprintf(msg, vs...)) } // Info 格式化打印信息级别日志 // 不同于zap内部可变参数逻辑,该可变参数是用于,字符串格式化的 func (l *Logger) Info(msg string, vs ...any) { if len(vs) == 0 { l.logger.Info(msg) return } l.logger.Infof(msg, vs...) } // Warn 格式化打印警告级别日志 // 不同于zap内部可变参数逻辑,该可变参数是用于,字符串格式化的 func (l *Logger) Warn(msg string, vs ...any) { if len(vs) == 0 { l.logger.Warn(msg) return } l.logger.Warnf(msg, vs...) } // Error 打印错误级别日志 // 该方法具有两种传参形式: // 1. error类型:会直接格式化打印%+v日志 // 2. 信息(格式化) // 3. error+格式化信息:error会作为 With 格式存在,且依旧以%+v格式输出 func (l *Logger) Error(vs ...any) { if len(vs) == 0 { return } err, ok := vs[0].(error) if ok { if len(vs) == 1 { l.logger.Errorf("%+v", err) return } withed := l.logger.With(zap.String("err", fmt.Sprintf("%+v", err))) msg, ok := vs[1].(string) if ok { withed.Errorf(msg, vs[2:]) return } withed.Error(vs[1:]) return } msg, ok := vs[0].(string) if ok { if len(vs) > 1 { l.logger.Errorf(msg, vs[1:]...) return } if len(vs) == 1 { l.logger.Error(vs[0]) return } } } // Lv 获取当前日志打印级别 func (l *Logger) Lv() zap.AtomicLevel { return l.lv } // SetLv 设置当前日志打印级别 func (l *Logger) SetLv(lv Level) { l.lv.SetLevel(lv.zap()) } // NewFileOutput 新增日志输出文件配置 func (l *Logger) NewFileOutput(opts ...FileOutputOpt) { cfg := new(outFileConfig) for _, opt := range opts { opt(cfg) } l.newOut(&lumberjack.Logger{ Filename: cfg.filename, MaxSize: cfg.maxSize, MaxAge: cfg.maxAge, MaxBackups: cfg.maxBackups, LocalTime: cfg.localTime, Compress: cfg.Compress, }) } // NewOutput 新增日志输出位置 func (l *Logger) NewOutput(writer io.Writer) { l.newOut(writer) } // AddCore 添加Core func (l *Logger) AddCore(core ...CoreContext) { l.setCore(third, core...) l.flush() } // Flush 将缓冲区日志刷新至目标 func (l *Logger) Flush() { err := l.logger.Sync() if err != nil { Warn("flush log error: %s", err.Error()) } } // newOut 设置日志写出位置 func (l *Logger) newOut(writer io.Writer) { l.setCore(output, CoreContext{ Core: zapcore.NewCore(jsonEncoder, zapcore.AddSync(writer), l.lv), Writer: writer, }) l.flush() } // 保存内部core(不负责刷新 logger) func (l *Logger) setCore(ct coreType, core ...CoreContext) { l.lock.Lock() defer l.lock.Unlock() if l.cores == nil { l.cores = make(map[coreType][]CoreContext) } l.cores[ct] = append(l.cores[ct], core...) } // 刷新内部 logger // 刷新互斥,触发刷新后,刷新完成前依旧按照旧的配置执行 func (l *Logger) flush() { l.lock.Lock() defer l.lock.Unlock() cores := make(coreContexts, 0, len(l.cores)) for _, core := range l.cores { cores = append(cores, core...) } l.logger = *zap.New( zapcore.NewTee(cores.Cores()...), zap.AddCaller(), ).Sugar() } func (l *Logger) clone() *Logger { newL := &Logger{cores: l.cores, lv: l.lv} newL.flush() return newL }