1. 添加 slog_logger.go 实现基于 Go 1.21+ slog 的结构化日志 2. 支持 trace_id、request_id、tenant_id 等标准字段注入 3. 添加日志标准化重构方案文档 推荐使用 Go 内置 log/slog,无需第三方依赖。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
178 lines
4.0 KiB
Markdown
178 lines
4.0 KiB
Markdown
# 日志标准化重构方案
|
||
|
||
## 问题分析
|
||
|
||
### 当前状态
|
||
- 已实现结构化日志接口 (`internal/pkg/logging/logger.go`)
|
||
- 支持 JSON 格式输出,带 trace_id、span_id、request_id 等标准字段
|
||
- 内置敏感字段脱敏
|
||
|
||
### 问题
|
||
大量代码仍使用标准库 `log`:
|
||
```go
|
||
log.Printf("[AUDIT_ERROR] failed to emit audit event: %v", err)
|
||
```
|
||
|
||
**缺陷:**
|
||
1. 非结构化,难以解析和搜索
|
||
2. 缺少 trace_id,无法关联请求链路
|
||
3. 缺少标准化字段,grep 难度大
|
||
4. 无法输出到统一日志收集系统
|
||
|
||
## 优化方案
|
||
|
||
### 1. 创建 log.Logger 适配器
|
||
|
||
将标准库 `log` 重定向到结构化日志:
|
||
|
||
```go
|
||
// logadapter 包:将标准 log 重定向到结构化日志
|
||
package logadapter
|
||
|
||
import (
|
||
"log"
|
||
"os"
|
||
"sync"
|
||
)
|
||
|
||
var (
|
||
defaultLogger Logger
|
||
mu sync.Mutex
|
||
)
|
||
|
||
// SetLogger 设置全局日志实例
|
||
func SetLogger(l Logger) {
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
defaultLogger = l
|
||
}
|
||
|
||
// Printf 实现 log.Logger 接口
|
||
func (l *stdLoggerAdapter) Printf(format string, v ...interface{}) {
|
||
if defaultLogger != nil {
|
||
defaultLogger.Info(fmt.Sprintf(format, v...), nil)
|
||
}
|
||
}
|
||
|
||
// init 拦截标准库 log
|
||
func init() {
|
||
log.SetOutput(&stdLoggerAdapter{})
|
||
log.SetFlags(0) // 禁用标准库时间戳
|
||
}
|
||
```
|
||
|
||
### 2. 使用 slog 作为后端(推荐)
|
||
|
||
Go 1.21+ 内置 `log/slog`,直接使用:
|
||
|
||
```go
|
||
import "log/slog"
|
||
|
||
func main() {
|
||
// 初始化 slog JSON handler
|
||
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||
Level: slog.LevelInfo,
|
||
})
|
||
slog.SetDefault(slog.New(handler))
|
||
|
||
// 使用 slog
|
||
slog.Info("server started", "addr", ":8080")
|
||
slog.Error("connection failed", "err", err, "peer", "10.0.0.1")
|
||
}
|
||
```
|
||
|
||
### 3. 统一字段命名
|
||
|
||
| 字段 | 标准化名称 | 说明 |
|
||
|------|-----------|------|
|
||
| 租户ID | `tenant_id` | |
|
||
| 用户ID | `user_id` | |
|
||
| 请求ID | `request_id` | HTTP 请求追踪 |
|
||
| Trace ID | `trace_id` | W3C Trace Context |
|
||
| 操作类型 | `operation` | 如 `withdraw`, `create` |
|
||
| 耗时 | `duration_ms` | 毫秒 |
|
||
| 状态码 | `status_code` | HTTP 状态码 |
|
||
| 错误码 | `error_code` | 业务错误码 |
|
||
|
||
### 4. 实施步骤
|
||
|
||
#### Phase 1: 基础设施
|
||
1. 使用 `log/slog` 替代自定义实现
|
||
2. 配置 JSON handler 输出到 stdout
|
||
3. 设置默认字段(service_name, version)
|
||
|
||
#### Phase 2: HTTP 层改造
|
||
1. 中间件注入 trace_id、request_id
|
||
2. Request/Response 日志
|
||
3. 慢请求日志
|
||
|
||
#### Phase 3: Domain 层改造
|
||
1. 业务操作日志(withdraw, create)
|
||
2. 错误日志包含 error_code
|
||
3. 审计事件日志
|
||
|
||
#### Phase 4: 清理
|
||
1. 移除所有 `log.Printf`
|
||
2. 添加 log level 配置
|
||
3. 支持日志采样
|
||
|
||
### 5. 日志格式规范
|
||
|
||
**JSON 日志条目:**
|
||
```json
|
||
{
|
||
"time": "2026-04-13T10:15:30.123Z",
|
||
"level": "INFO",
|
||
"service": "supply-api",
|
||
"trace_id": "abc123",
|
||
"request_id": "req-456",
|
||
"tenant_id": 1001,
|
||
"operation": "withdraw",
|
||
"duration_ms": 45,
|
||
"status_code": 200,
|
||
"message": "withdraw completed"
|
||
}
|
||
```
|
||
|
||
**错误日志:**
|
||
```json
|
||
{
|
||
"time": "2026-04-13T10:15:30.123Z",
|
||
"level": "ERROR",
|
||
"service": "supply-api",
|
||
"error_code": "SUP_SET_4001",
|
||
"error": "withdraw amount exceeds available balance",
|
||
"tenant_id": 1001,
|
||
"operation": "withdraw",
|
||
"stack_trace": "..."
|
||
}
|
||
```
|
||
|
||
### 6. 迁移检查清单
|
||
|
||
- [ ] 移除 `log.Printf` 替换为结构化日志
|
||
- [ ] 所有日志包含 `trace_id`(从 context 提取)
|
||
- [ ] 错误日志包含 `error_code`
|
||
- [ ] 敏感字段自动脱敏
|
||
- [ ] 配置 log level 支持动态调整
|
||
- [ ] Benchmark 测试日志性能影响 < 5%
|
||
|
||
## 推荐方案
|
||
|
||
**使用 Go 1.21+ 内置 `log/slog`**,无需引入第三方依赖。
|
||
|
||
```go
|
||
// 推荐配置
|
||
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||
Level: slog.LevelInfo,
|
||
AddSource: false,
|
||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||
if a.Key == slog.TimeKey {
|
||
a.Value = slog.StringValue(a.Value.Time().Format(time.RFC3339Nano))
|
||
}
|
||
return a
|
||
},
|
||
})
|
||
slog.SetDefault(slog.New(handler))
|
||
```
|