323 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package logger
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"path/filepath"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"go.uber.org/zap"
 | ||
| 	"go.uber.org/zap/zapcore"
 | ||
| 	"gopkg.in/natefinch/lumberjack.v2"
 | ||
| )
 | ||
| 
 | ||
| // Logger 日志器接口 - 基于 Zap 官方推荐
 | ||
| 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
 | ||
| 	Named(name string) Logger
 | ||
| 
 | ||
| 	// 同步和清理
 | ||
| 	Sync() error
 | ||
| 	Core() zapcore.Core
 | ||
| 
 | ||
| 	// 获取原生 Zap Logger(用于高级功能)
 | ||
| 	GetZapLogger() *zap.Logger
 | ||
| }
 | ||
| 
 | ||
| // Config 日志配置 - 基于 Zap 官方配置结构
 | ||
| type Config struct {
 | ||
| 	// 基础配置
 | ||
| 	Level      string `mapstructure:"level"`       // 日志级别
 | ||
| 	Format     string `mapstructure:"format"`      // 输出格式 (json/console)
 | ||
| 	Output     string `mapstructure:"output"`      // 输出方式 (stdout/stderr/file)
 | ||
| 	LogDir     string `mapstructure:"log_dir"`     // 日志目录
 | ||
| 	UseDaily   bool   `mapstructure:"use_daily"`   // 是否按日分包
 | ||
| 	UseColor   bool   `mapstructure:"use_color"`   // 是否使用彩色输出(仅console格式)
 | ||
| 
 | ||
| 	// 文件配置
 | ||
| 	MaxSize    int  `mapstructure:"max_size"`     // 单个文件最大大小(MB)
 | ||
| 	MaxBackups int  `mapstructure:"max_backups"`  // 最大备份文件数
 | ||
| 	MaxAge     int  `mapstructure:"max_age"`      // 最大保留天数
 | ||
| 	Compress   bool `mapstructure:"compress"`     // 是否压缩
 | ||
| 
 | ||
| 	// 高级功能
 | ||
| 	EnableLevelSeparation bool                       `mapstructure:"enable_level_separation"` // 是否启用按级别分文件
 | ||
| 	LevelConfigs          map[string]interface{}    `mapstructure:"level_configs"`           // 各级别配置(使用 interface{} 避免循环依赖)
 | ||
| 	EnableRequestLogging  bool                       `mapstructure:"enable_request_logging"`  // 是否启用请求日志
 | ||
| 	EnablePerformanceLog  bool                       `mapstructure:"enable_performance_log"`  // 是否启用性能日志
 | ||
| 
 | ||
| 	// 开发环境配置
 | ||
| 	Development bool `mapstructure:"development"` // 是否为开发环境
 | ||
| 	Sampling    bool `mapstructure:"sampling"`    // 是否启用采样
 | ||
| }
 | ||
| 
 | ||
| // ZapLogger Zap日志实现 - 基于官方推荐
 | ||
| type ZapLogger struct {
 | ||
| 	logger *zap.Logger
 | ||
| }
 | ||
| 
 | ||
| // NewLogger 创建新的日志实例 - 使用 Zap 官方推荐的方式
 | ||
| func NewLogger(config Config) (Logger, error) {
 | ||
| 	var logger *zap.Logger
 | ||
| 	var err error
 | ||
| 
 | ||
| 	// 根据环境创建合适的日志器
 | ||
| 	if config.Development {
 | ||
| 		logger, err = zap.NewDevelopment(
 | ||
| 			zap.AddCaller(),
 | ||
| 			zap.AddCallerSkip(1),
 | ||
| 			zap.AddStacktrace(zapcore.ErrorLevel),
 | ||
| 		)
 | ||
| 	} else {
 | ||
| 		logger, err = zap.NewProduction(
 | ||
| 			zap.AddCaller(),
 | ||
| 			zap.AddCallerSkip(1),
 | ||
| 			zap.AddStacktrace(zapcore.ErrorLevel),
 | ||
| 		)
 | ||
| 	}
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return nil, err
 | ||
| 	}
 | ||
| 
 | ||
| 	// 如果配置为文件输出,需要手动设置 Core
 | ||
| 	if config.Output == "file" {
 | ||
| 		writeSyncer, err := createFileWriteSyncer(config)
 | ||
| 		if err != nil {
 | ||
| 			return nil, err
 | ||
| 		}
 | ||
| 		
 | ||
| 		// 创建新的 Core 并替换
 | ||
| 		encoder := getEncoder(config.Format, config)
 | ||
| 		level := getLogLevel(config.Level)
 | ||
| 		core := zapcore.NewCore(encoder, writeSyncer, level)
 | ||
| 		logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
 | ||
| 	}
 | ||
| 
 | ||
| 	return &ZapLogger{
 | ||
| 		logger: logger,
 | ||
| 	}, nil
 | ||
| }
 | ||
| 
 | ||
| // 实现 Logger 接口
 | ||
| func (z *ZapLogger) Debug(msg string, fields ...zapcore.Field) {
 | ||
| 	z.logger.Debug(msg, fields...)
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Info(msg string, fields ...zapcore.Field) {
 | ||
| 	z.logger.Info(msg, fields...)
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Warn(msg string, fields ...zapcore.Field) {
 | ||
| 	z.logger.Warn(msg, fields...)
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Error(msg string, fields ...zapcore.Field) {
 | ||
| 	z.logger.Error(msg, fields...)
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Fatal(msg string, fields ...zapcore.Field) {
 | ||
| 	z.logger.Fatal(msg, fields...)
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Panic(msg string, fields ...zapcore.Field) {
 | ||
| 	z.logger.Panic(msg, fields...)
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) With(fields ...zapcore.Field) Logger {
 | ||
| 	return &ZapLogger{logger: z.logger.With(fields...)}
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) WithContext(ctx context.Context) Logger {
 | ||
| 	// 从上下文提取字段
 | ||
| 	fields := extractFieldsFromContext(ctx)
 | ||
| 	return &ZapLogger{logger: z.logger.With(fields...)}
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Named(name string) Logger {
 | ||
| 	return &ZapLogger{logger: z.logger.Named(name)}
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Sync() error {
 | ||
| 	return z.logger.Sync()
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) Core() zapcore.Core {
 | ||
| 	return z.logger.Core()
 | ||
| }
 | ||
| 
 | ||
| func (z *ZapLogger) GetZapLogger() *zap.Logger {
 | ||
| 	return z.logger
 | ||
| }
 | ||
| 
 | ||
| // 全局日志器 - 基于 Zap 官方推荐
 | ||
| var globalLogger *zap.Logger
 | ||
| 
 | ||
| // InitGlobalLogger 初始化全局日志器
 | ||
| func InitGlobalLogger(config Config) error {
 | ||
| 	var logger *zap.Logger
 | ||
| 	var err error
 | ||
| 
 | ||
| 	// 根据环境创建合适的日志器
 | ||
| 	if config.Development {
 | ||
| 		logger, err = zap.NewDevelopment(
 | ||
| 			zap.AddCaller(),
 | ||
| 			zap.AddCallerSkip(1),
 | ||
| 			zap.AddStacktrace(zapcore.ErrorLevel),
 | ||
| 		)
 | ||
| 	} else {
 | ||
| 		logger, err = zap.NewProduction(
 | ||
| 			zap.AddCaller(),
 | ||
| 			zap.AddCallerSkip(1),
 | ||
| 			zap.AddStacktrace(zapcore.ErrorLevel),
 | ||
| 		)
 | ||
| 	}
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		return err
 | ||
| 	}
 | ||
| 
 | ||
| 	// 如果配置为文件输出,需要手动设置 Core
 | ||
| 	if config.Output == "file" {
 | ||
| 		writeSyncer, err := createFileWriteSyncer(config)
 | ||
| 		if err != nil {
 | ||
| 			return err
 | ||
| 		}
 | ||
| 		
 | ||
| 		// 创建新的 Core 并替换
 | ||
| 		encoder := getEncoder(config.Format, config)
 | ||
| 		level := getLogLevel(config.Level)
 | ||
| 		core := zapcore.NewCore(encoder, writeSyncer, level)
 | ||
| 		logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
 | ||
| 	}
 | ||
| 
 | ||
| 	// 替换全局日志器
 | ||
| 	zap.ReplaceGlobals(logger)
 | ||
| 	globalLogger = logger
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // GetGlobalLogger 获取全局日志器
 | ||
| func GetGlobalLogger() *zap.Logger {
 | ||
| 	if globalLogger == nil {
 | ||
| 		// 如果没有初始化,使用默认的生产环境配置
 | ||
| 		globalLogger = zap.Must(zap.NewProduction())
 | ||
| 	}
 | ||
| 	return globalLogger
 | ||
| }
 | ||
| 
 | ||
| // L 获取全局日志器(Zap 官方推荐的方式)
 | ||
| func L() *zap.Logger {
 | ||
| 	return zap.L()
 | ||
| }
 | ||
| 
 | ||
| // 辅助函数
 | ||
| func getLogLevel(level string) zapcore.Level {
 | ||
| 	switch level {
 | ||
| 	case "debug":
 | ||
| 		return zapcore.DebugLevel
 | ||
| 	case "info":
 | ||
| 		return zapcore.InfoLevel
 | ||
| 	case "warn":
 | ||
| 		return zapcore.WarnLevel
 | ||
| 	case "error":
 | ||
| 		return zapcore.ErrorLevel
 | ||
| 	case "fatal":
 | ||
| 		return zapcore.FatalLevel
 | ||
| 	case "panic":
 | ||
| 		return zapcore.PanicLevel
 | ||
| 	default:
 | ||
| 		return zapcore.InfoLevel
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func getEncoder(format string, config Config) zapcore.Encoder {
 | ||
| 	encoderConfig := getEncoderConfig(config)
 | ||
| 	
 | ||
| 	if format == "console" {
 | ||
| 		return zapcore.NewConsoleEncoder(encoderConfig)
 | ||
| 	}
 | ||
| 	
 | ||
| 	return zapcore.NewJSONEncoder(encoderConfig)
 | ||
| }
 | ||
| 
 | ||
| func getEncoderConfig(config Config) zapcore.EncoderConfig {
 | ||
| 	encoderConfig := zap.NewProductionEncoderConfig()
 | ||
| 	
 | ||
| 	if config.Development {
 | ||
| 		encoderConfig = zap.NewDevelopmentEncoderConfig()
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 自定义时间格式
 | ||
| 	encoderConfig.TimeKey = "timestamp"
 | ||
| 	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 | ||
| 	
 | ||
| 	// 自定义级别格式
 | ||
| 	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
 | ||
| 	
 | ||
| 	// 自定义调用者格式
 | ||
| 	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
 | ||
| 	
 | ||
| 	return encoderConfig
 | ||
| }
 | ||
| 
 | ||
| func createFileWriteSyncer(config Config) (zapcore.WriteSyncer, error) {
 | ||
| 	// 使用 lumberjack 进行日志轮转
 | ||
| 	rotator := &lumberjack.Logger{
 | ||
| 		Filename:   getLogFilePath(config),
 | ||
| 		MaxSize:    config.MaxSize,
 | ||
| 		MaxBackups: config.MaxBackups,
 | ||
| 		MaxAge:     config.MaxAge,
 | ||
| 		Compress:   config.Compress,
 | ||
| 	}
 | ||
| 	
 | ||
| 	return zapcore.AddSync(rotator), nil
 | ||
| }
 | ||
| 
 | ||
| func getLogFilePath(config Config) string {
 | ||
| 	if config.UseDaily {
 | ||
| 		// 按日期分包
 | ||
| 		date := time.Now().Format("2006-01-02")
 | ||
| 		return filepath.Join(config.LogDir, date, "app.log")
 | ||
| 	}
 | ||
| 	
 | ||
| 	return filepath.Join(config.LogDir, "app.log")
 | ||
| }
 | ||
| 
 | ||
| func extractFieldsFromContext(ctx context.Context) []zapcore.Field {
 | ||
| 	var fields []zapcore.Field
 | ||
| 	
 | ||
| 	// 提取请求ID
 | ||
| 	if requestID := ctx.Value("request_id"); requestID != nil {
 | ||
| 		if id, ok := requestID.(string); ok {
 | ||
| 			fields = append(fields, zap.String("request_id", id))
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 提取用户ID
 | ||
| 	if userID := ctx.Value("user_id"); userID != nil {
 | ||
| 		if id, ok := userID.(string); ok {
 | ||
| 			fields = append(fields, zap.String("user_id", id))
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	// 提取跟踪ID
 | ||
| 	if traceID := ctx.Value("trace_id"); traceID != nil {
 | ||
| 		if id, ok := traceID.(string); ok {
 | ||
| 			fields = append(fields, zap.String("trace_id", id))
 | ||
| 		}
 | ||
| 	}
 | ||
| 	
 | ||
| 	return fields
 | ||
| }
 |