454 lines
11 KiB
Markdown
454 lines
11 KiB
Markdown
# 🚀 Zap 官方最佳实践日志系统指南
|
||
|
||
## 概述
|
||
|
||
本日志系统完全基于 [Zap 官方最佳实践](https://betterstack.com/community/guides/logging/go/zap/) 设计,使用 `zap.NewProduction()` 和 `zap.NewDevelopment()` 预设,提供高性能、结构化的日志记录。
|
||
|
||
## ✨ 核心特性
|
||
|
||
### 1. **基于 Zap 官方预设**
|
||
- 使用 `zap.NewProduction()` 生产环境预设
|
||
- 使用 `zap.NewDevelopment()` 开发环境预设
|
||
- 自动添加调用者信息、堆栈跟踪等
|
||
|
||
### 2. **全局日志器支持**
|
||
- 支持 `zap.ReplaceGlobals()` 全局替换
|
||
- 提供 `logger.L()` 和 `logger.GetGlobalLogger()` 访问
|
||
- 符合 Zap 官方推荐的使用方式
|
||
|
||
### 3. **强类型字段支持**
|
||
- 使用 `zap.String()`, `zap.Int()`, `zap.Error()` 等强类型字段
|
||
- 避免运行时类型错误
|
||
- 提供最佳性能
|
||
|
||
### 4. **上下文日志记录**
|
||
- 自动从上下文提取 `request_id`, `user_id`, `trace_id`
|
||
- 支持 `WithContext()` 方法
|
||
- 便于分布式系统追踪
|
||
|
||
## 🏗️ 架构设计
|
||
|
||
### 核心接口
|
||
|
||
```go
|
||
type Logger interface {
|
||
// 基础日志方法
|
||
Debug(msg string, fields ...zapcore.Field)
|
||
Info(msg string, fields ...zapcore.Field)
|
||
Warn(msg string, fields ...zapcore.Field)
|
||
Error(msg string, fields ...zapcore.Field)
|
||
Fatal(msg string, fields ...zapcore.Field)
|
||
Panic(msg string, fields ...zapcore.Field)
|
||
|
||
// 结构化日志方法
|
||
With(fields ...zapcore.Field) Logger
|
||
WithContext(ctx context.Context) Logger
|
||
Named(name string) Logger
|
||
|
||
// 同步和清理
|
||
Sync() error
|
||
Core() zapcore.Core
|
||
|
||
// 获取原生 Zap Logger
|
||
GetZapLogger() *zap.Logger
|
||
}
|
||
```
|
||
|
||
### 实现类型
|
||
|
||
1. **ZapLogger**: 标准日志器,基于 Zap 官方预设
|
||
2. **LevelLogger**: 级别分文件日志器,支持按级别分离
|
||
3. **全局日志器**: 通过 `zap.ReplaceGlobals()` 提供全局访问
|
||
|
||
## 🚀 使用方法
|
||
|
||
### 1. 基础使用
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"go.uber.org/zap"
|
||
"tyapi-server/internal/shared/logger"
|
||
)
|
||
|
||
func main() {
|
||
// 初始化全局日志器
|
||
config := logger.Config{
|
||
Development: true,
|
||
Output: "stdout",
|
||
Format: "console",
|
||
}
|
||
|
||
if err := logger.InitGlobalLogger(config); err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
// 使用全局日志器
|
||
logger.L().Info("应用启动成功")
|
||
|
||
// 或者获取全局日志器
|
||
globalLogger := logger.GetGlobalLogger()
|
||
globalLogger.Info("使用全局日志器")
|
||
}
|
||
```
|
||
|
||
### 2. 依赖注入使用
|
||
|
||
```go
|
||
type ProductService struct {
|
||
logger logger.Logger
|
||
}
|
||
|
||
func NewProductService(logger logger.Logger) *ProductService {
|
||
return &ProductService{logger: logger}
|
||
}
|
||
|
||
func (s *ProductService) CreateProduct(ctx context.Context, product *Product) error {
|
||
// 记录操作日志
|
||
s.logger.Info("创建产品",
|
||
zap.String("product_id", product.ID),
|
||
zap.String("product_name", product.Name),
|
||
zap.String("user_id", product.CreatedBy),
|
||
)
|
||
|
||
// 业务逻辑...
|
||
return nil
|
||
}
|
||
```
|
||
|
||
### 3. 上下文日志记录
|
||
|
||
```go
|
||
func (s *ProductService) GetProduct(ctx context.Context, id string) (*Product, error) {
|
||
// 自动从上下文提取字段
|
||
logger := s.logger.WithContext(ctx)
|
||
|
||
logger.Info("获取产品信息",
|
||
zap.String("product_id", id),
|
||
zap.String("operation", "get_product"),
|
||
)
|
||
|
||
// 业务逻辑...
|
||
return product, nil
|
||
}
|
||
```
|
||
|
||
### 4. 结构化字段
|
||
|
||
```go
|
||
// 使用强类型字段
|
||
s.logger.Info("用户登录",
|
||
zap.String("username", "john_doe"),
|
||
zap.Int("user_id", 12345),
|
||
zap.String("ip_address", "192.168.1.100"),
|
||
zap.String("user_agent", r.UserAgent()),
|
||
zap.Time("login_time", time.Now()),
|
||
)
|
||
|
||
// 记录错误
|
||
if err != nil {
|
||
s.logger.Error("数据库操作失败",
|
||
zap.Error(err),
|
||
zap.String("operation", "create_user"),
|
||
zap.String("table", "users"),
|
||
)
|
||
}
|
||
```
|
||
|
||
### 5. 级别分文件日志
|
||
|
||
```go
|
||
// 配置启用级别分文件
|
||
config := logger.Config{
|
||
EnableLevelSeparation: true,
|
||
Output: "file",
|
||
LogDir: "logs",
|
||
UseDaily: true,
|
||
LevelConfigs: map[string]interface{}{
|
||
"debug": map[string]interface{}{
|
||
"max_size": 50,
|
||
"max_backups": 3,
|
||
"max_age": 7,
|
||
},
|
||
"error": map[string]interface{}{
|
||
"max_size": 200,
|
||
"max_backups": 10,
|
||
"max_age": 90,
|
||
},
|
||
},
|
||
}
|
||
|
||
// 创建级别分文件日志器
|
||
levelLogger, err := logger.NewLevelLogger(logger.LevelLoggerConfig{
|
||
BaseConfig: config,
|
||
EnableLevelSeparation: true,
|
||
LevelConfigs: convertLevelConfigs(config.LevelConfigs),
|
||
})
|
||
```
|
||
|
||
## 📁 日志文件结构
|
||
|
||
### 按级别分文件
|
||
|
||
```
|
||
logs/
|
||
├── 2024-01-01/
|
||
│ ├── debug.log # 调试日志
|
||
│ ├── info.log # 信息日志
|
||
│ ├── warn.log # 警告日志
|
||
│ ├── error.log # 错误日志
|
||
│ ├── fatal.log # 致命错误日志
|
||
│ └── panic.log # 恐慌错误日志
|
||
└── app.log # 主日志文件
|
||
```
|
||
|
||
### 按日期分包
|
||
|
||
```
|
||
logs/
|
||
├── 2024-01-01/
|
||
│ ├── app.log
|
||
│ └── error.log
|
||
├── 2024-01-02/
|
||
│ ├── app.log
|
||
│ └── error.log
|
||
└── app.log # 当前日期
|
||
```
|
||
|
||
## ⚙️ 配置选项
|
||
|
||
### 基础配置
|
||
|
||
```yaml
|
||
logger:
|
||
# 环境配置
|
||
development: true # 是否为开发环境
|
||
|
||
# 输出配置
|
||
output: "file" # 输出方式: stdout, stderr, file
|
||
format: "json" # 输出格式: json, console
|
||
log_dir: "logs" # 日志目录
|
||
|
||
# 文件配置
|
||
max_size: 100 # 单个文件最大大小(MB)
|
||
max_backups: 5 # 最大备份文件数
|
||
max_age: 30 # 最大保留天数
|
||
compress: true # 是否压缩
|
||
|
||
# 高级功能
|
||
use_daily: true # 是否按日分包
|
||
enable_level_separation: true # 是否启用按级别分文件
|
||
use_color: false # 是否使用彩色输出
|
||
```
|
||
|
||
### 级别配置
|
||
|
||
```yaml
|
||
logger:
|
||
level_configs:
|
||
debug:
|
||
max_size: 50 # 50MB
|
||
max_backups: 3 # 3个备份
|
||
max_age: 7 # 7天
|
||
compress: true
|
||
info:
|
||
max_size: 100 # 100MB
|
||
max_backups: 5 # 5个备份
|
||
max_age: 30 # 30天
|
||
compress: true
|
||
error:
|
||
max_size: 200 # 200MB
|
||
max_backups: 10 # 10个备份
|
||
max_age: 90 # 90天
|
||
compress: true
|
||
```
|
||
|
||
## 🔧 最佳实践
|
||
|
||
### 1. **使用强类型字段**
|
||
|
||
```go
|
||
// ✅ 推荐:使用强类型字段
|
||
logger.Info("用户操作",
|
||
zap.String("user_id", userID),
|
||
zap.String("action", "login"),
|
||
zap.Time("timestamp", time.Now()),
|
||
)
|
||
|
||
// ❌ 避免:使用 Any 字段
|
||
logger.Info("用户操作",
|
||
zap.Any("user_id", userID),
|
||
zap.Any("action", "login"),
|
||
zap.Any("timestamp", time.Now()),
|
||
)
|
||
```
|
||
|
||
### 2. **合理使用日志级别**
|
||
|
||
```go
|
||
// Debug: 详细的调试信息
|
||
logger.Debug("SQL查询", zap.String("query", sql))
|
||
|
||
// Info: 重要的业务事件
|
||
logger.Info("用户注册成功", zap.String("user_id", userID))
|
||
|
||
// Warn: 警告信息,不影响功能
|
||
logger.Warn("数据库连接池使用率过高", zap.Int("usage", 85))
|
||
|
||
// Error: 错误信息,功能受影响
|
||
logger.Error("数据库连接失败", zap.Error(err))
|
||
|
||
// Fatal: 致命错误,应用无法继续
|
||
logger.Fatal("配置文件加载失败", zap.Error(err))
|
||
```
|
||
|
||
### 3. **上下文信息提取**
|
||
|
||
```go
|
||
// 在中间件中设置上下文
|
||
func LoggingMiddleware(logger logger.Logger) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
// 生成请求ID
|
||
requestID := uuid.New().String()
|
||
|
||
// 设置上下文
|
||
ctx := context.WithValue(c.Request.Context(), "request_id", requestID)
|
||
ctx = context.WithValue(ctx, "user_id", getUserID(c))
|
||
ctx = context.WithValue(ctx, "trace_id", getTraceID(c))
|
||
|
||
c.Request = c.Request.WithContext(ctx)
|
||
|
||
// 记录请求日志
|
||
logger.WithContext(ctx).Info("收到请求",
|
||
zap.String("method", c.Request.Method),
|
||
zap.String("path", c.Request.URL.Path),
|
||
zap.String("client_ip", c.ClientIP()),
|
||
)
|
||
|
||
c.Next()
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. **性能优化**
|
||
|
||
```go
|
||
// ✅ 推荐:延迟计算
|
||
if logger.Core().Enabled(zapcore.DebugLevel) {
|
||
logger.Debug("调试信息", zap.String("data", expensiveOperation()))
|
||
}
|
||
|
||
// ❌ 避免:总是计算
|
||
logger.Debug("调试信息", zap.String("data", expensiveOperation()))
|
||
```
|
||
|
||
## 🚨 错误处理
|
||
|
||
### 1. **Panic 恢复**
|
||
|
||
```go
|
||
// 使用 panic 恢复中间件
|
||
func PanicRecoveryMiddleware(logger *zap.Logger) gin.HandlerFunc {
|
||
return gin.RecoveryWithWriter(&panicLogger{logger: logger})
|
||
}
|
||
|
||
type panicLogger struct {
|
||
logger *zap.Logger
|
||
}
|
||
|
||
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
|
||
}
|
||
```
|
||
|
||
### 2. **错误日志记录**
|
||
|
||
```go
|
||
// 记录错误详情
|
||
if err != nil {
|
||
logger.Error("操作失败",
|
||
zap.Error(err),
|
||
zap.String("operation", "create_user"),
|
||
zap.String("user_id", userID),
|
||
zap.String("stack_trace", string(debug.Stack())),
|
||
)
|
||
return err
|
||
}
|
||
```
|
||
|
||
## 📊 性能基准
|
||
|
||
基于 [Zap 官方基准测试](https://betterstack.com/community/guides/logging/go/zap/):
|
||
|
||
| 包 | 时间 | 相对于 Zap | 内存分配 |
|
||
|----|------|------------|----------|
|
||
| zap | 193 ns/op | +0% | 0 allocs/op |
|
||
| zap (sugared) | 227 ns/op | +18% | 1 allocs/op |
|
||
| zerolog | 81 ns/op | -58% | 0 allocs/op |
|
||
| slog | 322 ns/op | +67% | 0 allocs/op |
|
||
|
||
## 🔍 调试和故障排除
|
||
|
||
### 1. **检查日志级别**
|
||
|
||
```go
|
||
// 检查日志级别是否启用
|
||
if logger.Core().Enabled(zapcore.DebugLevel) {
|
||
logger.Debug("调试信息")
|
||
}
|
||
```
|
||
|
||
### 2. **同步日志**
|
||
|
||
```go
|
||
// 确保日志写入完成
|
||
defer logger.Sync()
|
||
|
||
// 或者在应用关闭时
|
||
func cleanup() {
|
||
logger.Sync()
|
||
}
|
||
```
|
||
|
||
### 3. **验证配置**
|
||
|
||
```go
|
||
// 验证日志器配置
|
||
config := logger.Config{
|
||
Development: true,
|
||
Output: "stdout",
|
||
Format: "console",
|
||
}
|
||
|
||
logger, err := logger.NewLogger(config)
|
||
if err != nil {
|
||
log.Fatalf("创建日志器失败: %v", err)
|
||
}
|
||
```
|
||
|
||
## 🎯 总结
|
||
|
||
本日志系统完全基于 Zap 官方最佳实践设计,具有以下优势:
|
||
|
||
1. **高性能**: 基于 Zap 的高性能实现
|
||
2. **官方推荐**: 使用 `zap.NewProduction()` 和 `zap.NewDevelopment()` 预设
|
||
3. **强类型**: 支持强类型字段,避免运行时错误
|
||
4. **结构化**: 支持结构化日志记录
|
||
5. **上下文**: 自动提取上下文信息
|
||
6. **灵活配置**: 支持文件输出、级别分离、按日分包等
|
||
7. **全局访问**: 支持全局日志器访问
|
||
|
||
通过合理使用,您将获得高性能、结构化的日志系统,满足生产环境的各种需求!
|
||
|
||
## 📚 参考资源
|
||
|
||
- [Zap 官方文档](https://pkg.go.dev/go.uber.org/zap)
|
||
- [Zap 最佳实践指南](https://betterstack.com/community/guides/logging/go/zap/)
|
||
- [Zap GitHub 仓库](https://github.com/uber-go/zap)
|