# 现代日志系统编写规范指南

> **最终目标**  
> 不是“记很多日志”，而是在需要排查时，用最少的日志、最快的速度还原完整事实。  
> **日志应该是证据，而不是流水账。**

本规范基于 2025–2026 年业界最新实践（OpenTelemetry、Sentry Wide Events、结构化日志最佳实践等），用于指导程序日志系统的设计与编写，确保日志具备**高信号密度、强可查询性、可关联性与低成本**。

---

## 1. 核心原则

1. **结构化优先**：日志必须是机器可解析的数据，而不是自然语言句子。
2. **事件驱动**：记录“发生了什么”，而不是“我在想什么”。
3. **宽事件（Wide Events）**：在关键里程碑输出一条包含完整上下文的事件，优于大量细碎日志。
4. **强关联**：每条日志必须能关联到 Trace / Request，形成完整证据链。
5. **最小有效原则**：只记录对排查有价值的信息，控制体积与成本。
6. **一致性**：全团队、全服务使用统一的命名、字段与级别规范。

---

## 2. 日志分级规范

| 级别            | 含义与使用场景                                      | 生产环境默认 |
|-----------------|-----------------------------------------------------|--------------|
| **TRACE**       | 极细粒度调试信息                                    | 关闭         |
| **DEBUG**       | 开发调试、变量中间状态、详细执行路径                | 关闭         |
| **INFO**        | 正常业务里程碑、关键决策、重要状态变化（推荐默认）  | 开启         |
| **WARN**        | 异常但可自动恢复的情况（重试成功、降级、超时后恢复）| 开启         |
| **ERROR**       | 真正失败、需要人工关注或告警的事件                  | 开启 + 告警  |
| **FATAL / CRITICAL** | 系统不可用、严重故障                          | 开启 + 紧急告警 |

### 级别使用规则

- 生产环境默认级别设为 **INFO**。
- **INFO 不是垃圾桶**：高频、低价值的 INFO 必须降级为 DEBUG 或删除。
- **ERROR 必须克制**：正常业务失败（如支付被拒、库存不足）优先使用 WARN 或 INFO + 明确结果字段，避免告警疲劳。
- 禁止在生产环境长期开启 DEBUG/TRACE。

---

## 3. 结构化日志强制要求

### 3.1 输出格式

- **必须使用 JSON**（或等效的结构化 key-value 格式）。
- 禁止纯文本拼接句子。
- 推荐一行一个完整 JSON 对象（便于日志收集器处理）。

### 3.2 推荐基础字段

每条日志应尽量包含以下字段（根据场景增减）：

| 字段名              | 类型     | 说明                                   | 是否必须 |
|---------------------|----------|----------------------------------------|----------|
| `timestamp`         | string   | ISO 8601 UTC 时间                      | 是       |
| `level`             | string   | 日志级别                               | 是       |
| `event` / `event_name` | string | 事件名称（见命名规范）                 | 是       |
| `service`           | string   | 服务名                                 | 是       |
| `trace_id`          | string   | OpenTelemetry Trace ID                 | 强烈推荐 |
| `span_id`           | string   | OpenTelemetry Span ID                  | 强烈推荐 |
| `request_id` / `correlation_id` | string | 请求关联 ID                    | 强烈推荐 |
| `message`           | string   | 可选的人类可读补充说明                 | 可选     |

### 3.3 字段命名规范

- 全部使用 **snake_case**。
- 单位直接写进字段名：`duration_ms`、`size_bytes`、`latency_ms`、`amount_cents`。
- 优先扁平化，避免深层嵌套对象。
- 使用点分命名空间（如 `user.id`、`order.amount_cents`）或统一下划线均可，但全组织必须统一。
- 禁止同一概念出现多种命名（`userId` / `user_id` / `uid` 混用）。

---

## 4. 事件命名规范

事件名是日志的核心标识，必须稳定、可查询、语义清晰。

### 推荐风格

1. **domain.action**（强烈推荐）
   - `auth.login`
   - `payment.capture`
   - `cart.checkout`
   - `webhook.delivery`
   - `order.fulfill`

2. **过去时动词短语**
   - `order_placed`
   - `payment_failed`
   - `user_authenticated`
   - `batch_processed`

### 命名规则

- 使用 `snake_case` 或 `domain.action` 风格，全项目统一。
- 事件名应描述“发生了什么”，而不是开发者的心理活动。
- 避免模糊名称：`process`、`handle`、`do_something`、`error`。
- 成功与失败应通过属性区分（如 `result: "success" | "failed"`），或使用不同事件名。

---

## 5. Wide Events（宽事件）原则

**核心思想**：在关键操作边界输出一条包含丰富上下文的完整事件，而不是在流程中打很多细碎日志。

### 推荐做法

```text
收集上下文 → 在里程碑处输出一条宽事件（成功和失败路径都必须输出最终结果）
```

### 示例对比

**不推荐（细碎日志）**：
```
INFO  Starting checkout
INFO  Validating cart
INFO  Processing payment
INFO  Checkout complete
```

**推荐（宽事件）**：
```json
{
  "event": "checkout.completed",
  "level": "info",
  "order_id": "ord_99234",
  "user_id": "u_4821",
  "user_tier": "pro",
  "cart_value": 149.99,
  "item_count": 3,
  "payment_method": "stripe",
  "result": "success",
  "duration_ms": 1834,
  "trace_id": "..."
}
```

宽事件的优势：一次查询即可获得完整上下文，排查速度显著提升。

---

## 6. 上下文传播与可观测性关联

- 在请求入口生成 `request_id` / `correlation_id`，并贯穿整个调用链。
- 强烈推荐接入 **OpenTelemetry**，自动注入 `trace_id` 和 `span_id`。
- 使用 **Request-scoped Logger**：在请求开始时创建带有公共上下文的 Logger，并向下传递。
- 日志必须能与 Trace、Metrics 形成关联，支持“从指标告警 → Trace → 相关日志”的完整排查路径。

---

## 7. 安全与合规要求

- **禁止**记录以下内容：
  - 密码、Token、API Key、Authorization Header
  - 完整请求/响应体（除非经过严格脱敏且有明确必要）
  - 邮箱、手机号、真实姓名、身份证号、信用卡号等 PII
- 优先在**日志写入源头**进行脱敏（Redaction），而非依赖下游处理。
- 使用内部 ID 替代敏感标识。
- 审计日志（Audit Log）与普通业务日志应分开存储，并制定独立的保留策略。

---

## 8. 性能与成本控制

- 生产环境严格控制日志量，高频路径使用采样（Sampling）。
- 健康检查、心跳、轮询类日志默认丢弃或极低采样率。
- 使用高性能结构化日志库，避免字符串拼接开销。
- 云原生环境推荐输出到 **stdout/stderr**，由 Fluent Bit、Vector 或 OpenTelemetry Collector 收集。
- 制定分级保留策略（Debug 短保留，Audit 长保留）。

---

## 9. 编写检查清单（Checklist）

在提交包含日志代码前，请确认：

- [ ] 使用结构化 JSON，而非纯文本
- [ ] 事件名符合 `domain.action` 或过去时动词规范
- [ ] 字段使用统一的 snake_case，并包含必要上下文
- [ ] 生产环境默认级别为 INFO，DEBUG 已关闭
- [ ] 成功与失败路径都有最终结果事件
- [ ] 包含 `trace_id` / `span_id` 或 `request_id`
- [ ] 无敏感信息泄露
- [ ] 不是细碎日志，而是有价值的宽事件
- [ ] 字段命名与团队规范一致
- [ ] 该日志在真正排查时能提供证据价值

---

## 10. 推荐技术选型（参考）

| 语言       | 推荐库                          | 备注                     |
|------------|---------------------------------|--------------------------|
| Python     | structlog + OpenTelemetry       | 当前最佳实践组合         |
| Go         | slog（标准库）或 zap + OTel     | 官方结构化支持良好       |
| Node.js    | pino 或 winston + OTel          | 高性能优先选 pino        |
| Java       | Logback / Log4j2 + OTel         | 配合结构化布局           |
| 通用       | OpenTelemetry Logs API + Collector | 强烈推荐作为标准底座 |

---

## 11. 总结

日志系统的终极价值不在于数量，而在于**证据效力**。

好的日志系统让工程师在凌晨被叫醒时，能够用最少的查询、最快的速度，精确还原当时发生的事实。

请始终记住：

> **最终目标不是“记很多日志”，而是在需要排查时，用最少的日志、最快的速度还原完整事实。**  
> **日志应该是证据，而不是流水账。**

---

*本规范持续演进。建议结合实际业务定期 Review 并更新字段 Schema 与事件命名约定。*
