Files
lijiaoqiao/supply-api/docs/logging_standardization_plan_v1.md
Your Name 789707e4f3 feat(logging): 添加 slog-based 结构化日志支持
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>
2026-04-13 07:41:37 +08:00

178 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 日志标准化重构方案
## 问题分析
### 当前状态
- 已实现结构化日志接口 (`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))
```