package logger import ( "fmt" "log" "os" "path/filepath" "strings" "sync" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) const ( TRACKED = "trackID" ) var ( Logger *zap.Logger loggerMux sync.Once CallerSkip = 2 initErr error ) // BuildLogger 初始化日志系统 func BuildLogger() error { loggerMux.Do(func() { initErr = initLogger() }) return initErr } // initLogger 实际的日志初始化逻辑 func initLogger() error { logLevel := getLogLevel() logPath := os.Getenv("LOG_PATH") if logPath == "" { logPath = "./logs" // 默认日志路径 } // 尝试创建日志目录 if err := os.MkdirAll(logPath, 0755); err != nil { // 使用标准库的log记录错误,因为zap还没初始化 log.Printf("无法创建日志目录 %s: %v", logPath, err) return fmt.Errorf("无法创建日志目录 %s: %w", logPath, err) } // 配置滚动日志(按天滚动,保留28天) logFileName := filepath.Join(logPath, "ego.%Y%m%d.log") logs, err := rotatelogs.New( logFileName, rotatelogs.WithMaxAge(28*24*time.Hour), rotatelogs.WithRotationTime(24*time.Hour), ) if err != nil { log.Printf("创建滚动日志失败: %v", err) return fmt.Errorf("创建滚动日志失败: %w", err) } // 配置控制台输出 consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) consoleSyncer := zapcore.AddSync(os.Stdout) // 配置文件输出(生产环境JSON格式) prodEncoderConfig := zap.NewProductionEncoderConfig() prodEncoderConfig.EncodeTime = TimeEncoder prodEncoderConfig.CallerKey = "caller" prodEncoderConfig.EncodeCaller = zapcore.ShortCallerEncoder prodEncoder := zapcore.NewJSONEncoder(prodEncoderConfig) fileSyncer := zapcore.AddSync(logs) // 创建日志核心 core := zapcore.NewTee( zapcore.NewCore(consoleEncoder, consoleSyncer, logLevel), zapcore.NewCore(prodEncoder, fileSyncer, logLevel), ) // 初始化 Logger Logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(CallerSkip)) // 记录初始化成功 Logger.Info("日志系统初始化成功", zap.String("log_path", logPath), zap.String("log_level", logLevel.String()), ) return nil } // Debug 记录调试日志 func Debug(c *gin.Context, msg string, fields ...zap.Field) { if Logger != nil { logWithTrack(c, zap.DebugLevel, msg, fields...) } } // Info 记录信息日志 func Info(c *gin.Context, msg string, fields ...zap.Field) { if Logger != nil { logWithTrack(c, zap.InfoLevel, msg, fields...) } } // Warn 记录警告日志 func Warn(c *gin.Context, msg string, fields ...zap.Field) { if Logger != nil { logWithTrack(c, zap.WarnLevel, msg, fields...) } } // Error 记录错误日志 func Error(c *gin.Context, msg string, fields ...zap.Field) { if Logger != nil { logWithTrack(c, zap.ErrorLevel, msg, fields...) } } // logWithTrack 封装带 trackID 的日志记录 func logWithTrack(c *gin.Context, level zapcore.Level, msg string, fields ...zap.Field) { if Logger == nil { return } if c == nil { Logger.Check(level, msg).Write(fields...) return } trackID := GetTrackID(c) Logger.Check(level, msg).Write(append(fields, zap.String("trackID", trackID))...) } // GetTrackID 获取或生成 trackID func GetTrackID(c *gin.Context) string { if trackID, exists := c.Get(TRACKED); exists { return trackID.(string) } if random, err := uuid.NewRandom(); err == nil { // 生成新的 trackID 并存入上下文 newTrackID := strings.ToLower(random.String()) c.Set(TRACKED, newTrackID) return newTrackID } return "" } // getLogLevel 根据环境变量获取日志级别 func getLogLevel() zapcore.Level { switch strings.ToLower(os.Getenv("LOG_LEVEL")) { case "debug": return zap.DebugLevel case "info": return zap.InfoLevel case "warn": return zap.WarnLevel case "error": return zap.ErrorLevel default: return zap.InfoLevel // 默认使用 Info 级别 } } // TimeEncoder 自定义时间格式 func TimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format("2006-01-02 15:04:05.999")) }