Files
Inbox/Go项目实战/03_基础设施/02_日志/06_日志模块开发规范与质量保证手册.md

182 lines
6.9 KiB
Markdown
Raw Normal View History

2025-12-11 07:24:36 +08:00
---
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. **安全**: 显式脱敏。