change log
This commit is contained in:
@@ -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配置
|
||||||
# ===========================================
|
# ===========================================
|
||||||
|
|||||||
344
docs/按级别分文件日志系统说明.md
Normal file
344
docs/按级别分文件日志系统说明.md
Normal 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` - 依赖注入配置
|
||||||
@@ -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配置
|
||||||
|
|||||||
@@ -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客户端
|
||||||
|
|||||||
314
internal/shared/logger/level_logger.go
Normal file
314
internal/shared/logger/level_logger.go
Normal 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 ""
|
||||||
|
}
|
||||||
442
internal/shared/middleware/comprehensive_logger.go
Normal file
442
internal/shared/middleware/comprehensive_logger.go
Normal 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 ""
|
||||||
|
}
|
||||||
104
internal/shared/middleware/panic_recovery.go
Normal file
104
internal/shared/middleware/panic_recovery.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user