package external_logger import ( "fmt" "os" "path/filepath" "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) // ExternalServiceLoggingConfig 外部服务日志配置 type ExternalServiceLoggingConfig struct { Enabled bool `yaml:"enabled"` LogDir string `yaml:"log_dir"` ServiceName string `yaml:"service_name"` // 服务名称,用于区分日志目录 UseDaily bool `yaml:"use_daily"` EnableLevelSeparation bool `yaml:"enable_level_separation"` LevelConfigs map[string]ExternalServiceLevelFileConfig `yaml:"level_configs"` // 新增:请求和响应日志的独立配置 RequestLogConfig ExternalServiceLevelFileConfig `yaml:"request_log_config"` ResponseLogConfig ExternalServiceLevelFileConfig `yaml:"response_log_config"` } // ExternalServiceLevelFileConfig 外部服务级别文件配置 type ExternalServiceLevelFileConfig struct { MaxSize int `yaml:"max_size"` MaxBackups int `yaml:"max_backups"` MaxAge int `yaml:"max_age"` Compress bool `yaml:"compress"` } // ExternalServiceLogger 外部服务日志器 type ExternalServiceLogger struct { logger *zap.Logger config ExternalServiceLoggingConfig serviceName string // 新增:用于区分请求和响应日志的字段 requestLogger *zap.Logger responseLogger *zap.Logger } // NewExternalServiceLogger 创建外部服务日志器 func NewExternalServiceLogger(config ExternalServiceLoggingConfig) (*ExternalServiceLogger, error) { if !config.Enabled { return &ExternalServiceLogger{ logger: zap.NewNop(), serviceName: config.ServiceName, }, nil } // 根据服务名称创建独立的日志目录 serviceLogDir := filepath.Join(config.LogDir, config.ServiceName) // 确保日志目录存在 if err := os.MkdirAll(serviceLogDir, 0755); err != nil { return nil, fmt.Errorf("创建日志目录失败: %w", err) } // 创建基础配置 zapConfig := zap.NewProductionConfig() zapConfig.OutputPaths = []string{"stdout"} zapConfig.ErrorOutputPaths = []string{"stderr"} // 创建基础logger baseLogger, err := zapConfig.Build() if err != nil { return nil, fmt.Errorf("创建基础logger失败: %w", err) } // 创建请求和响应日志器 requestLogger, err := createRequestLogger(serviceLogDir, config) if err != nil { // 如果创建失败,使用基础logger作为备选 requestLogger = baseLogger fmt.Printf("创建请求日志器失败,使用基础logger: %v\n", err) } responseLogger, err := createResponseLogger(serviceLogDir, config) if err != nil { // 如果创建失败,使用基础logger作为备选 responseLogger = baseLogger fmt.Printf("创建响应日志器失败,使用基础logger: %v\n", err) } // 如果启用级别分离,创建文件输出 if config.EnableLevelSeparation { core := createSeparatedCore(serviceLogDir, config) baseLogger = zap.New(core) } // 创建日志器实例 logger := &ExternalServiceLogger{ logger: baseLogger, config: config, serviceName: config.ServiceName, requestLogger: requestLogger, responseLogger: responseLogger, } // 如果启用按天分隔,启动定时清理任务 if config.UseDaily { go logger.startCleanupTask() } return logger, nil } // createRequestLogger 创建请求日志器 func createRequestLogger(logDir string, config ExternalServiceLoggingConfig) (*zap.Logger, error) { // 创建编码器 encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.TimeKey = "timestamp" encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 使用默认配置如果未指定 requestConfig := config.RequestLogConfig if requestConfig.MaxSize == 0 { requestConfig.MaxSize = 100 } if requestConfig.MaxBackups == 0 { requestConfig.MaxBackups = 5 } if requestConfig.MaxAge == 0 { requestConfig.MaxAge = 30 } // 创建请求日志文件写入器 requestWriter := createFileWriter(logDir, "request", requestConfig, config.ServiceName, config.UseDaily) // 创建请求日志核心 requestCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(requestWriter), zapcore.InfoLevel, ) return zap.New(requestCore), nil } // createResponseLogger 创建响应日志器 func createResponseLogger(logDir string, config ExternalServiceLoggingConfig) (*zap.Logger, error) { // 创建编码器 encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.TimeKey = "timestamp" encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 使用默认配置如果未指定 responseConfig := config.ResponseLogConfig if responseConfig.MaxSize == 0 { responseConfig.MaxSize = 100 } if responseConfig.MaxBackups == 0 { responseConfig.MaxBackups = 5 } if responseConfig.MaxAge == 0 { responseConfig.MaxAge = 30 } // 创建响应日志文件写入器 responseWriter := createFileWriter(logDir, "response", responseConfig, config.ServiceName, config.UseDaily) // 创建响应日志核心 responseCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(responseWriter), zapcore.InfoLevel, ) return zap.New(responseCore), nil } // createSeparatedCore 创建分离的日志核心 func createSeparatedCore(logDir string, config ExternalServiceLoggingConfig) zapcore.Core { // 创建编码器 encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.TimeKey = "timestamp" encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 创建不同级别的文件输出 infoWriter := createFileWriter(logDir, "info", config.LevelConfigs["info"], config.ServiceName, config.UseDaily) errorWriter := createFileWriter(logDir, "error", config.LevelConfigs["error"], config.ServiceName, config.UseDaily) warnWriter := createFileWriter(logDir, "warn", config.LevelConfigs["warn"], config.ServiceName, config.UseDaily) // 新增:请求和响应日志的独立文件输出 requestWriter := createFileWriter(logDir, "request", config.RequestLogConfig, config.ServiceName, config.UseDaily) responseWriter := createFileWriter(logDir, "response", config.ResponseLogConfig, config.ServiceName, config.UseDaily) // 修复:创建真正的级别分离核心 // 使用自定义的LevelEnabler来确保每个Core只处理特定级别的日志 infoCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(infoWriter), &levelEnabler{minLevel: zapcore.InfoLevel, maxLevel: zapcore.InfoLevel}, // 只接受INFO级别 ) errorCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(errorWriter), &levelEnabler{minLevel: zapcore.ErrorLevel, maxLevel: zapcore.ErrorLevel}, // 只接受ERROR级别 ) warnCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(warnWriter), &levelEnabler{minLevel: zapcore.WarnLevel, maxLevel: zapcore.WarnLevel}, // 只接受WARN级别 ) // 新增:请求和响应日志核心 requestCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(requestWriter), &requestResponseEnabler{logType: "request"}, // 只接受请求日志 ) responseCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(responseWriter), &requestResponseEnabler{logType: "response"}, // 只接受响应日志 ) // 使用 zapcore.NewTee 合并核心,现在每个核心只会处理自己类型的日志 return zapcore.NewTee(infoCore, errorCore, warnCore, requestCore, responseCore) } // levelEnabler 自定义级别过滤器,确保只接受指定级别的日志 type levelEnabler struct { minLevel zapcore.Level maxLevel zapcore.Level } // Enabled 实现 zapcore.LevelEnabler 接口 func (l *levelEnabler) Enabled(level zapcore.Level) bool { return level >= l.minLevel && level <= l.maxLevel } // requestResponseEnabler 自定义日志类型过滤器,确保只接受特定类型的日志 type requestResponseEnabler struct { logType string } // Enabled 实现 zapcore.LevelEnabler 接口 func (r *requestResponseEnabler) Enabled(level zapcore.Level) bool { // 请求和响应日志通常是INFO级别 return level == zapcore.InfoLevel } // createFileWriter 创建文件写入器 func createFileWriter(logDir, level string, config ExternalServiceLevelFileConfig, serviceName string, useDaily bool) *lumberjack.Logger { // 使用默认配置如果未指定 if config.MaxSize == 0 { config.MaxSize = 100 } if config.MaxBackups == 0 { config.MaxBackups = 3 } if config.MaxAge == 0 { config.MaxAge = 28 } // 构建文件名 var filename string if useDaily { // 按天分隔:logs/westdex/2024-01-01/westdex_info.log date := time.Now().Format("2006-01-02") dateDir := filepath.Join(logDir, date) // 确保日期目录存在 if err := os.MkdirAll(dateDir, 0755); err != nil { // 如果创建日期目录失败,回退到根目录 filename = filepath.Join(logDir, fmt.Sprintf("%s_%s.log", serviceName, level)) } else { filename = filepath.Join(dateDir, fmt.Sprintf("%s_%s.log", serviceName, level)) } } else { // 传统方式:logs/westdex/westdex_info.log filename = filepath.Join(logDir, fmt.Sprintf("%s_%s.log", serviceName, level)) } return &lumberjack.Logger{ Filename: filename, MaxSize: config.MaxSize, MaxBackups: config.MaxBackups, MaxAge: config.MaxAge, Compress: config.Compress, } } // LogRequest 记录请求日志 func (e *ExternalServiceLogger) LogRequest(requestID, transactionID, apiCode string, url interface{}, params interface{}) { e.requestLogger.Info(fmt.Sprintf("%s API请求", e.serviceName), zap.String("service", e.serviceName), zap.String("request_id", requestID), zap.String("transaction_id", transactionID), zap.String("api_code", apiCode), zap.Any("url", url), zap.Any("params", params), zap.String("timestamp", time.Now().Format(time.RFC3339)), ) } // LogResponse 记录响应日志 func (e *ExternalServiceLogger) LogResponse(requestID, transactionID, apiCode string, statusCode int, response []byte, duration time.Duration) { e.responseLogger.Info(fmt.Sprintf("%s API响应", e.serviceName), zap.String("service", e.serviceName), zap.String("request_id", requestID), zap.String("transaction_id", transactionID), zap.String("api_code", apiCode), zap.Int("status_code", statusCode), zap.String("response", string(response)), zap.Duration("duration", duration), zap.String("timestamp", time.Now().Format(time.RFC3339)), ) } // LogResponseWithID 记录包含响应ID的响应日志 func (e *ExternalServiceLogger) LogResponseWithID(requestID, transactionID, apiCode string, statusCode int, response []byte, duration time.Duration, responseID string) { e.responseLogger.Info(fmt.Sprintf("%s API响应", e.serviceName), zap.String("service", e.serviceName), zap.String("request_id", requestID), zap.String("transaction_id", transactionID), zap.String("api_code", apiCode), zap.Int("status_code", statusCode), zap.String("response", string(response)), zap.Duration("duration", duration), zap.String("response_id", responseID), zap.String("timestamp", time.Now().Format(time.RFC3339)), ) } // LogError 记录错误日志 func (e *ExternalServiceLogger) LogError(requestID, transactionID, apiCode string, err error, params interface{}) { e.logger.Error(fmt.Sprintf("%s API错误", e.serviceName), zap.String("service", e.serviceName), zap.String("request_id", requestID), zap.String("transaction_id", transactionID), zap.String("api_code", apiCode), zap.Error(err), zap.Any("params", params), zap.String("timestamp", time.Now().Format(time.RFC3339)), ) } // LogErrorWithResponseID 记录包含响应ID的错误日志 func (e *ExternalServiceLogger) LogErrorWithResponseID(requestID, transactionID, apiCode string, err error, params interface{}, responseID string) { e.logger.Error(fmt.Sprintf("%s API错误", e.serviceName), zap.String("service", e.serviceName), zap.String("request_id", requestID), zap.String("transaction_id", transactionID), zap.String("api_code", apiCode), zap.Error(err), zap.Any("params", params), zap.String("response_id", responseID), zap.String("timestamp", time.Now().Format(time.RFC3339)), ) } // LogInfo 记录信息日志 func (e *ExternalServiceLogger) LogInfo(message string, fields ...zap.Field) { allFields := []zap.Field{zap.String("service", e.serviceName)} allFields = append(allFields, fields...) e.logger.Info(message, allFields...) } // LogWarn 记录警告日志 func (e *ExternalServiceLogger) LogWarn(message string, fields ...zap.Field) { allFields := []zap.Field{zap.String("service", e.serviceName)} allFields = append(allFields, fields...) e.logger.Warn(message, allFields...) } // LogErrorWithFields 记录带字段的错误日志 func (e *ExternalServiceLogger) LogErrorWithFields(message string, fields ...zap.Field) { allFields := []zap.Field{zap.String("service", e.serviceName)} allFields = append(allFields, fields...) e.logger.Error(message, allFields...) } // Sync 同步日志 func (e *ExternalServiceLogger) Sync() error { return e.logger.Sync() } // CleanupOldDateDirs 清理过期的日期目录 func (e *ExternalServiceLogger) CleanupOldDateDirs() error { if !e.config.UseDaily { return nil } logDir := filepath.Join(e.config.LogDir, e.serviceName) // 读取日志目录 entries, err := os.ReadDir(logDir) if err != nil { return fmt.Errorf("读取日志目录失败: %w", err) } // 计算过期时间(基于配置的MaxAge) maxAge := 28 // 默认28天 if errorConfig, exists := e.config.LevelConfigs["error"]; exists && errorConfig.MaxAge > 0 { maxAge = errorConfig.MaxAge } cutoffTime := time.Now().AddDate(0, 0, -maxAge) for _, entry := range entries { if !entry.IsDir() { continue } // 尝试解析目录名为日期 dirName := entry.Name() dirTime, err := time.Parse("2006-01-02", dirName) if err != nil { // 如果不是日期格式的目录,跳过 continue } // 检查是否过期 if dirTime.Before(cutoffTime) { dirPath := filepath.Join(logDir, dirName) if err := os.RemoveAll(dirPath); err != nil { return fmt.Errorf("删除过期目录失败 %s: %w", dirPath, err) } } } return nil } // startCleanupTask 启动定时清理任务 func (e *ExternalServiceLogger) startCleanupTask() { // 每天凌晨2点执行清理 ticker := time.NewTicker(24 * time.Hour) defer ticker.Stop() // 等待到下一个凌晨2点 now := time.Now() next := time.Date(now.Year(), now.Month(), now.Day()+1, 2, 0, 0, 0, now.Location()) time.Sleep(time.Until(next)) // 立即执行一次清理 if err := e.CleanupOldDateDirs(); err != nil { // 记录清理错误(这里使用标准输出,因为logger可能还未初始化) fmt.Printf("清理过期日志目录失败: %v\n", err) } // 定时执行清理 for range ticker.C { if err := e.CleanupOldDateDirs(); err != nil { fmt.Printf("清理过期日志目录失败: %v\n", err) } } } // GetServiceName 获取服务名称 func (e *ExternalServiceLogger) GetServiceName() string { return e.serviceName }