package logx import ( "base-framework/pkg/utils/uid" "fmt" "os" "path/filepath" "sync" "time" "base-framework/pkg/config" "base-framework/pkg/types" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // ----------------- 全局变量 ----------------- var ( Logger *zap.Logger once sync.Once currentDate string mu sync.Mutex infoFile *os.File warnFile *os.File errorFile *os.File infoCore zapcore.Core warnCore zapcore.Core errorCore zapcore.Core ) const LogID = "log_id" const logTmFmtWithMS = "2006-01-02 15:04:05.000" // 彩色日志级别 var levelColor = map[zapcore.Level]string{ zapcore.DebugLevel: "\033[36mDEBUG\033[0m", zapcore.InfoLevel: "\033[32mINFO\033[0m", zapcore.WarnLevel: "\033[33mWARN\033[0m", zapcore.ErrorLevel: "\033[31mERROR\033[0m", zapcore.DPanicLevel: "\033[35mDPANIC\033[0m", zapcore.PanicLevel: "\033[35mPANIC\033[0m", zapcore.FatalLevel: "\033[35mFATAL\033[0m", } // ----------------- trackCore ----------------- // trackCore 包装 zapcore.Core,实现自动生成 track_id type trackCore struct { zapcore.Core } func (c *trackCore) With(fields []zapcore.Field) zapcore.Core { return &trackCore{Core: c.Core.With(fields)} } func (c *trackCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { if c.Enabled(entry.Level) { return ce.AddCore(entry, c) } return ce } func (c *trackCore) Write(entry zapcore.Entry, fields []zapcore.Field) error { hasTrackID := false for _, f := range fields { if f.Key == LogID { hasTrackID = true break } } if !hasTrackID { fields = append(fields, Uint64(LogID, uid.NextID())) } return c.Core.Write(entry, fields) } // ----------------- 默认日志配置 ----------------- func defaultLogConfig() *types.Log { return &types.Log{ Dir: "logs", Level: "debug", Format: "json", Daily: true, MaxDays: 30, Caller: true, LevelDirs: true, Console: true, } } // ----------------- 初始化 ----------------- func InitLogger() { once.Do(func() { if config.Log == nil { config.Log = defaultLogConfig() } createDirIfNotExist(config.Log.Dir) switchDate(time.Now().Format("2006-01-02")) }) } func switchDate(today string) { currentDate = today // 关闭旧文件 if infoFile != nil { _ = infoFile.Close() } if warnFile != nil { _ = warnFile.Close() } if errorFile != nil { _ = errorFile.Close() } // 文件路径 infoPath, warnPath, errorPath := config.Log.Dir, config.Log.Dir, config.Log.Dir if config.Log.LevelDirs { infoPath = filepath.Join(config.Log.Dir, "info") warnPath = filepath.Join(config.Log.Dir, "warn") errorPath = filepath.Join(config.Log.Dir, "error") createDirIfNotExist(infoPath) createDirIfNotExist(warnPath) createDirIfNotExist(errorPath) } else { createDirIfNotExist(config.Log.Dir) } // 打开文件 infoFile = getOrCreateFile(filepath.Join(infoPath, fmt.Sprintf("info-%s.log", today))) warnFile = getOrCreateFile(filepath.Join(warnPath, fmt.Sprintf("warn-%s.log", today))) errorFile = getOrCreateFile(filepath.Join(errorPath, fmt.Sprintf("error-%s.log", today))) // 文件编码器 var fileEncoder zapcore.Encoder if config.Log.Format == "console" { fileEncoder = newConsoleEncoder(false) } else { fileEncoder = newJSONEncoder() } // 文件核心 infoCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(infoFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl <= zapcore.InfoLevel })) warnCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(warnFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.WarnLevel })) errorCore = zapcore.NewCore(fileEncoder, zapcore.AddSync(errorFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel })) cores := []zapcore.Core{ &trackCore{infoCore}, &trackCore{warnCore}, &trackCore{errorCore}, } // 开启 console 输出 if config.Log.Console { consoleCore := zapcore.NewCore(newConsoleEncoder(true), zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.DebugLevel })) cores = append(cores, &trackCore{consoleCore}) } Logger = zap.New(zapcore.NewTee(cores...)) if config.Log.Caller { Logger = Logger.WithOptions(zap.AddCaller()) } else { Logger = Logger.WithOptions(zap.AddCallerSkip(1)) } Logger = Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { if config.Log.Daily { checkDateSwitch() } return nil })) } // ----------------- 编码器 ----------------- func newConsoleEncoder(enableColor bool) zapcore.Encoder { cfg := zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", CallerKey: "caller", MessageKey: "msg", LineEnding: zapcore.DefaultLineEnding, EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format(logTmFmtWithMS)) }, EncodeLevel: func(lvl zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { if enableColor { color, ok := levelColor[lvl] if !ok { color = lvl.CapitalString() } enc.AppendString(color) } else { enc.AppendString(lvl.CapitalString()) } }, EncodeCaller: func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { pid := os.Getpid() enc.AppendString(fmt.Sprintf("[%d] %s", pid, caller.TrimmedPath())) }, } return zapcore.NewConsoleEncoder(cfg) } func newJSONEncoder() zapcore.Encoder { cfg := zap.NewProductionEncoderConfig() cfg.TimeKey = "ts" cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format(logTmFmtWithMS)) } cfg.EncodeLevel = zapcore.CapitalLevelEncoder cfg.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(cfg) } // ----------------- 工具函数 ----------------- func checkDateSwitch() { today := time.Now().Format("2006-01-02") if today != currentDate { mu.Lock() defer mu.Unlock() if today != currentDate { switchDate(today) } } } func createDirIfNotExist(dir string) { if _, err := os.Stat(dir); os.IsNotExist(err) { _ = os.MkdirAll(dir, 0755) } } func getOrCreateFile(file string) *os.File { f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { panic(err) } return f }