--- 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. **安全**: 显式脱敏。