489 lines
15 KiB
Markdown
489 lines
15 KiB
Markdown
好的,这是一份可以直接放在项目根目录的 `日志规范要求.md`,涵盖**格式标准、分类分级、内容规范、链路追踪、性能安全、运维告警**六大模块,每条规范都配有正反例,你的团队照着这个写代码就行。
|
||
|
||
---
|
||
|
||
# 生产级日志规范要求
|
||
|
||
> 版本:v2.0 | 适用于所有后端服务(Python/Java/Go/Node.js)
|
||
|
||
---
|
||
|
||
## 一、核心原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **结构化** | 所有日志必须输出为 JSON 格式,便于自动化采集和分析 |
|
||
| **可追踪** | 每个请求链路必须有唯一的 `traceId`,贯穿全流程 |
|
||
| **有上下文** | 每条日志必须包含足够的业务信息,能独立理解发生了什么 |
|
||
| **高性能** | 异步打印,禁止在业务主流程中同步写磁盘 |
|
||
| **安全合规** | 敏感信息自动脱敏,禁止打印密码、token、身份证号等 |
|
||
| **可告警** | ERROR 日志必须触发实时告警,且有明确的错误分类 |
|
||
|
||
---
|
||
|
||
## 二、日志分类
|
||
|
||
生产环境必须按用途分流存储,**禁止所有日志混写在同一文件**:
|
||
|
||
| 分类 | 文件名示例 | 用途 | 保留周期 |
|
||
|------|-----------|------|----------|
|
||
| **业务日志** | `app-biz.log` | 记录核心业务流程(订单、支付、登录、任务状态变更等) | 7天热存 + 30天冷存 |
|
||
| **系统日志** | `app-sys.log` | 记录框架、中间件、连接池、GC、线程池状态 | 7天 |
|
||
| **访问日志** | `app-access.log` | 记录所有 HTTP/RPC 请求的入参、出参、耗时 | 15天(用于审计) |
|
||
| **错误日志** | `app-error.log` | **仅记录 ERROR 级别**,含完整堆栈 | 30天(用于复盘) |
|
||
|
||
**配置要点**:
|
||
- 业务日志和错误日志必须独立文件,便于快速定位异常
|
||
- 框架类日志(如 `httpx`、`urllib3`)归入系统日志,且生产环境设为 WARN 级别
|
||
|
||
---
|
||
|
||
## 三、日志格式标准
|
||
|
||
### 3.1 统一 JSON 格式
|
||
|
||
所有日志必须输出为以下 JSON 结构,**字段名不得随意变更**:
|
||
|
||
```json
|
||
{
|
||
"@timestamp": "2026-08-19T10:30:45.123+08:00",
|
||
"level": "INFO",
|
||
"logger": "com.order.service.OrderService",
|
||
"traceId": "abc-123-def-456",
|
||
"spanId": "span-001",
|
||
"userId": "U10086",
|
||
"message": "订单状态更新成功",
|
||
"fields": {
|
||
"orderId": "ORD-20260819-001",
|
||
"fromStatus": "PENDING",
|
||
"toStatus": "PAID",
|
||
"costMs": 23,
|
||
"retryCount": 0
|
||
},
|
||
"file": "OrderService.java:156",
|
||
"thread": "http-nio-8080-exec-8",
|
||
"host": "pod-order-7x9k2",
|
||
"app": "order-service",
|
||
"env": "prod"
|
||
}
|
||
```
|
||
|
||
### 3.2 字段说明
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `@timestamp` | string | ✅ | ISO8601 格式,带时区(如 `+08:00`) |
|
||
| `level` | string | ✅ | DEBUG / INFO / WARNING / ERROR |
|
||
| `logger` | string | ✅ | 日志记录器名称,通常为类名 |
|
||
| `traceId` | string | ✅ | 全局唯一追踪ID,从入口生成,全链路透传 |
|
||
| `spanId` | string | 推荐 | 当前节点ID,用于区分调用链中的不同服务 |
|
||
| `userId` | string | 业务必填 | 操作用户标识,未登录可为空 |
|
||
| `message` | string | ✅ | 人类可读的日志摘要,简洁明了 |
|
||
| `fields` | object | ✅ | 结构化业务字段,所有动态数据放入此处 |
|
||
| `file` | string | 推荐 | 代码文件名和行号 |
|
||
| `thread` | string | 推荐 | 线程名 |
|
||
| `host` | string | 推荐 | 主机名或 Pod 名称 |
|
||
| `app` | string | ✅ | 应用名称 |
|
||
| `env` | string | ✅ | dev / test / staging / prod |
|
||
| `error` | object | ERROR时必填 | 包含 `type`、`message`、`stack_trace` |
|
||
|
||
### 3.3 ERROR 日志额外字段
|
||
|
||
当 `level = ERROR` 时,必须包含:
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"type": "ConnectionTimeoutError",
|
||
"message": "连接下游服务超时",
|
||
"stack_trace": "完整堆栈信息...",
|
||
"root_cause": "socket timeout after 3000ms"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、日志内容规范
|
||
|
||
### 4.1 日志级别使用标准
|
||
|
||
| 级别 | 使用场景 | 示例 |
|
||
|------|----------|------|
|
||
| **DEBUG** | 开发调试信息,生产环境**默认关闭** | 变量值、中间计算结果 |
|
||
| **INFO** | 关键业务流程节点、状态变更、外部调用结果 | 订单创建成功、支付回调收到、任务状态变更 |
|
||
| **WARNING** | 可恢复的异常、降级处理、重试、资源使用超阈值 | 重试第3次成功、缓存穿透、磁盘使用率>80% |
|
||
| **ERROR** | 业务失败、系统异常、需要人工介入的错误 | 支付失败、数据库连接断开、第三方接口返回500 |
|
||
|
||
### 4.2 INFO 级别日志内容要求
|
||
|
||
每条 INFO 日志必须回答 **5W1H**:
|
||
|
||
```
|
||
Who(谁操作) + What(做了什么) + When(何时) + Where(哪个服务/节点) + Why(上下文) + How(结果如何)
|
||
```
|
||
|
||
**✅ 正例:**
|
||
```python
|
||
logger.info(
|
||
"任务日志拉取成功",
|
||
extra={
|
||
"userId": "U10086",
|
||
"fields": {
|
||
"jobId": "ft_a016cd8885cd",
|
||
"tailLines": 5000,
|
||
"logSize": "2.3MB",
|
||
"costMs": 42,
|
||
"source": "frontend"
|
||
}
|
||
}
|
||
)
|
||
```
|
||
|
||
**❌ 反例(禁止):**
|
||
```python
|
||
logger.info("get logs success")
|
||
logger.info(f"job {job_id} status is {status}") # 禁止字符串拼接
|
||
```
|
||
|
||
### 4.3 WARNING/ERROR 日志内容要求
|
||
|
||
**必须包含三要素**:
|
||
1. 发生了什么(what)
|
||
2. 为什么发生(why)—— 异常类型/错误码
|
||
3. 业务上下文(context)—— 哪个业务对象失败了
|
||
|
||
**✅ 正例:**
|
||
```python
|
||
logger.warning(
|
||
"计算轮询检测到任务失败",
|
||
extra={
|
||
"fields": {
|
||
"jobId": "ft_a016cd8885cd",
|
||
"failureReason": "GPU资源不足",
|
||
"errorCode": "RESOURCE_INSUFFICIENT",
|
||
"retryCount": 3,
|
||
"lastRetryTime": "2026-08-19T08:38:25.495+08:00"
|
||
}
|
||
}
|
||
)
|
||
```
|
||
|
||
**❌ 反例(禁止):**
|
||
```python
|
||
logger.warning("compute polling reported failures") # 没有任何上下文
|
||
logger.error(f"error: {e}") # 只打了异常信息,没有业务ID
|
||
```
|
||
|
||
### 4.4 禁止打印的内容
|
||
|
||
| 类别 | 说明 |
|
||
|------|------|
|
||
| 密码/密钥 | 任何形式的 `password`、`secret`、`token`、`api_key` |
|
||
| 个人隐私 | 身份证号、手机号(需脱敏)、银行卡号 |
|
||
| 超大对象 | 超过 1KB 的 JSON/列表/文本内容 |
|
||
| 循环日志 | 禁止在 for/while 循环内打印 INFO 及以上级别 |
|
||
| 异常堆栈重复 | 同一异常在一个请求中只打印一次完整堆栈 |
|
||
|
||
---
|
||
|
||
## 五、链路追踪(TraceId)
|
||
|
||
### 5.1 基本原则
|
||
|
||
- **入口生成**:网关/前端/定时任务入口生成全局唯一的 `traceId`(32位UUID)
|
||
- **全链路透传**:通过 HTTP Header(`X-Trace-Id`)、RPC Meta、消息队列 Property 向下游传递
|
||
- **日志自动注入**:所有日志输出自动追加 `traceId`,代码中无需手动传入
|
||
- **跨线程传递**:使用 `MDC` 或 `ContextVars` 实现跨线程/协程的透传
|
||
|
||
### 5.2 实现要求
|
||
|
||
```python
|
||
# Python 示例:使用 logging 的 Filter 自动注入 traceId
|
||
class TraceIdFilter(logging.Filter):
|
||
def filter(self, record):
|
||
record.traceId = get_current_trace_id() or "N/A"
|
||
return True
|
||
|
||
# 所有日志自动带上 traceId
|
||
logger.info("订单创建成功") # 自动注入 traceId,代码无需传参
|
||
```
|
||
|
||
**❌ 绝对禁止**:`traceId` 字段值为 `"-"` 或 `null`。
|
||
|
||
---
|
||
|
||
## 六、性能与安全
|
||
|
||
### 6.1 性能要求
|
||
|
||
| 配置项 | 要求 |
|
||
|--------|------|
|
||
| **异步打印** | 必须使用异步 Appender,禁止同步刷盘阻塞业务线程 |
|
||
| **单文件大小** | ≤ 1GB,达到阈值自动滚动 |
|
||
| **滚动策略** | 按大小滚动(如 1GB)或按天滚动 |
|
||
| **采样率** | 核心业务 100%,非核心(如健康检查、非关键查询)≤ 10% |
|
||
| **禁止打印循环** | 循环体内不得打印 INFO 及以上日志 |
|
||
| **大对象截断** | 超过 1KB 的内容自动截断(前500字符 + 后500字符) |
|
||
|
||
### 6.2 安全要求
|
||
|
||
| 要求 | 说明 |
|
||
|------|------|
|
||
| **敏感字段自动脱敏** | 对 `mobile`、`idCard`、`password`、`token` 等字段自动掩码 |
|
||
| **脱敏规则** | 手机号:`138****5678`;身份证:`110***********1234` |
|
||
| **日志查询权限** | 生产日志平台必须有 RBAC 权限控制,禁止随意导出 |
|
||
| **审计追踪** | 谁在什么时候查询了哪些日志,必须记录审计日志 |
|
||
|
||
### 6.3 脱敏实现示例
|
||
|
||
```python
|
||
# 脱敏工具函数
|
||
def mask_sensitive(data: dict) -> dict:
|
||
sensitive_keys = {"password", "token", "api_key", "mobile", "id_card"}
|
||
for key in sensitive_keys:
|
||
if key in data:
|
||
value = str(data[key])
|
||
if len(value) >= 11: # 手机号
|
||
data[key] = value[:3] + "****" + value[-4:]
|
||
elif len(value) >= 18: # 身份证
|
||
data[key] = value[:3] + "***********" + value[-4:]
|
||
return data
|
||
```
|
||
|
||
---
|
||
|
||
## 七、运维与告警
|
||
|
||
### 7.1 日志采集架构
|
||
|
||
```
|
||
应用日志(本地文件)
|
||
↓
|
||
Filebeat(轻量采集器)
|
||
↓
|
||
Kafka(削峰填谷,保证不丢)
|
||
↓
|
||
Logstash(解析、过滤、脱敏)
|
||
↓
|
||
Elasticsearch(索引存储)
|
||
↓
|
||
Kibana / Grafana(查询展示)
|
||
```
|
||
|
||
**关键要求**:
|
||
- 禁止应用直接写入 ES,必须经过 Kafka 缓冲
|
||
- Filebeat 采集失败时必须有本地持久化和重试机制
|
||
|
||
### 7.2 告警规则
|
||
|
||
| 条件 | 动作 | 优先级 |
|
||
|------|------|--------|
|
||
| 同一服务 5 分钟内出现 ≥ 3 次 ERROR | 钉钉/企微告警 + 电话(P0级) | 最高 |
|
||
| 同一服务 10 分钟内 ERROR 率 > 5% | 钉钉告警(P1级) | 高 |
|
||
| 磁盘使用率 > 80% | 钉钉告警(P2级) | 中 |
|
||
| 单个 ERROR 堆栈重复出现 ≥ 10 次/分钟 | 聚合为一条告警,避免轰炸 | - |
|
||
|
||
### 7.3 错误聚合策略
|
||
|
||
- 相同 `error.type` + 相同 `logger` + 相同堆栈前3行 → 视为同一类错误
|
||
- 同一类错误 5 分钟内只发 **1 条告警**(防告警轰炸)
|
||
- 告警内容必须包含:`app`、`env`、`error.type`、首次发生时间、最近发生时间、累计次数
|
||
|
||
---
|
||
|
||
## 八、日志查询与使用规范
|
||
|
||
| 场景 | 查询方式 | 时效要求 |
|
||
|------|----------|----------|
|
||
| 日常运维 | Kibana 按 `traceId` 或 `userId` 检索 | 实时 |
|
||
| 异常排查 | 按 `app` + `level:ERROR` + 时间范围 | 实时 |
|
||
| 业务审计 | 按 `userId` + `logger:xxx` + 时间范围 | 30分钟内 |
|
||
| 性能分析 | 按 `costMs` 排序,找出慢请求 | 实时 |
|
||
| 安全审计 | 查询所有访问日志,按 IP/用户筛选 | 按需 |
|
||
|
||
---
|
||
|
||
## 九、检查清单(Code Review 必查)
|
||
|
||
| 检查项 | 通过标准 |
|
||
|--------|----------|
|
||
| ☐ JSON 格式 | 所有日志输出均为 JSON,字段名符合规范 |
|
||
| ☐ traceId | 所有日志都有 `traceId`,且不为 `"-"` |
|
||
| ☐ userId | 涉及用户操作的日志都有 `userId` |
|
||
| ☐ 业务上下文 | INFO 日志包含 `fields`,有订单ID/任务ID等 |
|
||
| ☐ ERROR 日志 | 包含 `error.type` + `stack_trace` + 业务ID |
|
||
| ☐ 敏感信息 | 无密码/手机号明文,有脱敏处理 |
|
||
| ☐ 异步打印 | 使用异步 Appender |
|
||
| ☐ 日志级别 | 框架类日志 ≥ WARN,业务日志分级合理 |
|
||
| ☐ 循环内日志 | 无循环内的 INFO 日志 |
|
||
| ☐ 日志分流 | 业务/系统/错误日志分文件存储 |
|
||
|
||
---
|
||
|
||
## 十、附:完整日志示例
|
||
|
||
### 示例一:业务成功流程(INFO)
|
||
|
||
```json
|
||
{
|
||
"@timestamp": "2026-08-19T10:30:45.123+08:00",
|
||
"level": "INFO",
|
||
"logger": "app.services.job_service",
|
||
"traceId": "tracer-abc123xyz789",
|
||
"spanId": "span-001",
|
||
"userId": "U10086",
|
||
"message": "计算任务创建成功",
|
||
"fields": {
|
||
"jobId": "ft_a016cd8885cd",
|
||
"jobType": "fine_tuning",
|
||
"modelId": "model-llama2-7b",
|
||
"datasetId": "ds-20260819-001",
|
||
"gpuCount": 4,
|
||
"estimatedTime": "2h30m",
|
||
"costMs": 1523,
|
||
"source": "api"
|
||
},
|
||
"file": "job_service.py:234",
|
||
"thread": "MainThread",
|
||
"host": "compute-pod-7x9k2",
|
||
"app": "compute-service",
|
||
"env": "prod"
|
||
}
|
||
```
|
||
|
||
### 示例二:可恢复的警告(WARNING)
|
||
|
||
```json
|
||
{
|
||
"@timestamp": "2026-08-19T08:38:27.074+08:00",
|
||
"level": "WARNING",
|
||
"logger": "app.workers.compute_poller",
|
||
"traceId": "tracer-xyz789abc123",
|
||
"spanId": "span-002",
|
||
"userId": "U10086",
|
||
"message": "计算任务状态轮询检测到失败,进入重试",
|
||
"fields": {
|
||
"jobId": "ft_a016cd8885cd",
|
||
"currentStatus": "FAILED",
|
||
"failureReason": "GPU节点不可用",
|
||
"errorCode": "NODE_UNAVAILABLE",
|
||
"retryCount": 2,
|
||
"maxRetries": 3,
|
||
"nextRetryDelay": 30
|
||
},
|
||
"file": "compute_poller.py:45",
|
||
"thread": "Thread-8",
|
||
"host": "compute-pod-7x9k2",
|
||
"app": "compute-service",
|
||
"env": "prod"
|
||
}
|
||
```
|
||
|
||
### 示例三:严重错误(ERROR)+ 告警
|
||
|
||
```json
|
||
{
|
||
"@timestamp": "2026-08-19T08:38:30.456+08:00",
|
||
"level": "ERROR",
|
||
"logger": "app.services.payment_service",
|
||
"traceId": "tracer-pay-789xyz",
|
||
"spanId": "span-003",
|
||
"userId": "U10086",
|
||
"message": "支付调用失败,订单状态回滚",
|
||
"fields": {
|
||
"orderId": "ORD-20260819-001",
|
||
"amount": 299.00,
|
||
"paymentMethod": "wechat",
|
||
"retryCount": 3,
|
||
"hasRollback": true
|
||
},
|
||
"error": {
|
||
"type": "PaymentTimeoutException",
|
||
"message": "支付网关超时,等待响应超过5000ms",
|
||
"stack_trace": "Traceback (most recent call last):\n File \"payment_service.py:89\" ...",
|
||
"root_cause": "upstream gateway 10.0.1.100:8080 connection timeout"
|
||
},
|
||
"file": "payment_service.py:156",
|
||
"thread": "http-nio-8080-exec-12",
|
||
"host": "order-pod-3f8k1",
|
||
"app": "order-service",
|
||
"env": "prod"
|
||
}
|
||
```
|
||
|
||
### 示例四:完整的请求访问日志(ACCESS)
|
||
|
||
```json
|
||
{
|
||
"@timestamp": "2026-08-19T10:30:45.001+08:00",
|
||
"level": "INFO",
|
||
"logger": "app.middleware.access_log",
|
||
"traceId": "tracer-abc123xyz789",
|
||
"userId": "U10086",
|
||
"message": "HTTP 请求完成",
|
||
"fields": {
|
||
"method": "POST",
|
||
"path": "/api/v1/jobs",
|
||
"statusCode": 200,
|
||
"clientIp": "192.168.1.100",
|
||
"userAgent": "Mozilla/5.0 ...",
|
||
"requestSize": 2048,
|
||
"responseSize": 512,
|
||
"costMs": 1523,
|
||
"requestBody": {"modelId": "model-llama2-7b", "datasetId": "ds-001"}, // 已脱敏
|
||
"responseBody": {"jobId": "ft_a016cd8885cd", "status": "CREATED"} // 已脱敏
|
||
},
|
||
"app": "compute-service",
|
||
"env": "prod"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十一、附录:技术栈配置速查
|
||
|
||
### Python (Logging + JSON)
|
||
|
||
```python
|
||
import logging
|
||
import json
|
||
from pythonjsonlogger import jsonlogger
|
||
|
||
logger = logging.getLogger("app")
|
||
handler = logging.FileHandler("logs/app-biz.log")
|
||
formatter = jsonlogger.JsonFormatter(
|
||
fmt="%(asctime)s %(levelname)s %(name)s %(traceId)s %(message)s",
|
||
rename_fields={"asctime": "@timestamp", "name": "logger"}
|
||
)
|
||
handler.setFormatter(formatter)
|
||
logger.addHandler(handler)
|
||
```
|
||
|
||
### Java (Logback + JSON)
|
||
|
||
```xml
|
||
<!-- logback-spring.xml -->
|
||
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
|
||
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
|
||
<fieldNames>
|
||
<timestamp>@timestamp</timestamp>
|
||
<level>level</level>
|
||
<thread>thread</thread>
|
||
<logger>logger</logger>
|
||
</fieldNames>
|
||
</encoder>
|
||
</appender>
|
||
```
|
||
|
||
### Go (Zap + JSON)
|
||
|
||
```go
|
||
logger, _ := zap.NewProduction()
|
||
logger.Info("订单创建成功",
|
||
zap.String("traceId", traceId),
|
||
zap.String("userId", userId),
|
||
zap.String("orderId", orderId),
|
||
zap.Int64("costMs", costMs),
|
||
)
|
||
``` |