Files
Inbox/Go项目实战/03_基础设施/02_日志/06_日志模块开发规范与质量保证手册.md
2025-12-11 07:24:36 +08:00

182 lines
6.9 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.

---
tags: []
aliases:
- 《日志模块开发规范与质量保证手册》
- 一、 核心开发规范 (The Golden Rules)
date created: 星期三, 十二月 10日 2025, 10:53:19 晚上
date modified: 星期三, 十二月 10日 2025, 11:31:04 晚上
---
# 《日志模块开发规范与质量保证手册》
---
## 一、 核心开发规范 (The Golden Rules)
这部分是“软约束”,属于团队共识,通过 Code Review 和 AI 辅助检查来执行。
### 1. 键名命名公约 (Key Naming Convention)
日志是给机器ELK/Loki读的键名必须统一方便建立索引。
- **规则**: 严禁使用 CamelCase (小驼峰) 或 PascalCase (大驼峰)**必须且只能使用 snake_case (下划线命名)**。
- **反例**: `userId`, `IPAddress`, `httpStatus`
- **正例**: `user_id`, `client_ip`, `http_status`
- **理由**: 多数数据库和搜索引擎(如 Elasticsearch的分词器对下划线更友好且 SQL 查询习惯也是下划线。
### 2. 类型安全铁律 (Type Safety Strictness)
利用 Zap 的强类型优势,拒绝隐式转换。
- **规则**: 在业务热点路径Hot Path**严禁使用 `zap.Any``zap.Reflect``Sugar` 模式**。
- **例外**: 仅在应用启动Init、Panic 恢复或非高频的配置加载阶段允许使用 `SugaredLogger`
- **理由**: `zap.Any` 会触发反射Reflection导致内存逃逸和 GC 压力。这是高性能系统的“隐形杀手”。
### 3. 上下文优先原则 (Context First)
日志不是孤岛,必须依附于请求上下文。
- **规则**: 所有 Controller、Service、Repository 层的方法,如果需要打印日志,**必须**使用 `log.WithContext(ctx).Info(…)` 及其变体。
- **禁止**: 严禁在业务流程中直接调用全局的 `log.Info(…)`(除非是系统级事件,如定时任务启动)。
- **理由**: 只有通过 `WithContext`,才能将 TraceID 串联起来。
### 4. 哨兵值与魔法字符串 (Sentinels & Magic Strings)
- **规则**: 核心日志字段的 Key 必须定义为常量Constant
- **实现**: 在 `pkg/log/standard.go` 中定义 `const TraceIDKey = "trace_id"`
- **禁止**: 代码中出现手写的 `zap.String("trace_id", …)`,防止拼写错误(如写成 `traceid`)。
### 5. 热点路径复用原则 (Hot Path Reuse)
针对循环Loop或复杂长流程函数严禁重复构建 Context Logger。
- **规则**: 必须在作用域入口处初始化 Logger 实例,并在该作用域内复用。
- **反例 (Bad)**:
```Go
for _, item := range items {
// ❌ 每次循环都分配内存
log.WithContext(ctx).Info("processing", zap.String("id", item.ID))
}
```
- **正例 (Good)**:
```Go
// ✅ 只分配一次,复用 l
l := log.WithContext(ctx)
for _, item := range items {
l.Info("processing", zap.String("id", item.ID))
}
```
- **理由**: 减少大量临时的 `zap.Logger` 结构体分配,降低 GC 的 Scavenge 阶段耗时。
### 6. 后台任务播种原则 (Background Trace Seeding)
所有非 HTTP 触发的后台任务入口Goroutine, Cron, MQ Handler必须是“有状态”的。
- **规则**: 任务的第一行代码必须调用 `StartBackgroundTrace`。
- **反例 (Bad)**:
```Go
func ProcessOrder(msg []byte) {
ctx := context.Background()
// ❌ 此时 ctx 空空如也,日志将丢失 TraceID
log.WithContext(ctx).Info("processing order")
}
```
- **正例 (Good)**:
```Go
func ProcessOrder(msg []byte) {
// ✅ 自动生成一个新的 TraceID 注入 ctx
ctx := log.StartBackgroundTrace(context.Background())
log.WithContext(ctx).Info("processing order")
}
```
---
## 二、 Linter 规则配置 (Automated Enforcement)
这部分是“硬约束”,我们将在 `.golangci.yml` 中配置这些规则,强行阻断不合规代码的提交。
### 1. 禁用标准库日志 (`depguard`)
防止开发人员手滑使用了 Go 原生的 `log` 或 `fmt` 打印日志。
Linter: depguard
配置策略:
- **Deny**:
- `log`: 标准库日志(无结构化,无法分级)。
- `fmt.Print*`: 控制台打印(生产环境绝对禁止)。
- `github.com/sirupsen/logrus`: 防止引入其他日志库。
### 2. 强制错误处理 (`errcheck`)
Zap 的 `Sync()` 方法可能会返回错误(特别是在 Linux 的 `/dev/stdout` 上),通常需要忽略,但写入文件的错误不能忽略。
Linter: errcheck / gosec
配置策略:
- 对 `logger.Sync()` 的错误处理进行豁免Exclude因为在某些 OS 下 stdout sync 必然报错,这是已知 issue。
- 但对 `logger.Info` 等方法的 IO 错误,原则上 Zap 内部处理了,不需要业务层捕获。
### 3. 自定义规则 (`ruleguard` - 高级)
标准的 Linter 无法检测“键名必须是 snake_case”。如果需要极致的管控我们可以引入 `ruleguard`。
AI 辅助检查逻辑:
由于配置 ruleguard 较复杂,我们约定在 AI 生成代码阶段 执行此逻辑:
- **Check 1**: 正则匹配所有 `zap.String("([a-z]+[A-Z][a-z]+)", …)` 模式,如果发现驼峰命名,立刻自我修正。
- **Check 2**: 扫描代码中是否存在 `fmt.Print`,如有则报错。
---
## 三、 安全与脱敏规范 (Security & Masking)
这是日志系统的“红线”。
### 1. PII (个人敏感信息) 零容忍
- **黑名单字段**: `password`, `token`, `access_token`, `refresh_token`, `credit_card`, `id_card`.
- **处理方式**:
- **方案 A (拦截器)**: 在 `zapcore` 层加 Hook但这会损耗性能。
- **方案 B (显式脱敏)**: 要求 AI 在生成代码时,对于敏感字段,自动包裹脱敏函数。例如 `zap.String("mobile", mask.Mobile(u.Mobile))`。
- **决策**: 采用 **方案 B**。依赖编码时的自觉和 AI 的辅助,性能最优。
### 2. 大字段截断
- **规则**: 禁止将 Base64 图片数据、巨大的 HTML 内容直接打入日志。
- **限制**: 单个 Field 的 Value 长度建议限制在 2KB 以内。
---
## 四、 AI 辅助编码的“质量契约” (AI Quality Contract)
为了确保我AI生成的代码符合上述规范请你用户在审查我的代码时使用以下 **Checklist** 进行验证。这也是我对你的承诺:
1. **Imports 检查**: 确认没有引入 `log` 或 `fmt`。
2. **Context 检查**: 确认 `log.WithContext(ctx)` 是日志调用的唯一起手式。
3. **Keys 检查**: 确认所有 JSON Key 都是 `snake_case`。
4. **Args 检查**: 确认使用的是 `zap.String/Int` 等强类型构造器,而非 `zap.Any`。
5. **Config 检查**: 确认没有硬编码的路径(如 `/var/log`),必须来自 `options.go`。
---
## 五、 总结与下一步
我们确立了:
1. **命名**: 强制 snake_case。
2. **类型**: 拒绝 `zap.Any`,拒绝 `fmt`。
3. **上下文**: 强制 `WithContext`。
4. **安全**: 显式脱敏。