292 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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)
 | ||
| }
 |