change log

This commit is contained in:
2025-07-28 15:21:37 +08:00
parent 610d0f5475
commit 560c81a8aa
7 changed files with 1351 additions and 12 deletions

View File

@@ -47,6 +47,50 @@ logger:
max_age: 30 max_age: 30
compress: true compress: true
use_daily: true use_daily: true
# 启用按级别分文件
enable_level_separation: true
# 各级别日志文件配置
level_configs:
debug:
max_size: 50 # 50MB
max_backups: 3
max_age: 7 # 7天
compress: true
info:
max_size: 100 # 100MB
max_backups: 5
max_age: 30 # 30天
compress: true
warn:
max_size: 100 # 100MB
max_backups: 5
max_age: 30 # 30天
compress: true
error:
max_size: 200 # 200MB
max_backups: 10
max_age: 90 # 90天
compress: true
fatal:
max_size: 100 # 100MB
max_backups: 10
max_age: 365 # 1年
compress: true
panic:
max_size: 100 # 100MB
max_backups: 10
max_age: 365 # 1年
compress: true
# 生产环境全面日志配置
comprehensive_logging:
enable_request_logging: true
enable_response_logging: true
enable_request_body_logging: false # 生产环境不记录请求体(安全考虑)
enable_error_logging: true
enable_business_logging: true
enable_performance_logging: true
max_body_size: 10240 # 10KB
exclude_paths: ["/health", "/metrics", "/favicon.ico", "/swagger"]
# =========================================== # ===========================================
# 🔐 JWT配置 # 🔐 JWT配置
# =========================================== # ===========================================

View File

@@ -0,0 +1,344 @@
# 📝 按级别分文件日志系统说明
## 概述
本系统支持按日志级别分文件存储,不同级别的日志写入不同的文件,便于日志管理和分析。
## 功能特性
### ✅ 按级别分文件
- **Debug级别**: `debug.log` - 调试信息
- **Info级别**: `info.log` - 一般信息
- **Warn级别**: `warn.log` - 警告信息
- **Error级别**: `error.log` - 错误信息
- **Fatal级别**: `fatal.log` - 致命错误
- **Panic级别**: `panic.log` - 恐慌错误
### ✅ 独立配置
- 每个级别可以独立配置文件大小、备份数量、保留天数
- 支持不同级别的压缩策略
- 灵活的轮转配置
### ✅ 按日分包支持
- 支持按日期分包:`logs/2024-01-01/error.log`
- 传统模式:`logs/error.log`
## 配置说明
### 基础配置
```yaml
logger:
# 基础配置
level: info
format: json
output: "file"
log_dir: "/app/logs"
use_daily: true
# 启用按级别分文件
enable_level_separation: true
# 各级别配置
level_configs:
debug:
max_size: 50 # 50MB
max_backups: 3
max_age: 7 # 7天
compress: true
info:
max_size: 100 # 100MB
max_backups: 5
max_age: 30 # 30天
compress: true
warn:
max_size: 100 # 100MB
max_backups: 5
max_age: 30 # 30天
compress: true
error:
max_size: 200 # 200MB
max_backups: 10
max_age: 90 # 90天
compress: true
fatal:
max_size: 100 # 100MB
max_backups: 10
max_age: 365 # 1年
compress: true
panic:
max_size: 100 # 100MB
max_backups: 10
max_age: 365 # 1年
compress: true
```
## 日志目录结构
### 按日分包模式
```
logs/
├── 2024-01-01/
│ ├── debug.log # 调试日志
│ ├── debug.log.1 # 调试日志备份
│ ├── info.log # 信息日志
│ ├── info.log.1 # 信息日志备份
│ ├── warn.log # 警告日志
│ ├── warn.log.1 # 警告日志备份
│ ├── error.log # 错误日志
│ ├── error.log.1 # 错误日志备份
│ ├── fatal.log # 致命错误日志
│ └── panic.log # 恐慌错误日志
├── 2024-01-02/
│ ├── debug.log
│ ├── info.log
│ ├── warn.log
│ ├── error.log
│ ├── fatal.log
│ └── panic.log
```
### 传统模式
```
logs/
├── debug.log # 调试日志
├── debug.log.1 # 调试日志备份
├── info.log # 信息日志
├── info.log.1 # 信息日志备份
├── warn.log # 警告日志
├── warn.log.1 # 警告日志备份
├── error.log # 错误日志
├── error.log.1 # 错误日志备份
├── fatal.log # 致命错误日志
└── panic.log # 恐慌错误日志
```
## 级别配置策略
### 1. Debug级别
- **用途**: 详细的调试信息
- **保留策略**: 短期保留7天
- **文件大小**: 较小50MB
- **备份数量**: 较少3个
### 2. Info级别
- **用途**: 一般业务信息
- **保留策略**: 中期保留30天
- **文件大小**: 中等100MB
- **备份数量**: 中等5个
### 3. Warn级别
- **用途**: 警告信息
- **保留策略**: 中期保留30天
- **文件大小**: 中等100MB
- **备份数量**: 中等5个
### 4. Error级别
- **用途**: 错误信息
- **保留策略**: 长期保留90天
- **文件大小**: 较大200MB
- **备份数量**: 较多10个
### 5. Fatal级别
- **用途**: 致命错误
- **保留策略**: 永久保留1年
- **文件大小**: 中等100MB
- **备份数量**: 较多10个
### 6. Panic级别
- **用途**: 恐慌错误
- **保留策略**: 永久保留1年
- **文件大小**: 中等100MB
- **备份数量**: 较多10个
## 使用方法
### 1. 基本使用
```go
import "tyapi-server/internal/shared/logger"
// 获取日志器
log := logger.GetLogger()
// 不同级别的日志会自动写入对应文件
log.Debug("调试信息", zap.String("component", "user_service"))
log.Info("用户登录成功", zap.String("user_id", "12345"))
log.Warn("用户多次登录失败", zap.String("phone", "138****8888"))
log.Error("数据库连接失败", zap.Error(err))
log.Fatal("系统无法启动", zap.Error(err))
log.Panic("严重错误", zap.Error(err))
```
### 2. 带上下文的日志
```go
// 从Gin上下文获取日志器
ctx := c.Request.Context()
log := logger.GetLogger().WithContext(ctx)
log.Info("处理用户请求",
zap.String("action", "user_login"),
zap.String("ip", c.ClientIP()),
)
```
### 3. 结构化日志字段
```go
log.Info("业务操作",
zap.String("operation", "create_user"),
zap.String("user_id", user.ID),
zap.Int("age", user.Age),
zap.Float64("score", 95.5),
zap.Bool("is_active", true),
zap.Error(err),
)
```
## 日志分析
### 1. 查看特定级别日志
```bash
# 查看错误日志
tail -f logs/2024-01-01/error.log
# 查看警告日志
tail -f logs/2024-01-01/warn.log
# 查看信息日志
tail -f logs/2024-01-01/info.log
```
### 2. 统计各级别日志数量
```bash
# 统计错误日志数量
wc -l logs/2024-01-01/error.log
# 统计警告日志数量
wc -l logs/2024-01-01/warn.log
# 统计信息日志数量
wc -l logs/2024-01-01/info.log
```
### 3. 搜索特定内容
```bash
# 在错误日志中搜索特定错误
grep "数据库连接失败" logs/2024-01-01/error.log
# 在警告日志中搜索特定警告
grep "用户多次登录失败" logs/2024-01-01/warn.log
# 在信息日志中搜索特定操作
grep "用户登录成功" logs/2024-01-01/info.log
```
### 4. 使用jq分析JSON日志
```bash
# 分析错误日志
cat logs/2024-01-01/error.log | jq 'select(.level == "error")'
# 统计错误类型
cat logs/2024-01-01/error.log | jq -r '.message' | sort | uniq -c
# 查看特定用户的错误
cat logs/2024-01-01/error.log | jq 'select(.user_id == "12345")'
```
## 监控和告警
### 1. 错误日志监控
```bash
# 监控错误日志增长
watch -n 5 'wc -l logs/$(date +%Y-%m-%d)/error.log'
# 监控错误日志大小
watch -n 5 'ls -lh logs/$(date +%Y-%m-%d)/error.log'
```
### 2. 告警配置
```bash
# 检查错误日志是否超过阈值
if [ $(wc -l < logs/$(date +%Y-%m-%d)/error.log) -gt 1000 ]; then
echo "错误日志数量过多,请检查系统状态"
fi
# 检查错误日志文件大小
if [ $(stat -c%s logs/$(date +%Y-%m-%d)/error.log) -gt 104857600 ]; then
echo "错误日志文件过大,请检查系统状态"
fi
```
## 性能优化
### 1. 异步写入
- 日志写入是异步的,不会阻塞业务逻辑
- 使用缓冲写入减少I/O操作
### 2. 级别过滤
- 只记录配置的级别及以上级别的日志
- 减少不必要的日志输出
### 3. 文件轮转
- 自动轮转避免单个文件过大
- 压缩旧日志文件节省磁盘空间
## 最佳实践
### 1. 级别使用规范
- **Debug**: 详细的调试信息(开发环境)
- **Info**: 重要的业务信息(生产环境)
- **Warn**: 需要关注但不影响主功能的问题
- **Error**: 影响功能的错误信息
- **Fatal**: 系统无法继续运行的错误
- **Panic**: 程序崩溃的错误
### 2. 日志内容规范
- 记录关键业务操作
- 包含足够的上下文信息
- 使用结构化的字段
- 避免记录敏感信息
### 3. 文件管理规范
- 定期清理旧日志文件
- 监控日志文件大小
- 设置合理的轮转策略
- 备份重要日志
## 故障排除
### 1. 常见问题
**问题**: 某个级别的日志文件没有创建
**解决**: 检查该级别是否在配置中定义
**问题**: 日志文件权限问题
**解决**: 确保应用有写入日志目录的权限
**问题**: 磁盘空间不足
**解决**: 调整日志保留策略,减少保留天数
### 2. 性能问题
**问题**: 日志写入影响性能
**解决**: 检查是否启用了异步写入
**问题**: 日志文件过大
**解决**: 调整文件大小限制和轮转策略
## 相关文件
- `internal/shared/logger/level_logger.go` - 按级别分文件日志器实现
- `internal/config/config.go` - 日志配置结构
- `configs/env.production.yaml` - 生产环境日志配置
- `internal/container/container.go` - 依赖注入配置

View File

@@ -90,6 +90,17 @@ type LoggerConfig struct {
Compress bool `mapstructure:"compress"` // 是否压缩 Compress bool `mapstructure:"compress"` // 是否压缩
UseColor bool `mapstructure:"use_color"` // 是否使用彩色输出 UseColor bool `mapstructure:"use_color"` // 是否使用彩色输出
UseDaily bool `mapstructure:"use_daily"` // 是否按日分包 UseDaily bool `mapstructure:"use_daily"` // 是否按日分包
// 按级别分文件配置
EnableLevelSeparation bool `mapstructure:"enable_level_separation"` // 是否启用按级别分文件
LevelConfigs map[string]LevelFileConfig `mapstructure:"level_configs"` // 各级别配置
}
// LevelFileConfig 单个级别文件配置
type LevelFileConfig struct {
MaxSize int `mapstructure:"max_size"` // 单个文件最大大小(MB)
MaxBackups int `mapstructure:"max_backups"` // 最大备份文件数
MaxAge int `mapstructure:"max_age"` // 最大保留天数
Compress bool `mapstructure:"compress"` // 是否压缩
} }
// JWTConfig JWT配置 // JWTConfig JWT配置

View File

@@ -6,6 +6,7 @@ import (
"go.uber.org/fx" "go.uber.org/fx"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gorm.io/gorm" "gorm.io/gorm"
"tyapi-server/internal/application/certification" "tyapi-server/internal/application/certification"
@@ -75,8 +76,28 @@ func NewContainer() *Container {
// 基础设施模块 // 基础设施模块
fx.Provide( fx.Provide(
// 日志器 - 提供自定义Logger和*zap.Logger // 日志器 - 提供自定义Logger和*zap.Logger
func(cfg *config.Config) (logger.Logger, error) { func(cfg *config.Config) (logger.Logger, error) {
if cfg.Logger.EnableLevelSeparation {
// 使用按级别分文件的日志器
levelConfig := logger.LevelLoggerConfig{
BaseConfig: logger.Config{
Level: cfg.Logger.Level,
Format: cfg.Logger.Format,
Output: cfg.Logger.Output,
LogDir: cfg.Logger.LogDir,
MaxSize: cfg.Logger.MaxSize,
MaxBackups: cfg.Logger.MaxBackups,
MaxAge: cfg.Logger.MaxAge,
Compress: cfg.Logger.Compress,
UseDaily: cfg.Logger.UseDaily,
},
EnableLevelSeparation: true,
LevelConfigs: convertLevelConfigs(cfg.Logger.LevelConfigs),
}
return logger.NewLevelLogger(levelConfig)
} else {
// 使用普通日志器
logCfg := logger.Config{ logCfg := logger.Config{
Level: cfg.Logger.Level, Level: cfg.Logger.Level,
Format: cfg.Logger.Format, Format: cfg.Logger.Format,
@@ -89,16 +110,21 @@ func NewContainer() *Container {
UseDaily: cfg.Logger.UseDaily, UseDaily: cfg.Logger.UseDaily,
} }
return logger.NewLogger(logCfg) return logger.NewLogger(logCfg)
}, }
// 提供普通的*zap.Logger用于大多数场景 },
func(log logger.Logger) *zap.Logger { // 提供普通的*zap.Logger用于大多数场景
if zapLogger, ok := log.(*logger.ZapLogger); ok { func(log logger.Logger) *zap.Logger {
return zapLogger.GetZapLogger() if zapLogger, ok := log.(*logger.ZapLogger); ok {
} return zapLogger.GetZapLogger()
// 如果类型转换失败创建一个默认的zap logger }
defaultLogger, _ := zap.NewProduction() // 如果类型转换失败创建一个默认的zap logger
return defaultLogger defaultLogger, _ := zap.NewProduction()
}, return defaultLogger
},
// 提供业务日志记录器
func(logger *zap.Logger) *middleware.BusinessLogger {
return middleware.NewBusinessLogger(logger)
},
// 数据库连接 // 数据库连接
func(cfg *config.Config, cacheService interfaces.CacheService, logger *zap.Logger) (*gorm.DB, error) { func(cfg *config.Config, cacheService interfaces.CacheService, logger *zap.Logger) (*gorm.DB, error) {
dbCfg := database.Config{ dbCfg := database.Config{
@@ -532,6 +558,8 @@ func RegisterLifecycleHooks(
// RegisterMiddlewares 注册中间件 // RegisterMiddlewares 注册中间件
func RegisterMiddlewares( func RegisterMiddlewares(
router *sharedhttp.GinRouter, router *sharedhttp.GinRouter,
panicRecovery *middleware.PanicRecoveryMiddleware,
comprehensiveLogger *middleware.ComprehensiveLoggerMiddleware,
requestID *middleware.RequestIDMiddleware, requestID *middleware.RequestIDMiddleware,
security *middleware.SecurityHeadersMiddleware, security *middleware.SecurityHeadersMiddleware,
responseTime *middleware.ResponseTimeMiddleware, responseTime *middleware.ResponseTimeMiddleware,
@@ -542,6 +570,9 @@ func RegisterMiddlewares(
errorTrackingMiddleware *middleware.ErrorTrackingMiddleware, errorTrackingMiddleware *middleware.ErrorTrackingMiddleware,
requestBodyLogger *middleware.RequestBodyLoggerMiddleware, requestBodyLogger *middleware.RequestBodyLoggerMiddleware,
) { ) {
// 注册所有中间件(按优先级顺序)
router.RegisterMiddleware(panicRecovery)
router.RegisterMiddleware(comprehensiveLogger)
router.RegisterMiddleware(requestID) router.RegisterMiddleware(requestID)
router.RegisterMiddleware(security) router.RegisterMiddleware(security)
router.RegisterMiddleware(responseTime) router.RegisterMiddleware(responseTime)
@@ -593,6 +624,26 @@ func RegisterRoutes(
// ================ 中间件包装函数 ================ // ================ 中间件包装函数 ================
// NewPanicRecoveryMiddlewareWrapper 创建Panic恢复中间件包装器
func NewPanicRecoveryMiddlewareWrapper(logger *zap.Logger) *middleware.PanicRecoveryMiddleware {
return middleware.NewPanicRecoveryMiddleware(logger)
}
// NewComprehensiveLoggerMiddlewareWrapper 创建全面日志中间件包装器
func NewComprehensiveLoggerMiddlewareWrapper(logger *zap.Logger, cfg *config.Config) *middleware.ComprehensiveLoggerMiddleware {
config := &middleware.ComprehensiveLoggerConfig{
EnableRequestLogging: true,
EnableResponseLogging: true,
EnableRequestBodyLogging: cfg.App.IsDevelopment(), // 开发环境记录请求体
EnableErrorLogging: true,
EnableBusinessLogging: true,
EnablePerformanceLogging: true,
MaxBodySize: 1024 * 10, // 10KB
ExcludePaths: []string{"/health", "/metrics", "/favicon.ico", "/swagger"},
}
return middleware.NewComprehensiveLoggerMiddleware(logger, config)
}
// NewRequestLoggerMiddlewareWrapper 创建请求日志中间件包装器 // NewRequestLoggerMiddlewareWrapper 创建请求日志中间件包装器
func NewRequestLoggerMiddlewareWrapper(logger *zap.Logger, cfg *config.Config, tracer *tracing.Tracer) *middleware.RequestLoggerMiddleware { func NewRequestLoggerMiddlewareWrapper(logger *zap.Logger, cfg *config.Config, tracer *tracing.Tracer) *middleware.RequestLoggerMiddleware {
return middleware.NewRequestLoggerMiddleware(logger, cfg.App.IsDevelopment(), tracer) return middleware.NewRequestLoggerMiddleware(logger, cfg.App.IsDevelopment(), tracer)
@@ -603,6 +654,35 @@ func NewRequestBodyLoggerMiddlewareWrapper(logger *zap.Logger, cfg *config.Confi
return middleware.NewRequestBodyLoggerMiddleware(logger, cfg.App.IsDevelopment(), tracer) return middleware.NewRequestBodyLoggerMiddleware(logger, cfg.App.IsDevelopment(), tracer)
} }
// ================ 辅助函数 ================
// convertLevelConfigs 转换级别配置
func convertLevelConfigs(configs map[string]config.LevelFileConfig) map[zapcore.Level]logger.LevelFileConfig {
result := make(map[zapcore.Level]logger.LevelFileConfig)
levelMap := map[string]zapcore.Level{
"debug": zapcore.DebugLevel,
"info": zapcore.InfoLevel,
"warn": zapcore.WarnLevel,
"error": zapcore.ErrorLevel,
"fatal": zapcore.FatalLevel,
"panic": zapcore.PanicLevel,
}
for levelStr, config := range configs {
if level, exists := levelMap[levelStr]; exists {
result[level] = logger.LevelFileConfig{
MaxSize: config.MaxSize,
MaxBackups: config.MaxBackups,
MaxAge: config.MaxAge,
Compress: config.Compress,
}
}
}
return result
}
// ================ Redis相关工厂函数 ================ // ================ Redis相关工厂函数 ================
// NewRedisClient 创建Redis客户端 // NewRedisClient 创建Redis客户端

View File

@@ -0,0 +1,314 @@
package logger
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
// LevelLoggerConfig 按级别分文件的日志配置
type LevelLoggerConfig struct {
BaseConfig Config
// 是否启用按级别分文件
EnableLevelSeparation bool
// 各级别日志文件配置
LevelConfigs map[zapcore.Level]LevelFileConfig
}
// LevelFileConfig 单个级别文件配置
type LevelFileConfig struct {
MaxSize int // 单个文件最大大小(MB)
MaxBackups int // 最大备份文件数
MaxAge int // 最大保留天数
Compress bool // 是否压缩
}
// LevelLogger 按级别分文件的日志器
type LevelLogger struct {
loggers map[zapcore.Level]*zap.Logger
config LevelLoggerConfig
}
// NewLevelLogger 创建按级别分文件的日志器
func NewLevelLogger(config LevelLoggerConfig) (*LevelLogger, error) {
if !config.EnableLevelSeparation {
// 如果不启用按级别分文件,使用普通日志器
normalLogger, err := NewLogger(config.BaseConfig)
if err != nil {
return nil, err
}
// 转换为LevelLogger格式
zapLogger := normalLogger.(*ZapLogger).GetZapLogger()
return &LevelLogger{
loggers: map[zapcore.Level]*zap.Logger{
zapcore.DebugLevel: zapLogger,
zapcore.InfoLevel: zapLogger,
zapcore.WarnLevel: zapLogger,
zapcore.ErrorLevel: zapLogger,
zapcore.FatalLevel: zapLogger,
zapcore.PanicLevel: zapLogger,
},
config: config,
}, nil
}
// 设置默认级别配置
if config.LevelConfigs == nil {
config.LevelConfigs = getDefaultLevelConfigs()
}
// 确保日志目录存在
if err := os.MkdirAll(config.BaseConfig.LogDir, 0755); err != nil {
return nil, fmt.Errorf("创建日志目录失败: %w", err)
}
// 为每个级别创建独立的日志器
loggers := make(map[zapcore.Level]*zap.Logger)
for level := range config.LevelConfigs {
logger, err := createLevelLogger(level, config)
if err != nil {
return nil, fmt.Errorf("创建级别日志器失败 [%s]: %w", level.String(), err)
}
loggers[level] = logger
}
return &LevelLogger{
loggers: loggers,
config: config,
}, nil
}
// getDefaultLevelConfigs 获取默认级别配置
func getDefaultLevelConfigs() map[zapcore.Level]LevelFileConfig {
return map[zapcore.Level]LevelFileConfig{
zapcore.DebugLevel: {
MaxSize: 50, // 50MB
MaxBackups: 3,
MaxAge: 7, // 7天
Compress: true,
},
zapcore.InfoLevel: {
MaxSize: 100, // 100MB
MaxBackups: 5,
MaxAge: 30, // 30天
Compress: true,
},
zapcore.WarnLevel: {
MaxSize: 100, // 100MB
MaxBackups: 5,
MaxAge: 30, // 30天
Compress: true,
},
zapcore.ErrorLevel: {
MaxSize: 200, // 200MB
MaxBackups: 10,
MaxAge: 90, // 90天
Compress: true,
},
zapcore.FatalLevel: {
MaxSize: 100, // 100MB
MaxBackups: 10,
MaxAge: 365, // 1年
Compress: true,
},
zapcore.PanicLevel: {
MaxSize: 100, // 100MB
MaxBackups: 10,
MaxAge: 365, // 1年
Compress: true,
},
}
}
// createLevelLogger 为单个级别创建日志器
func createLevelLogger(level zapcore.Level, config LevelLoggerConfig) (*zap.Logger, error) {
levelConfig := config.LevelConfigs[level]
// 创建编码器
encoderConfig := getEncoderConfig()
var encoder zapcore.Encoder
if config.BaseConfig.Format == "json" {
encoder = zapcore.NewJSONEncoder(encoderConfig)
} else {
encoder = zapcore.NewConsoleEncoder(encoderConfig)
}
// 创建文件输出
writeSyncer, err := createLevelFileWriteSyncer(level, config.BaseConfig, levelConfig)
if err != nil {
return nil, err
}
// 创建核心
core := zapcore.NewCore(encoder, writeSyncer, level)
// 创建日志器
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
return logger, nil
}
// createLevelFileWriteSyncer 创建级别文件输出同步器
func createLevelFileWriteSyncer(level zapcore.Level, baseConfig Config, levelConfig LevelFileConfig) (zapcore.WriteSyncer, error) {
// 构建日志文件路径
var logFilePath string
if baseConfig.UseDaily {
// 按日分包logs/2024-01-01/error.log
today := time.Now().Format("2006-01-02")
dailyDir := filepath.Join(baseConfig.LogDir, today)
if err := os.MkdirAll(dailyDir, 0755); err != nil {
return nil, fmt.Errorf("创建日期目录失败: %w", err)
}
logFilePath = filepath.Join(dailyDir, fmt.Sprintf("%s.log", level.String()))
} else {
// 传统方式logs/error.log
logFilePath = filepath.Join(baseConfig.LogDir, fmt.Sprintf("%s.log", level.String()))
}
// 创建lumberjack日志轮转器
lumberJackLogger := &lumberjack.Logger{
Filename: logFilePath,
MaxSize: levelConfig.MaxSize,
MaxBackups: levelConfig.MaxBackups,
MaxAge: levelConfig.MaxAge,
Compress: levelConfig.Compress,
}
return zapcore.AddSync(lumberJackLogger), nil
}
// Debug 调试日志
func (l *LevelLogger) Debug(msg string, fields ...zapcore.Field) {
if logger, exists := l.loggers[zapcore.DebugLevel]; exists {
logger.Debug(msg, fields...)
}
}
// Info 信息日志
func (l *LevelLogger) Info(msg string, fields ...zapcore.Field) {
if logger, exists := l.loggers[zapcore.InfoLevel]; exists {
logger.Info(msg, fields...)
}
}
// Warn 警告日志
func (l *LevelLogger) Warn(msg string, fields ...zapcore.Field) {
if logger, exists := l.loggers[zapcore.WarnLevel]; exists {
logger.Warn(msg, fields...)
}
}
// Error 错误日志
func (l *LevelLogger) Error(msg string, fields ...zapcore.Field) {
if logger, exists := l.loggers[zapcore.ErrorLevel]; exists {
logger.Error(msg, fields...)
}
}
// Fatal 致命错误日志
func (l *LevelLogger) Fatal(msg string, fields ...zapcore.Field) {
if logger, exists := l.loggers[zapcore.FatalLevel]; exists {
logger.Fatal(msg, fields...)
}
}
// Panic 恐慌日志
func (l *LevelLogger) Panic(msg string, fields ...zapcore.Field) {
if logger, exists := l.loggers[zapcore.PanicLevel]; exists {
logger.Panic(msg, fields...)
}
}
// With 添加字段
func (l *LevelLogger) With(fields ...zapcore.Field) Logger {
// 为每个级别创建带字段的日志器
newLoggers := make(map[zapcore.Level]*zap.Logger)
for level, logger := range l.loggers {
newLoggers[level] = logger.With(fields...)
}
return &LevelLogger{
loggers: newLoggers,
config: l.config,
}
}
// WithContext 从上下文添加字段
func (l *LevelLogger) WithContext(ctx context.Context) Logger {
// 从上下文中提取常用字段
fields := []zapcore.Field{}
if traceID := getTraceIDFromContextLevel(ctx); traceID != "" {
fields = append(fields, zap.String("trace_id", traceID))
}
if userID := getUserIDFromContextLevel(ctx); userID != "" {
fields = append(fields, zap.String("user_id", userID))
}
if requestID := getRequestIDFromContextLevel(ctx); requestID != "" {
fields = append(fields, zap.String("request_id", requestID))
}
return l.With(fields...)
}
// Sync 同步日志
func (l *LevelLogger) Sync() error {
var lastErr error
for _, logger := range l.loggers {
if err := logger.Sync(); err != nil {
lastErr = err
}
}
return lastErr
}
// GetLevelLogger 获取指定级别的日志器
func (l *LevelLogger) GetLevelLogger(level zapcore.Level) *zap.Logger {
if logger, exists := l.loggers[level]; exists {
return logger
}
return nil
}
// GetLoggers 获取所有级别的日志器
func (l *LevelLogger) GetLoggers() map[zapcore.Level]*zap.Logger {
return l.loggers
}
// 辅助函数从logger.go复制
func getTraceIDFromContextLevel(ctx context.Context) string {
if traceID := ctx.Value("trace_id"); traceID != nil {
if id, ok := traceID.(string); ok {
return id
}
}
return ""
}
func getUserIDFromContextLevel(ctx context.Context) string {
if userID := ctx.Value("user_id"); userID != nil {
if id, ok := userID.(string); ok {
return id
}
}
return ""
}
func getRequestIDFromContextLevel(ctx context.Context) string {
if requestID := ctx.Value("request_id"); requestID != nil {
if id, ok := requestID.(string); ok {
return id
}
}
return ""
}

View File

@@ -0,0 +1,442 @@
package middleware
import (
"bytes"
"context"
"io"
"strings"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// ComprehensiveLoggerMiddleware 全面日志中间件
type ComprehensiveLoggerMiddleware struct {
logger *zap.Logger
config *ComprehensiveLoggerConfig
}
// ComprehensiveLoggerConfig 全面日志配置
type ComprehensiveLoggerConfig struct {
EnableRequestLogging bool // 是否记录请求日志
EnableResponseLogging bool // 是否记录响应日志
EnableRequestBodyLogging bool // 是否记录请求体
EnableErrorLogging bool // 是否记录错误日志
EnableBusinessLogging bool // 是否记录业务日志
EnablePerformanceLogging bool // 是否记录性能日志
MaxBodySize int64 // 最大记录体大小
ExcludePaths []string // 排除的路径
IncludePaths []string // 包含的路径
}
// NewComprehensiveLoggerMiddleware 创建全面日志中间件
func NewComprehensiveLoggerMiddleware(logger *zap.Logger, config *ComprehensiveLoggerConfig) *ComprehensiveLoggerMiddleware {
if config == nil {
config = &ComprehensiveLoggerConfig{
EnableRequestLogging: true,
EnableResponseLogging: true,
EnableRequestBodyLogging: false, // 生产环境默认关闭
EnableErrorLogging: true,
EnableBusinessLogging: true,
EnablePerformanceLogging: true,
MaxBodySize: 1024 * 10, // 10KB
ExcludePaths: []string{"/health", "/metrics", "/favicon.ico"},
}
}
return &ComprehensiveLoggerMiddleware{
logger: logger,
config: config,
}
}
// GetName 返回中间件名称
func (m *ComprehensiveLoggerMiddleware) GetName() string {
return "comprehensive_logger"
}
// GetPriority 返回中间件优先级
func (m *ComprehensiveLoggerMiddleware) GetPriority() int {
return 90 // 高优先级在panic恢复之后
}
// Handle 返回中间件处理函数
func (m *ComprehensiveLoggerMiddleware) Handle() gin.HandlerFunc {
return func(c *gin.Context) {
// 检查是否应该记录此路径
if !m.shouldLogPath(c.Request.URL.Path) {
c.Next()
return
}
startTime := time.Now()
requestID := c.GetString("request_id")
traceID := c.GetString("trace_id")
userID := c.GetString("user_id")
// 记录请求开始
if m.config.EnableRequestLogging {
m.logRequest(c, startTime, requestID, traceID, userID)
}
// 捕获请求体(如果需要)
var requestBody []byte
if m.config.EnableRequestBodyLogging && m.shouldLogRequestBody(c) {
requestBody = m.captureRequestBody(c)
}
// 创建响应写入器包装器
responseWriter := &responseWriterWrapper{
ResponseWriter: c.Writer,
logger: m.logger,
config: m.config,
requestID: requestID,
traceID: traceID,
userID: userID,
startTime: startTime,
path: c.Request.URL.Path,
method: c.Request.Method,
}
c.Writer = responseWriter
// 处理请求
c.Next()
// 记录响应
if m.config.EnableResponseLogging {
m.logResponse(c, responseWriter, startTime, requestID, traceID, userID, requestBody)
}
// 记录错误
if m.config.EnableErrorLogging && len(c.Errors) > 0 {
m.logErrors(c, requestID, traceID, userID)
}
// 记录性能指标
if m.config.EnablePerformanceLogging {
m.logPerformance(c, startTime, requestID, traceID, userID)
}
}
}
// IsGlobal 是否为全局中间件
func (m *ComprehensiveLoggerMiddleware) IsGlobal() bool {
return true
}
// shouldLogPath 检查是否应该记录此路径
func (m *ComprehensiveLoggerMiddleware) shouldLogPath(path string) bool {
// 检查排除路径
for _, excludePath := range m.config.ExcludePaths {
if strings.HasPrefix(path, excludePath) {
return false
}
}
// 检查包含路径(如果指定了)
if len(m.config.IncludePaths) > 0 {
for _, includePath := range m.config.IncludePaths {
if strings.HasPrefix(path, includePath) {
return true
}
}
return false
}
return true
}
// shouldLogRequestBody 检查是否应该记录请求体
func (m *ComprehensiveLoggerMiddleware) shouldLogRequestBody(c *gin.Context) bool {
contentType := c.GetHeader("Content-Type")
return strings.Contains(contentType, "application/json") ||
strings.Contains(contentType, "application/x-www-form-urlencoded")
}
// captureRequestBody 捕获请求体
func (m *ComprehensiveLoggerMiddleware) captureRequestBody(c *gin.Context) []byte {
if c.Request.Body == nil {
return nil
}
body, err := io.ReadAll(c.Request.Body)
if err != nil {
return nil
}
// 重新设置body
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
// 限制大小
if int64(len(body)) > m.config.MaxBodySize {
return body[:m.config.MaxBodySize]
}
return body
}
// logRequest 记录请求日志
func (m *ComprehensiveLoggerMiddleware) logRequest(c *gin.Context, startTime time.Time, requestID, traceID, userID string) {
logFields := []zap.Field{
zap.String("log_type", "request"),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.String("query", c.Request.URL.RawQuery),
zap.String("client_ip", c.ClientIP()),
zap.String("user_agent", c.Request.UserAgent()),
zap.String("referer", c.Request.Referer()),
zap.Int64("content_length", c.Request.ContentLength),
zap.String("content_type", c.GetHeader("Content-Type")),
zap.Time("timestamp", startTime),
}
m.logger.Info("收到HTTP请求", logFields...)
}
// logResponse 记录响应日志
func (m *ComprehensiveLoggerMiddleware) logResponse(c *gin.Context, responseWriter *responseWriterWrapper, startTime time.Time, requestID, traceID, userID string, requestBody []byte) {
duration := time.Since(startTime)
statusCode := responseWriter.Status()
logFields := []zap.Field{
zap.String("log_type", "response"),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.Int("status_code", statusCode),
zap.Duration("duration", duration),
zap.Int("response_size", responseWriter.Size()),
zap.Time("timestamp", time.Now()),
}
// 添加请求体(如果记录了)
if len(requestBody) > 0 {
logFields = append(logFields, zap.String("request_body", string(requestBody)))
}
// 根据状态码选择日志级别
if statusCode >= 500 {
m.logger.Error("HTTP响应错误", logFields...)
} else if statusCode >= 400 {
m.logger.Warn("HTTP响应警告", logFields...)
} else {
m.logger.Info("HTTP响应成功", logFields...)
}
}
// logErrors 记录错误日志
func (m *ComprehensiveLoggerMiddleware) logErrors(c *gin.Context, requestID, traceID, userID string) {
for _, ginErr := range c.Errors {
logFields := []zap.Field{
zap.String("log_type", "error"),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.String("error_type", string(ginErr.Type)),
zap.Error(ginErr.Err),
zap.Time("timestamp", time.Now()),
}
m.logger.Error("请求处理错误", logFields...)
}
}
// logPerformance 记录性能日志
func (m *ComprehensiveLoggerMiddleware) logPerformance(c *gin.Context, startTime time.Time, requestID, traceID, userID string) {
duration := time.Since(startTime)
// 记录慢请求
if duration > 1*time.Second {
logFields := []zap.Field{
zap.String("log_type", "performance"),
zap.String("performance_type", "slow_request"),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.Duration("duration", duration),
zap.Time("timestamp", time.Now()),
}
m.logger.Warn("检测到慢请求", logFields...)
}
// 记录性能指标
logFields := []zap.Field{
zap.String("log_type", "performance"),
zap.String("performance_type", "request_metrics"),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.Duration("duration", duration),
zap.Time("timestamp", time.Now()),
}
m.logger.Debug("请求性能指标", logFields...)
}
// responseWriterWrapper 响应写入器包装器
type responseWriterWrapper struct {
gin.ResponseWriter
logger *zap.Logger
config *ComprehensiveLoggerConfig
requestID string
traceID string
userID string
startTime time.Time
path string
method string
status int
size int
}
// Write 实现Write方法
func (w *responseWriterWrapper) Write(b []byte) (int, error) {
size, err := w.ResponseWriter.Write(b)
w.size += size
return size, err
}
// WriteHeader 实现WriteHeader方法
func (w *responseWriterWrapper) WriteHeader(code int) {
w.status = code
w.ResponseWriter.WriteHeader(code)
}
// WriteString 实现WriteString方法
func (w *responseWriterWrapper) WriteString(s string) (int, error) {
size, err := w.ResponseWriter.WriteString(s)
w.size += size
return size, err
}
// Status 获取状态码
func (w *responseWriterWrapper) Status() int {
return w.status
}
// Size 获取响应大小
func (w *responseWriterWrapper) Size() int {
return w.size
}
// BusinessLogger 业务日志记录器
type BusinessLogger struct {
logger *zap.Logger
}
// NewBusinessLogger 创建业务日志记录器
func NewBusinessLogger(logger *zap.Logger) *BusinessLogger {
return &BusinessLogger{
logger: logger,
}
}
// LogUserAction 记录用户操作
func (bl *BusinessLogger) LogUserAction(ctx context.Context, action string, details map[string]interface{}) {
requestID := bl.getRequestIDFromContext(ctx)
traceID := bl.getTraceIDFromContext(ctx)
userID := bl.getUserIDFromContext(ctx)
logFields := []zap.Field{
zap.String("log_type", "business"),
zap.String("business_type", "user_action"),
zap.String("action", action),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.Time("timestamp", time.Now()),
}
// 添加详细信息
for key, value := range details {
logFields = append(logFields, zap.Any(key, value))
}
bl.logger.Info("用户操作记录", logFields...)
}
// LogBusinessEvent 记录业务事件
func (bl *BusinessLogger) LogBusinessEvent(ctx context.Context, event string, details map[string]interface{}) {
requestID := bl.getRequestIDFromContext(ctx)
traceID := bl.getTraceIDFromContext(ctx)
userID := bl.getUserIDFromContext(ctx)
logFields := []zap.Field{
zap.String("log_type", "business"),
zap.String("business_type", "business_event"),
zap.String("event", event),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.Time("timestamp", time.Now()),
}
// 添加详细信息
for key, value := range details {
logFields = append(logFields, zap.Any(key, value))
}
bl.logger.Info("业务事件记录", logFields...)
}
// LogSystemEvent 记录系统事件
func (bl *BusinessLogger) LogSystemEvent(ctx context.Context, event string, details map[string]interface{}) {
requestID := bl.getRequestIDFromContext(ctx)
traceID := bl.getTraceIDFromContext(ctx)
logFields := []zap.Field{
zap.String("log_type", "business"),
zap.String("business_type", "system_event"),
zap.String("event", event),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.Time("timestamp", time.Now()),
}
// 添加详细信息
for key, value := range details {
logFields = append(logFields, zap.Any(key, value))
}
bl.logger.Info("系统事件记录", logFields...)
}
// 辅助方法
func (bl *BusinessLogger) getRequestIDFromContext(ctx context.Context) string {
if requestID := ctx.Value("request_id"); requestID != nil {
if id, ok := requestID.(string); ok {
return id
}
}
return ""
}
func (bl *BusinessLogger) getTraceIDFromContext(ctx context.Context) string {
if traceID := ctx.Value("trace_id"); traceID != nil {
if id, ok := traceID.(string); ok {
return id
}
}
return ""
}
func (bl *BusinessLogger) getUserIDFromContext(ctx context.Context) string {
if userID := ctx.Value("user_id"); userID != nil {
if id, ok := userID.(string); ok {
return id
}
}
return ""
}

View File

@@ -0,0 +1,104 @@
package middleware
import (
"net/http"
"runtime/debug"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// PanicRecoveryMiddleware Panic恢复中间件
type PanicRecoveryMiddleware struct {
logger *zap.Logger
}
// NewPanicRecoveryMiddleware 创建Panic恢复中间件
func NewPanicRecoveryMiddleware(logger *zap.Logger) *PanicRecoveryMiddleware {
return &PanicRecoveryMiddleware{
logger: logger,
}
}
// GetName 返回中间件名称
func (m *PanicRecoveryMiddleware) GetName() string {
return "panic_recovery"
}
// GetPriority 返回中间件优先级
func (m *PanicRecoveryMiddleware) GetPriority() int {
return 100 // 最高优先级,第一个执行
}
// Handle 返回中间件处理函数
func (m *PanicRecoveryMiddleware) Handle() gin.HandlerFunc {
return gin.RecoveryWithWriter(&panicLogger{logger: m.logger})
}
// IsGlobal 是否为全局中间件
func (m *PanicRecoveryMiddleware) IsGlobal() bool {
return true
}
// panicLogger 实现io.Writer接口用于记录panic信息
type panicLogger struct {
logger *zap.Logger
}
// Write 实现io.Writer接口
func (pl *panicLogger) Write(p []byte) (n int, err error) {
pl.logger.Error("系统发生严重错误",
zap.String("error_type", "panic"),
zap.String("stack_trace", string(p)),
zap.String("timestamp", time.Now().Format("2006-01-02 15:04:05")),
)
return len(p), nil
}
// CustomPanicRecovery 自定义panic恢复中间件
func (m *PanicRecoveryMiddleware) CustomPanicRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 获取请求信息
requestID := c.GetString("request_id")
traceID := c.GetString("trace_id")
userID := c.GetString("user_id")
clientIP := c.ClientIP()
method := c.Request.Method
path := c.Request.URL.Path
userAgent := c.Request.UserAgent()
// 记录详细的panic信息
m.logger.Error("系统发生严重错误",
zap.Any("panic_error", err),
zap.String("error_type", "panic"),
zap.String("request_id", requestID),
zap.String("trace_id", traceID),
zap.String("user_id", userID),
zap.String("client_ip", clientIP),
zap.String("method", method),
zap.String("path", path),
zap.String("user_agent", userAgent),
zap.String("stack_trace", string(debug.Stack())),
zap.String("timestamp", time.Now().Format("2006-01-02 15:04:05")),
)
// 返回500错误响应
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "服务器内部错误",
"error_code": "INTERNAL_SERVER_ERROR",
"request_id": requestID,
"timestamp": time.Now().Unix(),
})
// 中止请求处理
c.Abort()
}
}()
c.Next()
}
}