Files
2025-12-11 07:24:36 +08:00

6.5 KiB
Raw Permalink Blame History

tags, aliases, date created, date modified
tags aliases date created date modified
1. 核心设计目标 (Core Design Goals)
星期三, 十二月 10日 2025, 10:27:39 晚上 星期三, 十二月 10日 2025, 10:28:15 晚上

1. 核心设计目标 (Core Design Goals)

目标一:全链路上下文关联 (Contextual Traceability)

这是最核心的差异点。传统的 log.Println("Database error") 在并发环境下毫无价值,因为你不知道这条错误属于哪个请求。

  • 设计要求:
    • 自动注入 TraceID: 必须能够从 context.Context 中提取 TraceID(目前 internal/pkg/app 已经生成了 TraceID并自动将其附加到每一条日志中。
    • 请求元数据绑定: 除了 TraceID还应支持自动绑定 UserIDIPMethodPath 等元数据,形成请求的完整快照。
    • 跨组件穿透: 日志对象必须能够在 Layer 之间传递(例如 Controller -> Service -> Repository且保持上下文不丢失。

目标二:严格的结构化契约 (Strict Structured Schema)

日志是写给机器看的,不是写给通过 SSH 连上服务器的人看的。

  • 设计要求:
    • JSON First: 生产环境强制使用 JSON 格式。
    • Schema 统一: 字段命名必须统一。例如,不要混用 uid, user_id, userId,必须在设计阶段锁定为 snake_case (如 user_id)。
    • 类型安全: 时间戳必须统一格式(推荐 ISO8601 或 Unix Nano数字字段不能变成字符串便于聚合计算

目标三:高性能与零侵入 (High Performance & Zero Allocation)

日志通常是系统中 IO 最密集的组件之一。

  • 设计要求:
    • 低 GC 压力: 利用 Zap 的核心优势,避免大量的 interface{} 反射和字符串拼接,使用强类型的 Fieldzap.Int, zap.String)。
    • 异步 IO (可选): 考虑是否引入 Buffer 机制(牺牲极端崩溃下的日志完整性换取吞吐量)。
    • Level 级联过滤: 在 Debug 级别关闭时Debug 级别的日志构造逻辑(如复杂的对象序列化)不应被执行。

目标四:安全与合规 (Security & Compliance)

这往往是被忽视的一点,也是导致安全事故的频发区。

  • 设计要求:
    • 敏感数据脱敏: 必须具备“黑名单”机制。任何包含 password, token, mobile, credit_card 的字段在输出前必须被自动掩盖Masking
    • 安全截断: 防止打印过大的 Body如 Base64 图片上传)导致磁盘爆满或日志系统瘫痪,限制单条日志最大长度。

2. 场景化行为对比 (Dev Vs Prod)

为了兼顾开发体验和生产运维标准,我们需要在设计中明确区分两种环境的行为。

维度 开发环境 (Development) 生产环境 (Production) 设计意图
编码格式 Console (彩色,人类易读) JSON (机器易读) 开发追求直观;生产追求 ELK 解析效率。
输出目标 Stdout (控制台) File + Stdout (双写) 开发侧容器即焚;生产侧需持久化 + 容器采集。
日志级别 Debug Info / Warn 生产环境过滤掉大量 Debug 噪音,节省存储成本。
堆栈追踪 Error 级别即打印 Panic 或 Fatal 才打印 减少生产环境日志体积,除非发生严重故障。
调用行号 显示 (Caller) 显示 (Caller) 快速定位代码位置。

3. 架构定位与边界 (Architecture Boundary)

我们需要明确日志模块在架构中的位置:

  • 位置: 属于 Infrastructure Layer (Level 0/1)。
  • 依赖关系:
    • 被谁依赖: 所有层Handler, Service, Repository都依赖 Log。
    • 依赖谁: 仅依赖标准库和第三方 Log Driver (Zap)不应依赖业务逻辑
  • 与其他模块的关系:
    • vs ecode: ecode 定义错误的类型CodeLog 记录错误的现场Stack/Trace
    • vs app.Response: Response 负责对用户说话经过清洗的、友好的信息Log 负责对开发者说话(原始的、包含脏数据的真相)。

4. 深度反思与自我反驳 (Critical Thinking & Risk Analysis)

在敲定设计目标前,必须审视潜在的矛盾和风险:

反驳点 1全链路上下文TraceID的传递成本

  • 挑战: 要想让 Repository 层的日志也打出 TraceID必须修改所有方法的签名为 func (ctx context.Context, …)。这对现有代码(如果是非 Context 风格)是巨大的重构。
  • 回应: 我们的 Repository 接口目前设计中已经包含了 context.Context。这是一个必须遵守的“硬约束”。如果缺少 Context日志将断层。
  • 结论: 必须在规范中强调:所有层级的方法首个参数必须是 Context

反驳点 2脱敏机制的性能损耗

  • 挑战: 如果每一条日志都要遍历字段去匹配“黑名单”进行正则替换CPU 开销极大。
  • 回应: 不能使用正则扫描全文。
  • 修正方案: 利用 Zap 的 HookCore 包装,仅针对特定 Keypassword)进行值替换,或者要求开发者在打印敏感结构体时显式调用 .Reduct() 方法,而非隐式全局扫描。隐式扫描在 Golang 中通常是性能杀手。

反驳点 3异步写入的丢数据风险

  • 挑战: 为了性能使用 Buffered Write,如果进程被 kill -9 或 Panic 崩溃,缓冲区日志会丢失,而崩溃前的日志往往最重要。
  • 回应: 对于交易类系统,可靠性 > 吞吐量
  • 结论: 默认采用 同步写入 (Sync Write)。Zap 本身性能已经足够强(纳秒级),除非达到数万 QPS否则不需要引入 Buffer。对于 Panic必须使用 defer logger.Sync() 确保刷盘。

5. 待确认问题 (Open Questions)

在进入下一步(编写详细规格说明书)之前,我需要确认你对以下几点的偏好:

  1. 全局单例 vs 纯依赖注入:

    • 选项 A: 提供 log.Info() 全局静态方法(方便,但有副作用)。
    • 选项 B: 强制必须通过 l.Info() 实例方法调用(架构更洁癖,但调用繁琐)。
    • 推荐: 选项 A + B。提供全局方法作为快捷方式(底层代理到单例),同时支持 DI 注入。你倾向于哪种?
  2. 日志轮转 (Rotation) 策略:

    • 你是倾向于按 大小 切割(如 100MB 一个文件),还是按 时间 切割(每天一个文件)?
    • 通常建议: 按大小切割(防止单文件过大无法打开),配合最大文件保留数。