package logger import ( "context" "os" "path/filepath" "strings" "time" "github.com/gofiber/fiber/v3" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) var Log *zap.Logger const TraceIDKey = "request_id" // ====================== // 配置结构 // ====================== type Config struct { Dir string // 日志目录 Level string // debug info warn error MaxSize int // MB MaxBackups int MaxAge int // 天 Compress bool // 是否压缩 } // ====================== // 初始化 // ====================== func Init(cfg Config) { if cfg.Dir == "" { cfg.Dir = "logs" } if cfg.Level == "" { cfg.Level = "info" } level := parseLevel(cfg.Level) // ====================== // encoder 配置 // ====================== encoderConfig := zapcore.EncoderConfig{ TimeKey: "time", LevelKey: "level", MessageKey: "msg", CallerKey: "caller", EncodeCaller: zapcore.ShortCallerEncoder, } // 👉 文件 JSON encoder jsonEncoder := zapcore.NewJSONEncoder(func() zapcore.EncoderConfig { cfg := encoderConfig cfg.EncodeLevel = zapcore.CapitalLevelEncoder cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") return cfg }()) // 👉 控制台 encoder(可读) consoleEncoder := zapcore.NewConsoleEncoder(func() zapcore.EncoderConfig { cfg := encoderConfig cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") return cfg }()) // ====================== // writer // ====================== consoleWriter := zapcore.AddSync(os.Stdout) infoWriter := getWriter(cfg, "info") errorWriter := getWriter(cfg, "error") // ====================== // core // ====================== // 控制台(所有级别) consoleCore := zapcore.NewCore( consoleEncoder, consoleWriter, level, ) // info 文件 infoCore := zapcore.NewCore( jsonEncoder, infoWriter, zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl < zapcore.ErrorLevel && lvl >= level }), ) // error 文件 errorCore := zapcore.NewCore( jsonEncoder, errorWriter, zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel }), ) core := zapcore.NewTee( consoleCore, infoCore, errorCore, ) Log = zap.New( core, zap.AddCaller(), // 打印调用位置 zap.AddCallerSkip(1), // 跳过封装层 ) } // ====================== // Daily Writer(按天切割) // ====================== type DailyWriter struct { cfg Config level string log *lumberjack.Logger date string } func newDailyWriter(cfg Config, level string) *DailyWriter { return &DailyWriter{ cfg: cfg, level: level, } } func (w *DailyWriter) Write(p []byte) (n int, err error) { today := time.Now().Format("2006-01-02") if w.log == nil || w.date != today { dir := filepath.Join(w.cfg.Dir, w.level) _ = os.MkdirAll(dir, os.ModePerm) filename := filepath.Join(dir, today+".log") w.log = &lumberjack.Logger{ Filename: filename, MaxSize: w.cfg.MaxSize, MaxBackups: w.cfg.MaxBackups, MaxAge: w.cfg.MaxAge, Compress: w.cfg.Compress, } w.date = today } return w.log.Write(p) } func (w *DailyWriter) Sync() error { if w.log != nil { return w.log.Close() } return nil } // ====================== // writer 封装 // ====================== func getWriter(cfg Config, level string) zapcore.WriteSyncer { return zapcore.AddSync(newDailyWriter(cfg, level)) } // ====================== // level 解析 // ====================== func parseLevel(lvl string) zapcore.Level { switch strings.ToLower(lvl) { case "debug": return zap.DebugLevel case "warn": return zap.WarnLevel case "error": return zap.ErrorLevel default: return zap.InfoLevel } } // ====================== // trace 支持 // ====================== func SetTraceID(c fiber.Ctx, traceID string) { if c == nil { return } ctx := context.WithValue(c.Context(), TraceIDKey, traceID) c.SetContext(ctx) } func FromCtx(ctx context.Context) *zap.Logger { if ctx == nil { return Log } traceID, _ := ctx.Value(TraceIDKey).(string) if traceID == "" { return Log } return Log.With(zap.String("trace_id", traceID)) } // ====================== // 优雅退出 // ====================== func Sync() { if Log != nil { _ = Log.Sync() } }