创建仓库

This commit is contained in:
2025-12-11 07:24:36 +08:00
commit 0d81c1792d
128 changed files with 15104 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
---
tags: []
aliases:
- 日志模块工程化实施标准
date created: 星期三, 十二月 10日 2025, 10:58:53 晚上
date modified: 星期三, 十二月 10日 2025, 11:42:26 晚上
---
# 日志模块工程化实施标准
---
## 一、 注释与文档规范 (Documentation Standards)
目标:“中文友好 (Chinese Friendly)” 且 “符合 GoDoc 标准”。
我们采用 混合语言策略:结构定义用英文(为了 IDE 兼容性),业务解释用中文(为了团队协作)。
### 1. 导出的包与函数 (Exported Symbols)
所有对外暴露的函数(首字母大写),必须编写文档注释。
- **格式要求**
- 第一行:`// FunctionName 简短的英文或中文摘要` (符合 Go Lint 检查)。
- 空一行。
- 详细说明:**必须使用中文**解释函数的行为、副作用Side Effects和潜在风险。
- 参数说明:如果有复杂参数,使用 `// - param: explanation` 格式。
- **范例 (Style Guide)**:
> // WithContext returns a logger with the trace ID injected.
>
> //
>
> // [功能]: 从 context.Context 中提取 TraceID 并附加到 Logger 字段中。
>
> // [注意]: 这是一个轻量级操作,但如果 ctx 为 nil将返回原始 Logger 的 fallback。
>
> // [场景]: 务必在 Controller 或 Service 的入口处优先调用。
### 2. 内部实现细节 (Internal Logic)
对于 `internal/pkg/log` 内部复杂的逻辑(如 `lumberjack` 的配置转换),必须在代码块上方添加中文注释。
- **原则**:解释 **“为什么这么做 (Why)”**,而不是“做了什么 (What)”。代码本身已经展示了做了什么。
- **范例**:
> // [Why]: 这里不使用 zap.NewProduction 自带的 OutputPaths
>
> // 因为我们需要同时输出到控制台 (为了 Docker 采集) 和文件 (为了本地容灾)
>
> // 且文件输出需要通过 Lumberjack 进行轮转控制。
### 3. README 维护
`internal/pkg/log/README.md` 中维护一份**“速查手册”**。
- **必填内容**
- 如何在 `config.yaml` 中配置(给出默认值)。
- 如何动态调整日志级别(如通过信号或 API
- 常见错误码Code与日志关键字的对应关系。
---
## 二、 可拓展性设计 (Extensibility Design)
虽然我们拒绝“过度封装”但必须为未来的变化预留接口Hook Points
### 1. 配置扩展Functional Options 模式
我们在 `Init` 函数中,不应列出所有参数,而应使用 `Option` 模式。
- **设计**: `func Init(opts …Option) error`
- **预留能力**: 未来如果需要添加“发送日志到 Kafka”或“开启 Sentry 报警”,只需新增一个 `WithKafka(addr)` 的 Option而无需修改 `Init` 的函数签名,保证了对旧代码的兼容性。
### 2. 核心扩展Zap Hooks
Zap 原生支持 `Hooks`。我们的封装必须暴露这一能力。
- **场景**: 当日志级别为 `Error``Fatal` 时,可能需要同步触发飞书/钉钉报警。
- **实现标准**: 在 `zap.go` 的构建逻辑中,检查配置是否定义了 Hooks。这允许我们在不侵入日志核心代码的情况下挂载报警逻辑。
### 3. 字段扩展Context Key Registry
随着业务发展,需要记录的元数据会增加(如 `TenantID`, `RequestID`, `SpanID`)。
- **标准**: 不要在 `context.go` 里写死 key 的提取逻辑。
- **设计**: 定义一个 `type ContextExtractor func(ctx) []Field` 类型。默认提供 `TraceIDExtractor`。允许在初始化时注册新的 Extractor。这使得业务线可以自定义需要提取的 Context 字段。
---
## 三、 查漏补缺 (Gap Analysis)
在之前的讨论中,有几个隐蔽但致命的工程细节尚未覆盖,这里作为最后防线进行补充。
### 1. 关于 `Logger.Fatal` 的使用禁令
- **风险**: `zap.Logger.Fatal` 会在打印日志后调用 `os.Exit(1)`
- **工程标准**: **在 Web 服务HTTP Server严禁在业务逻辑层调用 `Fatal`。**
- _原因_: 这会直接杀死整个进程,导致所有正在处理的请求中断(没有 Graceful Shutdown
- _替代_: 遇到不可恢复错误,使用 `Error` 级别日志,并返回 `500` 错误给客户端,由上层中间件处理。
- _例外_: 仅在 `main.go` 启动阶段(如连不上数据库、读不到配置)可以使用 `Fatal`
### 2. 时间格式的一致性
- **问题**: Zap 默认的时间格式可能是浮点数Unix Epoch或非标准字符串。
- **标准**: 生产环境统一配置为 **`ISO8601` (2025-12-10T22:00:00.000Z)**。
- _理由_: 这种格式跨时区友好且能被几乎所有日志分析工具ELK, Splunk, CloudWatch自动识别并建立时间索引。
### 3. 动态日志级别 (Hot Reload)
- **需求**: 线上出 Bug 时,需要临时把 Level 调成 Debug查完再调回 Info且不能重启服务。
- **实现标准**: 利用 `zap.AtomicLevel`
- 我们需要暴露一个 HTTP 接口(如 `PUT /admin/log/level`)或监听配置文件的 `fsnotify` 事件。
- 收到变更信号后,直接调用 `atomicLevel.SetLevel(zap.DebugLevel)`。这是线程安全的,无需重启实例。
### 4. 测试支持 (Testing Support)
- **问题**: 单元测试时,不仅不想看到日志刷屏,有时还需要断言“是否打印了某条错误日志”。
- **标准**:
- 提供 `pkg/log/test_helper.go`
- 封装 `zaptest/observer`
- 允许测试代码通过 `log.NewTestLogger()` 获取一个观察者对象,从而断言 `logs.FilterMessage("error").Len() == 1`
### 5. 链路完整性保障
- **风险**: 开发者容易遗忘在 `go func()` 中传递 Context。
- **标准**: 在 Code Review 时,重点检查所有 `go` 关键字后是否跟随了 Context 的传递或播种操作。
### 6. 框架初始化与 Panic 处理
- **风险**: `gin.Default()` 会自动注册只打印文本日志的 Recovery 中间件,破坏 JSON 格式。
- **标准**:
1. 必须使用 `gin.New()` 初始化 Engine。
2. 必须手动注册我们自定义的 `middleware.Recovery``middleware.AccessLog`
3. 确保 Panic 日志中包含 TraceID`c.Request.Context` 中尝试恢复)。
---
## 四、 总结与就绪确认
至此,我们已经完成了日志模块的**全生命周期设计**
1. **架构**: 基础设施层,无业务依赖。
2. **技术栈**: Zap + Lumberjack + Context Adapter。
3. **模式**: 单例兜底 + 依赖注入,强类型约束。
4. **规范**: Snake_case 键名,中文友好文档,严禁 Fatal。