package logx import ( "base-framework/pkg/routinex" "base-framework/pkg/utils/strutil" "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 TrackID = "trackId" 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 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 ----------------- // 在写入日志时自动从 routinex 获取 log_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 { val := routinex.Get(TrackID) if val == nil { val = uid.NextID() routinex.Set(TrackID, val) // 保证同一请求复用同一 trackID } fields = append(fields, zap.String(TrackID, strutil.ToString(val))) return c.Core.Write(entry, fields) } // InitLogger ----------------- 初始化日志 ----------------- func InitLogger() { once.Do(func() { if config.Log == nil { config.Log = defaultLogConfig() } createDirIfNotExist(config.Log.Dir) switchDate(time.Now().Format("2006-01-02")) }) } func defaultLogConfig() *types.Log { return &types.Log{ Dir: "logs", Level: "debug", Format: "json", Daily: true, MaxDays: 30, Caller: true, LevelDirs: true, Console: true, } } // ----------------- 初始化核心 ----------------- 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 = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(infoFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl <= zapcore.InfoLevel }))} warnCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(warnFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl == zapcore.WarnLevel }))} errorCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(errorFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel }))} cores := []zapcore.Core{infoCore, warnCore, errorCore} if config.Log.Console { consoleCore := &trackCore{zapcore.NewCore(newConsoleEncoder(true), zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.DebugLevel }))} cores = append(cores, 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] [%d] %s", pid, routinex.CurGID(), 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 }