package logger import ( "context" "fmt" "os" "path/filepath" "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) // Logger 日志接口 type Logger interface { Debug(msg string, fields ...zapcore.Field) Info(msg string, fields ...zapcore.Field) Warn(msg string, fields ...zapcore.Field) Error(msg string, fields ...zapcore.Field) Fatal(msg string, fields ...zapcore.Field) Panic(msg string, fields ...zapcore.Field) With(fields ...zapcore.Field) Logger WithContext(ctx context.Context) Logger Sync() error } // ZapLogger Zap日志实现 type ZapLogger struct { logger *zap.Logger } // Config 日志配置 type Config struct { Level string Format string Output string LogDir string // 日志目录 MaxSize int // 单个文件最大大小(MB) MaxBackups int // 最大备份文件数 MaxAge int // 最大保留天数 Compress bool // 是否压缩 UseDaily bool // 是否按日分包 } // NewLogger 创建新的日志实例 func NewLogger(config Config) (Logger, error) { // 设置日志级别 level, err := zapcore.ParseLevel(config.Level) if err != nil { return nil, fmt.Errorf("无效的日志级别: %w", err) } // 配置编码器 var encoder zapcore.Encoder encoderConfig := getEncoderConfig() switch config.Format { case "json": encoder = zapcore.NewJSONEncoder(encoderConfig) case "console": encoder = zapcore.NewConsoleEncoder(encoderConfig) default: encoder = zapcore.NewJSONEncoder(encoderConfig) } // 配置输出 var writeSyncer zapcore.WriteSyncer switch config.Output { case "stdout": writeSyncer = zapcore.AddSync(os.Stdout) case "stderr": writeSyncer = zapcore.AddSync(os.Stderr) case "file": writeSyncer, err = createFileWriteSyncer(config) if err != nil { return nil, fmt.Errorf("创建文件输出失败: %w", err) } default: writeSyncer = zapcore.AddSync(os.Stdout) } // 创建核心 core := zapcore.NewCore(encoder, writeSyncer, level) // 创建logger logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)) return &ZapLogger{ logger: logger, }, nil } // createFileWriteSyncer 创建文件输出同步器 func createFileWriteSyncer(config Config) (zapcore.WriteSyncer, error) { // 设置默认日志目录 if config.LogDir == "" { config.LogDir = "logs" } // 确保日志目录存在 if err := os.MkdirAll(config.LogDir, 0755); err != nil { return nil, fmt.Errorf("创建日志目录失败: %w", err) } // 设置默认值 if config.MaxSize == 0 { config.MaxSize = 100 // 默认100MB } if config.MaxBackups == 0 { config.MaxBackups = 3 // 默认3个备份 } if config.MaxAge == 0 { config.MaxAge = 7 // 默认7天 } // 构建日志文件路径 var logFilePath string if config.UseDaily { // 按日分包:logs/2024-01-01/app.log today := time.Now().Format("2006-01-02") dailyDir := filepath.Join(config.LogDir, today) if err := os.MkdirAll(dailyDir, 0755); err != nil { return nil, fmt.Errorf("创建日期目录失败: %w", err) } logFilePath = filepath.Join(dailyDir, "app.log") } else { // 传统方式:logs/app.log logFilePath = filepath.Join(config.LogDir, "app.log") } // 创建lumberjack日志轮转器 lumberJackLogger := &lumberjack.Logger{ Filename: logFilePath, MaxSize: config.MaxSize, // 单个文件最大大小(MB) MaxBackups: config.MaxBackups, // 最大备份文件数 MaxAge: config.MaxAge, // 最大保留天数 Compress: config.Compress, // 是否压缩 } return zapcore.AddSync(lumberJackLogger), nil } // getEncoderConfig 获取编码器配置 func getEncoderConfig() zapcore.EncoderConfig { return zapcore.EncoderConfig{ TimeKey: "timestamp", LevelKey: "level", NameKey: "logger", CallerKey: "caller", FunctionKey: zapcore.OmitKey, MessageKey: "message", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.StringDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } } // Debug 调试日志 func (l *ZapLogger) Debug(msg string, fields ...zapcore.Field) { l.logger.Debug(msg, fields...) } // Info 信息日志 func (l *ZapLogger) Info(msg string, fields ...zapcore.Field) { l.logger.Info(msg, fields...) } // Warn 警告日志 func (l *ZapLogger) Warn(msg string, fields ...zapcore.Field) { l.logger.Warn(msg, fields...) } // Error 错误日志 func (l *ZapLogger) Error(msg string, fields ...zapcore.Field) { l.logger.Error(msg, fields...) } // Fatal 致命错误日志 func (l *ZapLogger) Fatal(msg string, fields ...zapcore.Field) { l.logger.Fatal(msg, fields...) } // Panic 恐慌日志 func (l *ZapLogger) Panic(msg string, fields ...zapcore.Field) { l.logger.Panic(msg, fields...) } // With 添加字段 func (l *ZapLogger) With(fields ...zapcore.Field) Logger { return &ZapLogger{ logger: l.logger.With(fields...), } } // WithContext 从上下文添加字段 func (l *ZapLogger) WithContext(ctx context.Context) Logger { // 从上下文中提取常用字段 fields := []zapcore.Field{} if traceID := getTraceIDFromContext(ctx); traceID != "" { fields = append(fields, zap.String("trace_id", traceID)) } if userID := getUserIDFromContext(ctx); userID != "" { fields = append(fields, zap.String("user_id", userID)) } if requestID := getRequestIDFromContext(ctx); requestID != "" { fields = append(fields, zap.String("request_id", requestID)) } return l.With(fields...) } // Sync 同步日志 func (l *ZapLogger) Sync() error { return l.logger.Sync() } // GetZapLogger 获取内部的zap.Logger实例 func (l *ZapLogger) GetZapLogger() *zap.Logger { return l.logger } // getTraceIDFromContext 从上下文获取追踪ID func getTraceIDFromContext(ctx context.Context) string { if traceID := ctx.Value("trace_id"); traceID != nil { if id, ok := traceID.(string); ok { return id } } return "" } // getUserIDFromContext 从上下文获取用户ID func getUserIDFromContext(ctx context.Context) string { if userID := ctx.Value("user_id"); userID != nil { if id, ok := userID.(string); ok { return id } } return "" } // getRequestIDFromContext 从上下文获取请求ID func getRequestIDFromContext(ctx context.Context) string { if requestID := ctx.Value("request_id"); requestID != nil { if id, ok := requestID.(string); ok { return id } } return "" } // Field 创建日志字段的便捷函数 func String(key, val string) zapcore.Field { return zap.String(key, val) } func Int(key string, val int) zapcore.Field { return zap.Int(key, val) } func Int64(key string, val int64) zapcore.Field { return zap.Int64(key, val) } func Float64(key string, val float64) zapcore.Field { return zap.Float64(key, val) } func Bool(key string, val bool) zapcore.Field { return zap.Bool(key, val) } func Error(err error) zapcore.Field { return zap.Error(err) } func Any(key string, val interface{}) zapcore.Field { return zap.Any(key, val) } func Duration(key string, val interface{}) zapcore.Field { return zap.Any(key, val) }