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

6.9 KiB
Raw Blame History

tags, aliases, date created, date modified
tags aliases date created date modified
《日志模块开发规范与质量保证手册》
一、 核心开发规范 (The Golden Rules)
星期三, 十二月 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.Anyzap.ReflectSugar 模式
  • 例外: 仅在应用启动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 原生的 logfmt 打印日志。

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 检查: 确认没有引入 logfmt
  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. 安全: 显式脱敏。