Files
lijiaoqiao/supply-api/docs/logging_standardization_plan_v1.md

178 lines
4.0 KiB
Markdown
Raw Normal View History

# 日志标准化重构方案
## 问题分析
### 当前状态
- 已实现结构化日志接口 (`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))
```