add JRZQ09J8、FLXGDEA8、FLXGDEA9、JRZQ1D09
add external_services log
This commit is contained in:
264
internal/shared/external_logger/README.md
Normal file
264
internal/shared/external_logger/README.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# 通用外部服务日志系统
|
||||
|
||||
## 概述
|
||||
|
||||
这是一个为外部服务(如 westdex、zhicha、yushan 等)提供统一日志记录功能的通用系统。所有外部服务共享相同的日志基础架构,但保持各自独立的日志文件目录。
|
||||
|
||||
## 设计目标
|
||||
|
||||
1. **代码复用**: 避免重复的日志实现代码
|
||||
2. **统一格式**: 所有外部服务使用相同的日志格式
|
||||
3. **独立存储**: 每个服务的日志存储在独立目录中
|
||||
4. **灵活配置**: 支持每个服务独立的日志配置
|
||||
5. **易于扩展**: 新增外部服务时只需简单配置
|
||||
|
||||
## 架构特点
|
||||
|
||||
### 1. 共享核心
|
||||
- 统一的日志接口
|
||||
- 相同的日志格式
|
||||
- 一致的配置结构
|
||||
- 共用的文件轮转策略
|
||||
|
||||
### 2. 服务分离
|
||||
- 每个服务有独立的日志目录
|
||||
- 通过 `service` 字段区分来源
|
||||
- 可独立配置每个服务的日志参数
|
||||
- 支持按级别分离日志文件
|
||||
|
||||
### 3. 配置灵活
|
||||
- 支持从配置文件读取
|
||||
- 支持自定义配置创建
|
||||
- 支持简单模式(无日志)
|
||||
- 支持日志级别分离
|
||||
|
||||
## 已集成的服务
|
||||
|
||||
### 1. WestDex (西部数据)
|
||||
- 服务名称: `westdex`
|
||||
- 日志目录: `logs/external_services/westdex/`
|
||||
- 主要功能: 企业信息查询
|
||||
|
||||
### 2. Zhicha (智查金控)
|
||||
- 服务名称: `zhicha`
|
||||
- 日志目录: `logs/external_services/zhicha/`
|
||||
- 主要功能: 企业信息查询、AES加密
|
||||
|
||||
### 3. Yushan (羽山)
|
||||
- 服务名称: `yushan`
|
||||
- 日志目录: `logs/external_services/yushan/`
|
||||
- 主要功能: 企业信息查询、AES加密
|
||||
|
||||
## 日志格式
|
||||
|
||||
所有服务的日志都包含以下标准字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"level": "INFO",
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"msg": "服务名 API请求",
|
||||
"service": "服务名",
|
||||
"request_id": "服务名_唯一ID",
|
||||
"api_code": "API代码",
|
||||
"url": "请求URL",
|
||||
"params": "请求参数",
|
||||
"status_code": "响应状态码",
|
||||
"response": "响应内容",
|
||||
"duration": "请求耗时",
|
||||
"error": "错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
## 配置结构
|
||||
|
||||
```yaml
|
||||
# 外部服务日志根目录
|
||||
external_services_log_dir: "./logs/external_services"
|
||||
|
||||
# 各服务配置
|
||||
westdex:
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "./logs/external_services"
|
||||
service_name: "westdex"
|
||||
use_daily: true # 启用按天分隔
|
||||
enable_level_separation: true # 启用级别分离
|
||||
level_configs:
|
||||
info: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
error: { max_size: 200, max_backups: 10, max_age: 90, compress: true }
|
||||
warn: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
|
||||
zhicha:
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "./logs/external_services"
|
||||
service_name: "zhicha"
|
||||
use_daily: true # 启用按天分隔
|
||||
enable_level_separation: true # 启用级别分离
|
||||
level_configs:
|
||||
info: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
error: { max_size: 200, max_backups: 10, max_age: 90, compress: true }
|
||||
warn: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
|
||||
yushan:
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "./logs/external_services"
|
||||
service_name: "yushan"
|
||||
use_daily: true # 启用按天分隔
|
||||
enable_level_separation: true # 启用级别分离
|
||||
level_configs:
|
||||
info: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
error: { max_size: 200, max_backups: 10, max_age: 90, compress: true }
|
||||
warn: { max_size: 100, max_backups: 3, max_age: 28, compress: true }
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 从配置创建服务
|
||||
```go
|
||||
// 推荐方式:从配置文件创建
|
||||
westdexService, err := westdex.NewWestDexServiceWithConfig(cfg)
|
||||
zhichaService, err := zhicha.NewZhichaServiceWithConfig(cfg)
|
||||
yushanService, err := yushan.NewYushanServiceWithConfig(cfg)
|
||||
```
|
||||
|
||||
### 2. 自定义日志配置
|
||||
```go
|
||||
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||
Enabled: true,
|
||||
LogDir: "./logs/external_services",
|
||||
ServiceName: "custom_service",
|
||||
EnableLevelSeparation: true,
|
||||
// ... 其他配置
|
||||
}
|
||||
|
||||
service := NewCustomServiceWithLogging(url, key, secret, loggingConfig)
|
||||
```
|
||||
|
||||
### 3. 简单模式(无日志)
|
||||
```go
|
||||
// 创建无日志的服务实例
|
||||
service := NewServiceSimple(url, key, secret)
|
||||
```
|
||||
|
||||
## 日志级别
|
||||
|
||||
### 1. INFO 级别
|
||||
- API 请求日志
|
||||
- API 响应日志
|
||||
- 一般信息日志
|
||||
|
||||
### 2. WARN 级别
|
||||
- 警告信息
|
||||
- 非致命错误
|
||||
|
||||
### 3. ERROR 级别
|
||||
- API 调用错误
|
||||
- 系统异常
|
||||
- 业务逻辑错误
|
||||
|
||||
## 文件轮转策略
|
||||
|
||||
### 1. 按大小+时间混合分隔
|
||||
|
||||
系统支持两种日志分隔策略:
|
||||
|
||||
#### 按天分隔(推荐)
|
||||
- **UseDaily**: 设置为 `true` 时启用
|
||||
- 每天创建新的日期目录:`logs/westdex/2024-01-01/`
|
||||
- 在日期目录下按级别分隔:`westdex_info.log`、`westdex_error.log`、`westdex_warn.log`
|
||||
- 自动清理过期的日期目录
|
||||
|
||||
#### 传统方式
|
||||
- **UseDaily**: 设置为 `false` 时使用
|
||||
- 直接在服务目录下按级别分隔:`logs/westdex/westdex_info.log`
|
||||
|
||||
### 2. 文件轮转配置
|
||||
|
||||
每个日志级别都支持以下轮转配置:
|
||||
|
||||
- **MaxSize**: 单个文件最大大小(MB)
|
||||
- **MaxBackups**: 最大备份文件数
|
||||
- **MaxAge**: 最大保留天数
|
||||
- **Compress**: 是否压缩旧文件
|
||||
|
||||
### 3. 目录结构示例
|
||||
|
||||
```
|
||||
logs/
|
||||
├── westdex/
|
||||
│ ├── 2024-01-01/
|
||||
│ │ ├── westdex_info.log
|
||||
│ │ ├── westdex_error.log
|
||||
│ │ └── westdex_warn.log
|
||||
│ ├── 2024-01-02/
|
||||
│ │ ├── westdex_info.log
|
||||
│ │ ├── westdex_error.log
|
||||
│ │ └── westdex_warn.log
|
||||
│ └── westdex_info.log (回退文件)
|
||||
├── zhicha/
|
||||
│ ├── 2024-01-01/
|
||||
│ │ ├── zhicha_info.log
|
||||
│ │ ├── zhicha_error.log
|
||||
│ │ └── zhicha_warn.log
|
||||
│ └── zhicha_info.log (回退文件)
|
||||
└── yushan/
|
||||
├── 2024-01-01/
|
||||
│ ├── yushan_info.log
|
||||
│ ├── yushan_error.log
|
||||
│ └── yushan_warn.log
|
||||
└── yushan_info.log (回退文件)
|
||||
```
|
||||
|
||||
## 扩展新服务
|
||||
|
||||
要添加新的外部服务,只需:
|
||||
|
||||
1. 在服务中使用 `external_logger.ExternalServiceLogger`
|
||||
2. 设置合适的 `ServiceName`
|
||||
3. 使用统一的日志接口
|
||||
4. 在配置文件中添加相应的日志配置
|
||||
|
||||
```go
|
||||
// 新服务示例
|
||||
func NewCustomService(config CustomConfig) *CustomService {
|
||||
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||
ServiceName: "custom_service",
|
||||
LogDir: config.LogDir,
|
||||
// ... 其他配置
|
||||
}
|
||||
|
||||
logger, _ := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||
|
||||
return &CustomService{
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 优势总结
|
||||
|
||||
1. **维护简便**: 只需维护一套日志代码
|
||||
2. **格式统一**: 所有服务使用相同的日志格式
|
||||
3. **配置灵活**: 支持每个服务独立的配置
|
||||
4. **易于扩展**: 新增服务只需简单配置
|
||||
5. **性能优化**: 共享的日志基础设施
|
||||
6. **监控友好**: 统一的日志格式便于监控和分析
|
||||
7. **智能分隔**: 支持按大小+时间混合分隔策略
|
||||
8. **自动清理**: 自动清理过期的日志目录,节省磁盘空间
|
||||
9. **故障回退**: 如果按天分隔失败,自动回退到传统方式
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保日志目录有足够的磁盘空间
|
||||
2. 定期清理过期的日志文件
|
||||
3. 监控日志文件大小,避免磁盘空间不足
|
||||
4. 在生产环境中建议启用日志压缩
|
||||
5. 根据业务需求调整日志保留策略
|
||||
6. 启用按天分隔时,确保系统时间准确
|
||||
7. 监控自动清理任务的执行情况
|
||||
8. 建议在生产环境中设置合理的 `MaxAge` 值,避免日志文件过多
|
||||
|
||||
286
internal/shared/external_logger/example_usage.md
Normal file
286
internal/shared/external_logger/example_usage.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# 通用外部服务日志系统使用示例
|
||||
|
||||
## 概述
|
||||
|
||||
这个通用的外部服务日志系统允许 westdex 和 zhicha 服务共享相同的日志基础架构,但保持各自独立的日志文件目录。
|
||||
|
||||
## 目录结构
|
||||
|
||||
使用共享日志系统后,日志目录结构如下:
|
||||
|
||||
```
|
||||
logs/
|
||||
├── external_services/ # 外部服务日志根目录
|
||||
│ ├── westdex/ # westdex 服务日志
|
||||
│ │ ├── westdex_info.log
|
||||
│ │ ├── westdex_error.log
|
||||
│ │ └── westdex_warn.log
|
||||
│ ├── zhicha/ # zhicha 服务日志
|
||||
│ │ ├── zhicha_info.log
|
||||
│ │ ├── zhicha_error.log
|
||||
│ │ └── zhicha_warn.log
|
||||
│ └── yushan/ # yushan 服务日志
|
||||
│ ├── yushan_info.log
|
||||
│ ├── yushan_error.log
|
||||
│ └── yushan_warn.log
|
||||
```
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 1. 在 config.yaml 中配置
|
||||
|
||||
```yaml
|
||||
# 外部服务日志根目录
|
||||
external_services_log_dir: "./logs/external_services"
|
||||
|
||||
# westdex 配置
|
||||
westdex:
|
||||
url: "https://api.westdex.com"
|
||||
key: "your_key"
|
||||
secret_id: "your_secret_id"
|
||||
secret_second_id: "your_secret_second_id"
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "./logs/external_services" # 使用共享根目录
|
||||
enable_level_separation: true
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
error:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
|
||||
# zhicha 配置
|
||||
zhicha:
|
||||
url: "https://www.zhichajinkong.com/dataMiddle/api/handle"
|
||||
app_id: "your_app_id"
|
||||
app_secret: "your_app_secret"
|
||||
pro_id: "your_pro_id"
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "./logs/external_services" # 使用共享根目录
|
||||
enable_level_separation: true
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
error:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
|
||||
# yushan 配置
|
||||
yushan:
|
||||
url: "https://api.yushan.com"
|
||||
api_key: "your_api_key"
|
||||
acct_id: "your_acct_id"
|
||||
logging:
|
||||
enabled: true
|
||||
log_dir: "./logs/external_services" # 使用共享根目录
|
||||
enable_level_separation: true
|
||||
level_configs:
|
||||
info:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
error:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
warn:
|
||||
max_size: 100
|
||||
max_backups: 3
|
||||
max_age: 28
|
||||
compress: true
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 创建 WestDex 服务
|
||||
```go
|
||||
import "tyapi-server/internal/infrastructure/external/westdex"
|
||||
|
||||
// 从配置创建(推荐)
|
||||
westdexService, err := westdex.NewWestDexServiceWithConfig(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 使用自定义日志配置
|
||||
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||
Enabled: true,
|
||||
LogDir: "./logs/external_services",
|
||||
ServiceName: "westdex", // 会自动设置
|
||||
EnableLevelSeparation: true,
|
||||
// ... 其他配置
|
||||
}
|
||||
|
||||
westdexService, err := westdex.NewWestDexServiceWithLogging(
|
||||
"url", "key", "secretID", "secretSecondID",
|
||||
loggingConfig,
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 创建 Zhicha 服务
|
||||
```go
|
||||
import "tyapi-server/internal/infrastructure/external/zhicha"
|
||||
|
||||
// 从配置创建(推荐)
|
||||
zhichaService, err := zhicha.NewZhichaServiceWithConfig(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 使用自定义日志配置
|
||||
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||
Enabled: true,
|
||||
LogDir: "./logs/external_services",
|
||||
ServiceName: "zhicha", // 会自动设置
|
||||
EnableLevelSeparation: true,
|
||||
// ... 其他配置
|
||||
}
|
||||
|
||||
zhichaService, err := zhicha.NewZhichaServiceWithLogging(
|
||||
"url", "appID", "appSecret", "proID",
|
||||
loggingConfig,
|
||||
)
|
||||
```
|
||||
|
||||
### 3. 创建 Yushan 服务
|
||||
```go
|
||||
import "tyapi-server/internal/infrastructure/external/yushan"
|
||||
|
||||
// 从配置创建(推荐)
|
||||
yushanService, err := yushan.NewYushanServiceWithConfig(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 使用自定义日志配置
|
||||
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||
Enabled: true,
|
||||
LogDir: "./logs/external_services",
|
||||
ServiceName: "yushan", // 会自动设置
|
||||
EnableLevelSeparation: true,
|
||||
// ... 其他配置
|
||||
}
|
||||
|
||||
yushanService, err := yushan.NewYushanServiceWithLogging(
|
||||
"url", "apiKey", "acctID",
|
||||
loggingConfig,
|
||||
)
|
||||
```
|
||||
|
||||
## 日志内容示例
|
||||
|
||||
### WestDex 日志内容
|
||||
```json
|
||||
{
|
||||
"level": "INFO",
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"msg": "westdex API请求",
|
||||
"service": "westdex",
|
||||
"request_id": "westdex_12345678",
|
||||
"api_code": "G05HZ01",
|
||||
"url": "https://api.westdex.com/G05HZ01",
|
||||
"params": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### Zhicha 日志内容
|
||||
```json
|
||||
{
|
||||
"level": "INFO",
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"msg": "zhicha API请求",
|
||||
"service": "zhicha",
|
||||
"request_id": "zhicha_87654321",
|
||||
"api_code": "handle",
|
||||
"url": "https://www.zhichajinkong.com/dataMiddle/api/handle",
|
||||
"params": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### Yushan 日志内容
|
||||
```json
|
||||
{
|
||||
"level": "INFO",
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"msg": "yushan API请求",
|
||||
"service": "yushan",
|
||||
"request_id": "yushan_12345678",
|
||||
"api_code": "G05HZ01",
|
||||
"url": "https://api.yushan.com",
|
||||
"params": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
### 1. 代码复用
|
||||
- 相同的日志基础架构
|
||||
- 统一的日志格式
|
||||
- 相同的配置结构
|
||||
|
||||
### 2. 维护简便
|
||||
- 只需维护一套日志代码
|
||||
- 统一的日志级别管理
|
||||
- 统一的文件轮转策略
|
||||
|
||||
### 3. 清晰分离
|
||||
- 每个服务有独立的日志目录
|
||||
- 通过 `service` 字段区分来源
|
||||
- 可独立配置每个服务的日志参数
|
||||
|
||||
### 4. 扩展性
|
||||
- 易于添加新的外部服务
|
||||
- 统一的日志接口
|
||||
- 灵活的配置选项
|
||||
|
||||
## 添加新的外部服务
|
||||
|
||||
要添加新的外部服务(如 TianYanCha),只需:
|
||||
|
||||
1. 在服务中使用 `external_logger.ExternalServiceLogger`
|
||||
2. 设置合适的 `ServiceName`
|
||||
3. 使用统一的日志接口
|
||||
|
||||
```go
|
||||
// 新服务示例
|
||||
func NewTianYanChaService(config TianYanChaConfig) *TianYanChaService {
|
||||
loggingConfig := external_logger.ExternalServiceLoggingConfig{
|
||||
ServiceName: "tianyancha",
|
||||
LogDir: config.LogDir,
|
||||
// ... 其他配置
|
||||
}
|
||||
|
||||
logger, _ := external_logger.NewExternalServiceLogger(loggingConfig)
|
||||
|
||||
return &TianYanChaService{
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样新服务的日志会自动保存到 `logs/external_services/tianyancha/` 目录。
|
||||
315
internal/shared/external_logger/external_logger.go
Normal file
315
internal/shared/external_logger/external_logger.go
Normal file
@@ -0,0 +1,315 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 如果启用级别分离,创建文件输出
|
||||
if config.EnableLevelSeparation {
|
||||
core := createSeparatedCore(serviceLogDir, config)
|
||||
baseLogger = zap.New(core)
|
||||
}
|
||||
|
||||
// 创建日志器实例
|
||||
logger := &ExternalServiceLogger{
|
||||
logger: baseLogger,
|
||||
config: config,
|
||||
serviceName: config.ServiceName,
|
||||
}
|
||||
|
||||
// 如果启用按天分隔,启动定时清理任务
|
||||
if config.UseDaily {
|
||||
go logger.startCleanupTask()
|
||||
}
|
||||
|
||||
return logger, 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)
|
||||
|
||||
// 修复:创建真正的级别分离核心
|
||||
// 使用自定义的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级别
|
||||
)
|
||||
|
||||
// 使用 zapcore.NewTee 合并核心,现在每个核心只会处理自己级别的日志
|
||||
return zapcore.NewTee(infoCore, errorCore, warnCore)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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, apiCode string, url interface{}, params interface{}) {
|
||||
e.logger.Info(fmt.Sprintf("%s API请求", e.serviceName),
|
||||
zap.String("service", e.serviceName),
|
||||
zap.String("request_id", requestID),
|
||||
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, apiCode string, statusCode int, response []byte, duration time.Duration) {
|
||||
e.logger.Info(fmt.Sprintf("%s API响应", e.serviceName),
|
||||
zap.String("service", e.serviceName),
|
||||
zap.String("request_id", requestID),
|
||||
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)),
|
||||
)
|
||||
}
|
||||
|
||||
// LogError 记录错误日志
|
||||
func (e *ExternalServiceLogger) LogError(requestID, 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("api_code", apiCode),
|
||||
zap.Error(err),
|
||||
zap.Any("params", params),
|
||||
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
|
||||
}
|
||||
147
internal/shared/logger/factory.go
Normal file
147
internal/shared/logger/factory.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// LoggerFactory 日志器工厂 - 基于 Zap 官方推荐
|
||||
type LoggerFactory struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
// NewLoggerFactory 创建日志器工厂
|
||||
func NewLoggerFactory(config Config) *LoggerFactory {
|
||||
return &LoggerFactory{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateLogger 创建普通日志器
|
||||
func (f *LoggerFactory) CreateLogger() (Logger, error) {
|
||||
return NewLogger(f.config)
|
||||
}
|
||||
|
||||
// CreateProductionLogger 创建生产环境日志器 - 使用 Zap 官方推荐
|
||||
func (f *LoggerFactory) CreateProductionLogger() (*zap.Logger, error) {
|
||||
// 使用 Zap 官方的生产环境预设
|
||||
logger, err := zap.NewProduction(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果配置为文件输出,需要手动设置 Core
|
||||
if f.config.Output == "file" {
|
||||
writeSyncer, err := createFileWriteSyncer(f.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建新的 Core 并替换
|
||||
encoder := getEncoder(f.config.Format, f.config)
|
||||
level := getLogLevel(f.config.Level)
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
}
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
// CreateDevelopmentLogger 创建开发环境日志器 - 使用 Zap 官方推荐
|
||||
func (f *LoggerFactory) CreateDevelopmentLogger() (*zap.Logger, error) {
|
||||
// 使用 Zap 官方的开发环境预设
|
||||
logger, err := zap.NewDevelopment(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果配置为文件输出,需要手动设置 Core
|
||||
if f.config.Output == "file" {
|
||||
writeSyncer, err := createFileWriteSyncer(f.config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建新的 Core 并替换
|
||||
encoder := getEncoder(f.config.Format, f.config)
|
||||
level := getLogLevel(f.config.Level)
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
}
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
// CreateCustomLogger 创建自定义配置日志器
|
||||
func (f *LoggerFactory) CreateCustomLogger() (*zap.Logger, error) {
|
||||
// 根据环境选择预设
|
||||
if f.config.Development {
|
||||
return f.CreateDevelopmentLogger()
|
||||
}
|
||||
return f.CreateProductionLogger()
|
||||
}
|
||||
|
||||
// CreateLoggerByEnvironment 根据环境创建合适的日志器
|
||||
func (f *LoggerFactory) CreateLoggerByEnvironment() (*zap.Logger, error) {
|
||||
if f.config.Development {
|
||||
return f.CreateDevelopmentLogger()
|
||||
}
|
||||
return f.CreateProductionLogger()
|
||||
}
|
||||
|
||||
// CreateLoggerWithOptions 使用选项模式创建日志器
|
||||
func (f *LoggerFactory) CreateLoggerWithOptions(options ...LoggerOption) (*zap.Logger, error) {
|
||||
// 应用选项
|
||||
for _, option := range options {
|
||||
option(&f.config)
|
||||
}
|
||||
|
||||
// 创建日志器
|
||||
return f.CreateLoggerByEnvironment()
|
||||
}
|
||||
|
||||
// LoggerOption 日志器选项函数
|
||||
type LoggerOption func(*Config)
|
||||
|
||||
// WithLevel 设置日志级别
|
||||
func WithLevel(level string) LoggerOption {
|
||||
return func(config *Config) {
|
||||
config.Level = level
|
||||
}
|
||||
}
|
||||
|
||||
// WithFormat 设置日志格式
|
||||
func WithFormat(format string) LoggerOption {
|
||||
return func(config *Config) {
|
||||
config.Format = format
|
||||
}
|
||||
}
|
||||
|
||||
// WithOutput 设置输出目标
|
||||
func WithOutput(output string) LoggerOption {
|
||||
return func(config *Config) {
|
||||
config.Output = output
|
||||
}
|
||||
}
|
||||
|
||||
// WithDevelopment 设置是否为开发环境
|
||||
func WithDevelopment(development bool) LoggerOption {
|
||||
return func(config *Config) {
|
||||
config.Development = development
|
||||
}
|
||||
}
|
||||
|
||||
// WithColor 设置是否使用彩色输出
|
||||
func WithColor(useColor bool) LoggerOption {
|
||||
return func(config *Config) {
|
||||
config.UseColor = useColor
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -12,303 +10,219 @@ import (
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// LevelLoggerConfig 按级别分文件的日志配置
|
||||
// LevelLogger 级别分文件日志器 - 基于 Zap 官方推荐
|
||||
type LevelLogger struct {
|
||||
logger *zap.Logger
|
||||
levelLoggers map[zapcore.Level]*zap.Logger
|
||||
config LevelLoggerConfig
|
||||
}
|
||||
|
||||
// LevelLoggerConfig 级别分文件日志器配置
|
||||
type LevelLoggerConfig struct {
|
||||
BaseConfig Config
|
||||
// 是否启用按级别分文件
|
||||
BaseConfig Config
|
||||
EnableLevelSeparation bool
|
||||
// 各级别日志文件配置
|
||||
LevelConfigs map[zapcore.Level]LevelFileConfig
|
||||
LevelConfigs map[zapcore.Level]LevelFileConfig
|
||||
}
|
||||
|
||||
// LevelFileConfig 单个级别文件配置
|
||||
type LevelFileConfig struct {
|
||||
MaxSize int // 单个文件最大大小(MB)
|
||||
MaxBackups int // 最大备份文件数
|
||||
MaxAge int // 最大保留天数
|
||||
Compress bool // 是否压缩
|
||||
MaxSize int `mapstructure:"max_size"`
|
||||
MaxBackups int `mapstructure:"max_backups"`
|
||||
MaxAge int `mapstructure:"max_age"`
|
||||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// LevelLogger 按级别分文件的日志器
|
||||
type LevelLogger struct {
|
||||
loggers map[zapcore.Level]*zap.Logger
|
||||
config LevelLoggerConfig
|
||||
}
|
||||
// NewLevelLogger 创建级别分文件日志器
|
||||
func NewLevelLogger(config LevelLoggerConfig) (Logger, error) {
|
||||
// 根据环境创建基础日志器
|
||||
var baseLogger *zap.Logger
|
||||
var err error
|
||||
|
||||
// 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)
|
||||
if config.BaseConfig.Development {
|
||||
baseLogger, err = zap.NewDevelopment(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
} else {
|
||||
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
||||
baseLogger, err = zap.NewProduction(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
}
|
||||
|
||||
// 创建文件输出
|
||||
writeSyncer, err := createLevelFileWriteSyncer(level, config.BaseConfig, levelConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建核心
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
// 创建级别分文件的日志器
|
||||
levelLogger := &LevelLogger{
|
||||
logger: baseLogger,
|
||||
levelLoggers: make(map[zapcore.Level]*zap.Logger),
|
||||
config: config,
|
||||
}
|
||||
|
||||
// 创建日志器
|
||||
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
// 为每个级别创建专门的日志器
|
||||
if config.EnableLevelSeparation {
|
||||
levelLogger.createLevelLoggers()
|
||||
}
|
||||
|
||||
return logger, nil
|
||||
return levelLogger, nil
|
||||
}
|
||||
|
||||
// createLevelFileWriteSyncer 创建级别文件输出同步器
|
||||
func createLevelFileWriteSyncer(level zapcore.Level, baseConfig Config, levelConfig LevelFileConfig) (zapcore.WriteSyncer, error) {
|
||||
// 构建日志文件路径
|
||||
// createLevelLoggers 创建各级别的日志器
|
||||
func (l *LevelLogger) createLevelLoggers() {
|
||||
levels := []zapcore.Level{
|
||||
zapcore.DebugLevel,
|
||||
zapcore.InfoLevel,
|
||||
zapcore.WarnLevel,
|
||||
zapcore.ErrorLevel,
|
||||
zapcore.FatalLevel,
|
||||
zapcore.PanicLevel,
|
||||
}
|
||||
|
||||
for _, level := range levels {
|
||||
// 获取该级别的配置
|
||||
levelConfig, exists := l.config.LevelConfigs[level]
|
||||
if !exists {
|
||||
// 如果没有配置,使用默认配置
|
||||
levelConfig = LevelFileConfig{
|
||||
MaxSize: 100,
|
||||
MaxBackups: 5,
|
||||
MaxAge: 30,
|
||||
Compress: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 创建该级别的文件输出
|
||||
writeSyncer := l.createLevelWriteSyncer(level, levelConfig)
|
||||
|
||||
// 创建编码器
|
||||
encoder := getEncoder(l.config.BaseConfig.Format, l.config.BaseConfig)
|
||||
|
||||
// 创建 Core
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
|
||||
// 创建该级别的日志器
|
||||
levelLogger := zap.New(core,
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
|
||||
l.levelLoggers[level] = levelLogger
|
||||
}
|
||||
}
|
||||
|
||||
// createLevelWriteSyncer 创建级别特定的文件输出同步器
|
||||
func (l *LevelLogger) createLevelWriteSyncer(level zapcore.Level, config LevelFileConfig) zapcore.WriteSyncer {
|
||||
// 构建文件路径
|
||||
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()))
|
||||
if l.config.BaseConfig.UseDaily {
|
||||
// 按日期分包:logs/2024-01-01/debug.log
|
||||
date := time.Now().Format("2006-01-02")
|
||||
levelName := level.String()
|
||||
logFilePath = filepath.Join(l.config.BaseConfig.LogDir, date, levelName+".log")
|
||||
} else {
|
||||
// 传统方式:logs/error.log
|
||||
logFilePath = filepath.Join(baseConfig.LogDir, fmt.Sprintf("%s.log", level.String()))
|
||||
// 传统方式:logs/debug.log
|
||||
levelName := level.String()
|
||||
logFilePath = filepath.Join(l.config.BaseConfig.LogDir, levelName+".log")
|
||||
}
|
||||
|
||||
// 创建lumberjack日志轮转器
|
||||
lumberJackLogger := &lumberjack.Logger{
|
||||
// 创建 lumberjack 日志轮转器
|
||||
rotator := &lumberjack.Logger{
|
||||
Filename: logFilePath,
|
||||
MaxSize: levelConfig.MaxSize,
|
||||
MaxBackups: levelConfig.MaxBackups,
|
||||
MaxAge: levelConfig.MaxAge,
|
||||
Compress: levelConfig.Compress,
|
||||
MaxSize: config.MaxSize,
|
||||
MaxBackups: config.MaxBackups,
|
||||
MaxAge: config.MaxAge,
|
||||
Compress: config.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
|
||||
return zapcore.AddSync(rotator)
|
||||
}
|
||||
|
||||
// GetLevelLogger 获取指定级别的日志器
|
||||
func (l *LevelLogger) GetLevelLogger(level zapcore.Level) *zap.Logger {
|
||||
if logger, exists := l.loggers[level]; exists {
|
||||
if logger, exists := l.levelLoggers[level]; exists {
|
||||
return logger
|
||||
}
|
||||
return nil
|
||||
return l.logger
|
||||
}
|
||||
|
||||
// GetLoggers 获取所有级别的日志器
|
||||
func (l *LevelLogger) GetLoggers() map[zapcore.Level]*zap.Logger {
|
||||
return l.loggers
|
||||
// 实现 Logger 接口
|
||||
func (l *LevelLogger) Debug(msg string, fields ...zapcore.Field) {
|
||||
if logger := l.GetLevelLogger(zapcore.DebugLevel); logger != nil {
|
||||
logger.Debug(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数(从logger.go复制)
|
||||
func getTraceIDFromContextLevel(ctx context.Context) string {
|
||||
if traceID := ctx.Value("trace_id"); traceID != nil {
|
||||
if id, ok := traceID.(string); ok {
|
||||
return id
|
||||
func (l *LevelLogger) Info(msg string, fields ...zapcore.Field) {
|
||||
if logger := l.GetLevelLogger(zapcore.InfoLevel); logger != nil {
|
||||
logger.Info(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LevelLogger) Warn(msg string, fields ...zapcore.Field) {
|
||||
if logger := l.GetLevelLogger(zapcore.WarnLevel); logger != nil {
|
||||
logger.Warn(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LevelLogger) Error(msg string, fields ...zapcore.Field) {
|
||||
if logger := l.GetLevelLogger(zapcore.ErrorLevel); logger != nil {
|
||||
logger.Error(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LevelLogger) Fatal(msg string, fields ...zapcore.Field) {
|
||||
if logger := l.GetLevelLogger(zapcore.FatalLevel); logger != nil {
|
||||
logger.Fatal(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LevelLogger) Panic(msg string, fields ...zapcore.Field) {
|
||||
if logger := l.GetLevelLogger(zapcore.PanicLevel); logger != nil {
|
||||
logger.Panic(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LevelLogger) With(fields ...zapcore.Field) Logger {
|
||||
// 为所有级别添加字段
|
||||
for level, logger := range l.levelLoggers {
|
||||
l.levelLoggers[level] = logger.With(fields...)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *LevelLogger) WithContext(ctx context.Context) Logger {
|
||||
// 从上下文提取字段
|
||||
fields := extractFieldsFromContext(ctx)
|
||||
return l.With(fields...)
|
||||
}
|
||||
|
||||
func (l *LevelLogger) Named(name string) Logger {
|
||||
// 为所有级别添加名称
|
||||
for level, logger := range l.levelLoggers {
|
||||
l.levelLoggers[level] = logger.Named(name)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *LevelLogger) Sync() error {
|
||||
// 同步所有级别的日志器
|
||||
for _, logger := range l.levelLoggers {
|
||||
if err := logger.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return l.logger.Sync()
|
||||
}
|
||||
|
||||
func getUserIDFromContextLevel(ctx context.Context) string {
|
||||
if userID := ctx.Value("user_id"); userID != nil {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
func (l *LevelLogger) Core() zapcore.Core {
|
||||
return l.logger.Core()
|
||||
}
|
||||
|
||||
func getRequestIDFromContextLevel(ctx context.Context) string {
|
||||
if requestID := ctx.Value("request_id"); requestID != nil {
|
||||
if id, ok := requestID.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (l *LevelLogger) GetZapLogger() *zap.Logger {
|
||||
return l.logger
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -12,8 +10,9 @@ import (
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// Logger 日志接口
|
||||
// Logger 日志器接口 - 基于 Zap 官方推荐
|
||||
type Logger interface {
|
||||
// 基础日志方法
|
||||
Debug(msg string, fields ...zapcore.Field)
|
||||
Info(msg string, fields ...zapcore.Field)
|
||||
Warn(msg string, fields ...zapcore.Field)
|
||||
@@ -21,271 +20,303 @@ type Logger interface {
|
||||
Fatal(msg string, fields ...zapcore.Field)
|
||||
Panic(msg string, fields ...zapcore.Field)
|
||||
|
||||
// 结构化日志方法
|
||||
With(fields ...zapcore.Field) Logger
|
||||
WithContext(ctx context.Context) Logger
|
||||
Named(name string) Logger
|
||||
|
||||
// 同步和清理
|
||||
Sync() error
|
||||
Core() zapcore.Core
|
||||
|
||||
// 获取原生 Zap Logger(用于高级功能)
|
||||
GetZapLogger() *zap.Logger
|
||||
}
|
||||
|
||||
// ZapLogger Zap日志实现
|
||||
// Config 日志配置 - 基于 Zap 官方配置结构
|
||||
type Config struct {
|
||||
// 基础配置
|
||||
Level string `mapstructure:"level"` // 日志级别
|
||||
Format string `mapstructure:"format"` // 输出格式 (json/console)
|
||||
Output string `mapstructure:"output"` // 输出方式 (stdout/stderr/file)
|
||||
LogDir string `mapstructure:"log_dir"` // 日志目录
|
||||
UseDaily bool `mapstructure:"use_daily"` // 是否按日分包
|
||||
UseColor bool `mapstructure:"use_color"` // 是否使用彩色输出(仅console格式)
|
||||
|
||||
// 文件配置
|
||||
MaxSize int `mapstructure:"max_size"` // 单个文件最大大小(MB)
|
||||
MaxBackups int `mapstructure:"max_backups"` // 最大备份文件数
|
||||
MaxAge int `mapstructure:"max_age"` // 最大保留天数
|
||||
Compress bool `mapstructure:"compress"` // 是否压缩
|
||||
|
||||
// 高级功能
|
||||
EnableLevelSeparation bool `mapstructure:"enable_level_separation"` // 是否启用按级别分文件
|
||||
LevelConfigs map[string]interface{} `mapstructure:"level_configs"` // 各级别配置(使用 interface{} 避免循环依赖)
|
||||
EnableRequestLogging bool `mapstructure:"enable_request_logging"` // 是否启用请求日志
|
||||
EnablePerformanceLog bool `mapstructure:"enable_performance_log"` // 是否启用性能日志
|
||||
|
||||
// 开发环境配置
|
||||
Development bool `mapstructure:"development"` // 是否为开发环境
|
||||
Sampling bool `mapstructure:"sampling"` // 是否启用采样
|
||||
}
|
||||
|
||||
// ZapLogger Zap日志实现 - 基于官方推荐
|
||||
type ZapLogger struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// Config 日志配置
|
||||
type Config struct {
|
||||
Level string
|
||||
Format string
|
||||
Output string
|
||||
LogDir string // 日志目录
|
||||
MaxSize int // 单个文件最大大小(MB)
|
||||
MaxBackups int // 最大备份文件数
|
||||
MaxAge int // 最大保留天数
|
||||
Compress bool // 是否压缩
|
||||
UseDaily bool // 是否按日分包
|
||||
}
|
||||
|
||||
// NewLogger 创建新的日志实例
|
||||
// NewLogger 创建新的日志实例 - 使用 Zap 官方推荐的方式
|
||||
func NewLogger(config Config) (Logger, error) {
|
||||
// 设置日志级别
|
||||
level, err := zapcore.ParseLevel(config.Level)
|
||||
var logger *zap.Logger
|
||||
var err error
|
||||
|
||||
// 根据环境创建合适的日志器
|
||||
if config.Development {
|
||||
logger, err = zap.NewDevelopment(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
} else {
|
||||
logger, err = zap.NewProduction(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的日志级别: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 配置编码器
|
||||
var encoder zapcore.Encoder
|
||||
encoderConfig := getEncoderConfig()
|
||||
|
||||
switch config.Format {
|
||||
case "json":
|
||||
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
||||
case "console":
|
||||
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
||||
default:
|
||||
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
// 配置输出
|
||||
var writeSyncer zapcore.WriteSyncer
|
||||
switch config.Output {
|
||||
case "stdout":
|
||||
writeSyncer = zapcore.AddSync(os.Stdout)
|
||||
case "stderr":
|
||||
writeSyncer = zapcore.AddSync(os.Stderr)
|
||||
case "file":
|
||||
writeSyncer, err = createFileWriteSyncer(config)
|
||||
// 如果配置为文件输出,需要手动设置 Core
|
||||
if config.Output == "file" {
|
||||
writeSyncer, err := createFileWriteSyncer(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建文件输出失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
writeSyncer = zapcore.AddSync(os.Stdout)
|
||||
|
||||
// 创建新的 Core 并替换
|
||||
encoder := getEncoder(config.Format, config)
|
||||
level := getLogLevel(config.Level)
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
}
|
||||
|
||||
// 创建核心
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
|
||||
// 创建logger
|
||||
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
|
||||
return &ZapLogger{
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createFileWriteSyncer 创建文件输出同步器
|
||||
func createFileWriteSyncer(config Config) (zapcore.WriteSyncer, error) {
|
||||
// 设置默认日志目录
|
||||
if config.LogDir == "" {
|
||||
config.LogDir = "logs"
|
||||
}
|
||||
// 实现 Logger 接口
|
||||
func (z *ZapLogger) Debug(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
// 确保日志目录存在
|
||||
if err := os.MkdirAll(config.LogDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建日志目录失败: %w", err)
|
||||
}
|
||||
func (z *ZapLogger) Info(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Info(msg, fields...)
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if config.MaxSize == 0 {
|
||||
config.MaxSize = 100 // 默认100MB
|
||||
}
|
||||
if config.MaxBackups == 0 {
|
||||
config.MaxBackups = 3 // 默认3个备份
|
||||
}
|
||||
if config.MaxAge == 0 {
|
||||
config.MaxAge = 7 // 默认7天
|
||||
}
|
||||
func (z *ZapLogger) Warn(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// 构建日志文件路径
|
||||
var logFilePath string
|
||||
if config.UseDaily {
|
||||
// 按日分包:logs/2024-01-01/app.log
|
||||
today := time.Now().Format("2006-01-02")
|
||||
dailyDir := filepath.Join(config.LogDir, today)
|
||||
if err := os.MkdirAll(dailyDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建日期目录失败: %w", err)
|
||||
}
|
||||
logFilePath = filepath.Join(dailyDir, "app.log")
|
||||
func (z *ZapLogger) Error(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Error(msg, fields...)
|
||||
}
|
||||
|
||||
func (z *ZapLogger) Fatal(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Fatal(msg, fields...)
|
||||
}
|
||||
|
||||
func (z *ZapLogger) Panic(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Panic(msg, fields...)
|
||||
}
|
||||
|
||||
func (z *ZapLogger) With(fields ...zapcore.Field) Logger {
|
||||
return &ZapLogger{logger: z.logger.With(fields...)}
|
||||
}
|
||||
|
||||
func (z *ZapLogger) WithContext(ctx context.Context) Logger {
|
||||
// 从上下文提取字段
|
||||
fields := extractFieldsFromContext(ctx)
|
||||
return &ZapLogger{logger: z.logger.With(fields...)}
|
||||
}
|
||||
|
||||
func (z *ZapLogger) Named(name string) Logger {
|
||||
return &ZapLogger{logger: z.logger.Named(name)}
|
||||
}
|
||||
|
||||
func (z *ZapLogger) Sync() error {
|
||||
return z.logger.Sync()
|
||||
}
|
||||
|
||||
func (z *ZapLogger) Core() zapcore.Core {
|
||||
return z.logger.Core()
|
||||
}
|
||||
|
||||
func (z *ZapLogger) GetZapLogger() *zap.Logger {
|
||||
return z.logger
|
||||
}
|
||||
|
||||
// 全局日志器 - 基于 Zap 官方推荐
|
||||
var globalLogger *zap.Logger
|
||||
|
||||
// InitGlobalLogger 初始化全局日志器
|
||||
func InitGlobalLogger(config Config) error {
|
||||
var logger *zap.Logger
|
||||
var err error
|
||||
|
||||
// 根据环境创建合适的日志器
|
||||
if config.Development {
|
||||
logger, err = zap.NewDevelopment(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
} else {
|
||||
// 传统方式:logs/app.log
|
||||
logFilePath = filepath.Join(config.LogDir, "app.log")
|
||||
logger, err = zap.NewProduction(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
}
|
||||
|
||||
// 创建lumberjack日志轮转器
|
||||
lumberJackLogger := &lumberjack.Logger{
|
||||
Filename: logFilePath,
|
||||
MaxSize: config.MaxSize, // 单个文件最大大小(MB)
|
||||
MaxBackups: config.MaxBackups, // 最大备份文件数
|
||||
MaxAge: config.MaxAge, // 最大保留天数
|
||||
Compress: config.Compress, // 是否压缩
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return zapcore.AddSync(lumberJackLogger), nil
|
||||
}
|
||||
|
||||
// getEncoderConfig 获取编码器配置
|
||||
func getEncoderConfig() zapcore.EncoderConfig {
|
||||
return zapcore.EncoderConfig{
|
||||
TimeKey: "timestamp",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
FunctionKey: zapcore.OmitKey,
|
||||
MessageKey: "message",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeDuration: zapcore.StringDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
}
|
||||
|
||||
// Debug 调试日志
|
||||
func (l *ZapLogger) Debug(msg string, fields ...zapcore.Field) {
|
||||
l.logger.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
// Info 信息日志
|
||||
func (l *ZapLogger) Info(msg string, fields ...zapcore.Field) {
|
||||
l.logger.Info(msg, fields...)
|
||||
}
|
||||
|
||||
// Warn 警告日志
|
||||
func (l *ZapLogger) Warn(msg string, fields ...zapcore.Field) {
|
||||
l.logger.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// Error 错误日志
|
||||
func (l *ZapLogger) Error(msg string, fields ...zapcore.Field) {
|
||||
l.logger.Error(msg, fields...)
|
||||
}
|
||||
|
||||
// Fatal 致命错误日志
|
||||
func (l *ZapLogger) Fatal(msg string, fields ...zapcore.Field) {
|
||||
l.logger.Fatal(msg, fields...)
|
||||
}
|
||||
|
||||
// Panic 恐慌日志
|
||||
func (l *ZapLogger) Panic(msg string, fields ...zapcore.Field) {
|
||||
l.logger.Panic(msg, fields...)
|
||||
}
|
||||
|
||||
// With 添加字段
|
||||
func (l *ZapLogger) With(fields ...zapcore.Field) Logger {
|
||||
return &ZapLogger{
|
||||
logger: l.logger.With(fields...),
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext 从上下文添加字段
|
||||
func (l *ZapLogger) WithContext(ctx context.Context) Logger {
|
||||
// 从上下文中提取常用字段
|
||||
fields := []zapcore.Field{}
|
||||
|
||||
if traceID := getTraceIDFromContext(ctx); traceID != "" {
|
||||
fields = append(fields, zap.String("trace_id", traceID))
|
||||
}
|
||||
|
||||
if userID := getUserIDFromContext(ctx); userID != "" {
|
||||
fields = append(fields, zap.String("user_id", userID))
|
||||
}
|
||||
|
||||
if requestID := getRequestIDFromContext(ctx); requestID != "" {
|
||||
fields = append(fields, zap.String("request_id", requestID))
|
||||
}
|
||||
|
||||
return l.With(fields...)
|
||||
}
|
||||
|
||||
// Sync 同步日志
|
||||
func (l *ZapLogger) Sync() error {
|
||||
return l.logger.Sync()
|
||||
}
|
||||
|
||||
// GetZapLogger 获取内部的zap.Logger实例
|
||||
func (l *ZapLogger) GetZapLogger() *zap.Logger {
|
||||
return l.logger
|
||||
}
|
||||
|
||||
// getTraceIDFromContext 从上下文获取追踪ID
|
||||
func getTraceIDFromContext(ctx context.Context) string {
|
||||
if traceID := ctx.Value("trace_id"); traceID != nil {
|
||||
if id, ok := traceID.(string); ok {
|
||||
return id
|
||||
// 如果配置为文件输出,需要手动设置 Core
|
||||
if config.Output == "file" {
|
||||
writeSyncer, err := createFileWriteSyncer(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建新的 Core 并替换
|
||||
encoder := getEncoder(config.Format, config)
|
||||
level := getLogLevel(config.Level)
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
}
|
||||
return ""
|
||||
|
||||
// 替换全局日志器
|
||||
zap.ReplaceGlobals(logger)
|
||||
globalLogger = logger
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUserIDFromContext 从上下文获取用户ID
|
||||
func getUserIDFromContext(ctx context.Context) string {
|
||||
if userID := ctx.Value("user_id"); userID != nil {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
}
|
||||
// GetGlobalLogger 获取全局日志器
|
||||
func GetGlobalLogger() *zap.Logger {
|
||||
if globalLogger == nil {
|
||||
// 如果没有初始化,使用默认的生产环境配置
|
||||
globalLogger = zap.Must(zap.NewProduction())
|
||||
}
|
||||
return ""
|
||||
return globalLogger
|
||||
}
|
||||
|
||||
// getRequestIDFromContext 从上下文获取请求ID
|
||||
func getRequestIDFromContext(ctx context.Context) string {
|
||||
// L 获取全局日志器(Zap 官方推荐的方式)
|
||||
func L() *zap.Logger {
|
||||
return zap.L()
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func getLogLevel(level string) zapcore.Level {
|
||||
switch level {
|
||||
case "debug":
|
||||
return zapcore.DebugLevel
|
||||
case "info":
|
||||
return zapcore.InfoLevel
|
||||
case "warn":
|
||||
return zapcore.WarnLevel
|
||||
case "error":
|
||||
return zapcore.ErrorLevel
|
||||
case "fatal":
|
||||
return zapcore.FatalLevel
|
||||
case "panic":
|
||||
return zapcore.PanicLevel
|
||||
default:
|
||||
return zapcore.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
func getEncoder(format string, config Config) zapcore.Encoder {
|
||||
encoderConfig := getEncoderConfig(config)
|
||||
|
||||
if format == "console" {
|
||||
return zapcore.NewConsoleEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
return zapcore.NewJSONEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
func getEncoderConfig(config Config) zapcore.EncoderConfig {
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
|
||||
if config.Development {
|
||||
encoderConfig = zap.NewDevelopmentEncoderConfig()
|
||||
}
|
||||
|
||||
// 自定义时间格式
|
||||
encoderConfig.TimeKey = "timestamp"
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
|
||||
// 自定义级别格式
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
|
||||
// 自定义调用者格式
|
||||
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
|
||||
|
||||
return encoderConfig
|
||||
}
|
||||
|
||||
func createFileWriteSyncer(config Config) (zapcore.WriteSyncer, error) {
|
||||
// 使用 lumberjack 进行日志轮转
|
||||
rotator := &lumberjack.Logger{
|
||||
Filename: getLogFilePath(config),
|
||||
MaxSize: config.MaxSize,
|
||||
MaxBackups: config.MaxBackups,
|
||||
MaxAge: config.MaxAge,
|
||||
Compress: config.Compress,
|
||||
}
|
||||
|
||||
return zapcore.AddSync(rotator), nil
|
||||
}
|
||||
|
||||
func getLogFilePath(config Config) string {
|
||||
if config.UseDaily {
|
||||
// 按日期分包
|
||||
date := time.Now().Format("2006-01-02")
|
||||
return filepath.Join(config.LogDir, date, "app.log")
|
||||
}
|
||||
|
||||
return filepath.Join(config.LogDir, "app.log")
|
||||
}
|
||||
|
||||
func extractFieldsFromContext(ctx context.Context) []zapcore.Field {
|
||||
var fields []zapcore.Field
|
||||
|
||||
// 提取请求ID
|
||||
if requestID := ctx.Value("request_id"); requestID != nil {
|
||||
if id, ok := requestID.(string); ok {
|
||||
return id
|
||||
fields = append(fields, zap.String("request_id", id))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Field 创建日志字段的便捷函数
|
||||
func String(key, val string) zapcore.Field {
|
||||
return zap.String(key, val)
|
||||
}
|
||||
|
||||
func Int(key string, val int) zapcore.Field {
|
||||
return zap.Int(key, val)
|
||||
}
|
||||
|
||||
func Int64(key string, val int64) zapcore.Field {
|
||||
return zap.Int64(key, val)
|
||||
}
|
||||
|
||||
func Float64(key string, val float64) zapcore.Field {
|
||||
return zap.Float64(key, val)
|
||||
}
|
||||
|
||||
func Bool(key string, val bool) zapcore.Field {
|
||||
return zap.Bool(key, val)
|
||||
}
|
||||
|
||||
func Error(err error) zapcore.Field {
|
||||
return zap.Error(err)
|
||||
}
|
||||
|
||||
func Any(key string, val interface{}) zapcore.Field {
|
||||
return zap.Any(key, val)
|
||||
}
|
||||
|
||||
func Duration(key string, val interface{}) zapcore.Field {
|
||||
return zap.Any(key, val)
|
||||
|
||||
// 提取用户ID
|
||||
if userID := ctx.Value("user_id"); userID != nil {
|
||||
if id, ok := userID.(string); ok {
|
||||
fields = append(fields, zap.String("user_id", id))
|
||||
}
|
||||
}
|
||||
|
||||
// 提取跟踪ID
|
||||
if traceID := ctx.Value("trace_id"); traceID != nil {
|
||||
if id, ok := traceID.(string); ok {
|
||||
fields = append(fields, zap.String("trace_id", id))
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user