6.9 KiB
tags, aliases, date created, date modified
| tags | aliases | date created | date modified | ||
|---|---|---|---|---|---|
|
星期三, 十二月 10日 2025, 10:53:19 晚上 | 星期三, 十二月 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):
for _, item := range items { // ❌ 每次循环都分配内存 log.WithContext(ctx).Info("processing", zap.String("id", item.ID)) } -
正例 (Good):
// ✅ 只分配一次,复用 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):
func ProcessOrder(msg []byte) { ctx := context.Background() // ❌ 此时 ctx 空空如也,日志将丢失 TraceID log.WithContext(ctx).Info("processing order") } -
正例 (Good):
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 的辅助,性能最优。
- 方案 A (拦截器): 在
2. 大字段截断
- 规则: 禁止将 Base64 图片数据、巨大的 HTML 内容直接打入日志。
- 限制: 单个 Field 的 Value 长度建议限制在 2KB 以内。
四、 AI 辅助编码的“质量契约” (AI Quality Contract)
为了确保我(AI)生成的代码符合上述规范,请你(用户)在审查我的代码时,使用以下 Checklist 进行验证。这也是我对你的承诺:
- Imports 检查: 确认没有引入
log或fmt。 - Context 检查: 确认
log.WithContext(ctx)是日志调用的唯一起手式。 - Keys 检查: 确认所有 JSON Key 都是
snake_case。 - Args 检查: 确认使用的是
zap.String/Int等强类型构造器,而非zap.Any。 - Config 检查: 确认没有硬编码的路径(如
/var/log),必须来自options.go。
五、 总结与下一步
我们确立了:
- 命名: 强制 snake_case。
- 类型: 拒绝
zap.Any,拒绝fmt。 - 上下文: 强制
WithContext。 - 安全: 显式脱敏。