335 lines
8.9 KiB
Markdown
335 lines
8.9 KiB
Markdown
# 日志规范完善版 (By ChatGPT)
|
||
|
||
> 基于现有 `日志规范说明.md` 整理,目标是把“什么时候记、谁来记、记什么、记到什么程度”说清楚,便于后续统一埋点和排查问题。
|
||
|
||
## 1. 文档目标
|
||
|
||
日志的核心目标不是“尽可能多地打印”,而是为了后续能够:
|
||
|
||
- 快速定位问题发生在哪一层
|
||
- 还原一次请求的完整链路
|
||
- 区分业务异常、参数异常和系统异常
|
||
- 追踪核心业务动作是否执行成功
|
||
- 在网络调用、数据库调用、SDK 调用等场景下,快速确认耗时和返回状态
|
||
|
||
## 2. 总体原则
|
||
|
||
### 2.1 谁负责记录谁的日志
|
||
|
||
- **Controller**:负责接口入口层的访问日志和兜底异常日志
|
||
- **Service**:负责业务里程碑日志
|
||
- **Utils / RPC / HTTP / Redis / DB / SDK**:负责外部依赖调用的前后日志
|
||
- **下游异常**:由下游自己记录,不在上游重复记录
|
||
|
||
### 2.2 日志要满足的最小信息
|
||
|
||
一条可用日志,至少应能回答以下问题:
|
||
|
||
- 谁发起的:`uid`、`traceId`
|
||
- 调了什么:类名、方法名、接口 URI、目标服务名
|
||
- 做了多久:`durationMs`、耗时统计
|
||
- 结果如何:成功、失败、HTTP 状态码、业务状态码
|
||
- 为什么失败:异常类型、必要的错误摘要
|
||
|
||
### 2.3 避免重复记录
|
||
|
||
当异常已经在下游被明确处理并记录时,上游不要再重复打 ERROR:
|
||
|
||
- 下游负责记录:具体依赖失败、远程超时、数据库异常等
|
||
- 上游负责感知:把异常继续抛出,或转换为统一业务异常
|
||
- 上游只保留必要的上下文日志,不重复打印同一条堆栈
|
||
|
||
## 3. Controller 层日志规范
|
||
|
||
Controller 是接口入口层,重点关注“请求进来了、请求结果是什么、是否发生兜底异常”。
|
||
|
||
### 3.1 必须记录的内容
|
||
|
||
由 `@DefaultControllerLog` 统一记录:
|
||
|
||
- 接口描述 `desc`
|
||
- 类名 `className`
|
||
- 方法名 `methodName`
|
||
- 请求 URI `uri`
|
||
- 用户标识 `uid`
|
||
- 耗时 `durationMs`
|
||
- 响应状态 `status`
|
||
- 异常类型 `exception`
|
||
|
||
### 3.2 记录方式
|
||
|
||
Controller 推荐只保留一层统一切面:
|
||
|
||
- 请求进入时开始计时
|
||
- 方法正常返回后记录成功日志
|
||
- 方法抛出异常后记录失败日志
|
||
- 在 `finally` 中完成统一输出,保证无论成功失败都能落日志
|
||
|
||
### 3.3 Controller 的异常处理原则
|
||
|
||
- Controller 内部应有 **try-catch 兜底** 的意识,但不要吞掉异常
|
||
- 如果异常来自下游服务、数据库、SDK 等,应该继续向上抛出,由对应下游层自己先记录
|
||
- 如果是 Controller 自身拼装、参数转换、分支判断等导致的异常,可以记录兜底日志
|
||
- 参数解析失败、JSON 反序列化失败等客户端问题,优先作为 **400 类问题** 处理,不应误报成系统故障
|
||
|
||
### 3.4 建议记录示例
|
||
|
||
- 成功请求:记录 `INFO`
|
||
- 业务异常:记录 `WARN` 或 `INFO`,视业务重要程度决定
|
||
- 未预期异常:记录 `ERROR`
|
||
|
||
### 3.5 当前项目中的对应实现
|
||
|
||
项目里已有:
|
||
|
||
- `DefaultControllerLogAspect`
|
||
- `ControllerLogBO`
|
||
- `BusinessControllerExceptionHandler`
|
||
- `TraceContextUtil`
|
||
|
||
这套实现已经具备 Controller 统一记录的基础,后续建议补齐:
|
||
|
||
- `uid` 的获取逻辑
|
||
- `traceId` / `spanId` 在日志中的统一输出
|
||
- 对不同异常类型的日志级别区分
|
||
|
||
## 4. Service 层日志规范
|
||
|
||
Service 层是业务核心,不负责“每一步都打日志”,而是负责记录对业务有里程碑意义的节点。
|
||
|
||
### 4.1 应记录的场景
|
||
|
||
建议在以下场景记录业务日志:
|
||
|
||
- 核心对象创建
|
||
- 核心对象删除
|
||
- 业务状态变化
|
||
- 数据同步完成
|
||
- 流程节点切换
|
||
- 核心任务开始与结束
|
||
- 影响用户结果的关键决策点
|
||
|
||
### 4.2 不建议记录的场景
|
||
|
||
以下情况一般不应在 Service 层重复记录:
|
||
|
||
- 下游抛出的异常已经在下游记录过
|
||
- 单纯的参数透传
|
||
- 没有业务意义的中间变量变化
|
||
- 每次循环都打印相同信息
|
||
|
||
### 4.3 Service 层建议关注的日志内容
|
||
|
||
一条好的业务日志通常包含:
|
||
|
||
- 业务对象 ID
|
||
- 业务动作
|
||
- 状态前后变化
|
||
- 处理结果
|
||
- 关键条件分支
|
||
|
||
示例字段:
|
||
|
||
- `orderId`
|
||
- `userId`
|
||
- `beforeStatus`
|
||
- `afterStatus`
|
||
- `action`
|
||
- `result`
|
||
|
||
### 4.4 异常处理原则
|
||
|
||
- Service 层可对业务异常做转换,但不要重复打印同一个下游异常堆栈
|
||
- 如果 Service 自己发现业务不成立,应抛出明确的业务异常
|
||
- 如果是资源、网络、数据库等问题,交给下游或统一异常处理层记录
|
||
|
||
## 5. Utils 与跨服务调用日志规范
|
||
|
||
Utils、RPC、HTTP Client、Redis、DB、SDK 等调用是最容易出现网络波动和性能问题的区域,因此必须前后成对记录。
|
||
|
||
### 5.1 调用前必须记录
|
||
|
||
调用发起前,应记录以下信息:
|
||
|
||
- 目标名称:服务名、库名、SDK 名称、URL、Redis Key 前缀等
|
||
- 请求摘要:请求参数的关键字段,避免打印敏感数据
|
||
- 调用目的:本次调用是为了什么业务动作
|
||
- 关联链路:`traceId`、`spanId`
|
||
|
||
### 5.2 调用后必须记录
|
||
|
||
调用返回后,应记录:
|
||
|
||
- 耗时 `durationMs`
|
||
- 返回结果摘要
|
||
- 对方状态码或业务状态码
|
||
- 是否超时
|
||
- 是否重试
|
||
|
||
### 5.3 建议关注的日志内容
|
||
|
||
对于外部依赖,重点不是“全量打印响应”,而是以下信息:
|
||
|
||
- 请求目标
|
||
- 请求关键参数
|
||
- 返回耗时
|
||
- 返回状态
|
||
- 失败原因
|
||
|
||
### 5.4 不应记录的内容
|
||
|
||
- 密码、令牌、密钥、身份证号等敏感信息
|
||
- 过大的请求/响应全文
|
||
- 无法脱敏的用户隐私数据
|
||
- 已知会产生大量噪音的调试级细节
|
||
|
||
### 5.5 异常处理原则
|
||
|
||
- 下游调用失败,由下游先记录
|
||
- 上游只做必要的异常转换或透传
|
||
- 若需要重试,应记录每次重试的次数和最终结果
|
||
|
||
## 6. 日志字段建议
|
||
|
||
建议统一使用以下基础字段:
|
||
|
||
- `traceId`:一次请求链路的全局标识
|
||
- `spanId`:当前调用点标识
|
||
- `uid`:用户标识
|
||
- `className`:类名
|
||
- `methodName`:方法名
|
||
- `uri`:请求路径或目标路径
|
||
- `durationMs`:耗时
|
||
- `status`:HTTP 状态或调用状态
|
||
- `exception`:异常类型或摘要
|
||
- `desc`:业务描述
|
||
|
||
### 6.1 字段使用建议
|
||
|
||
- `traceId`:用于串联一次请求的完整链路
|
||
- `spanId`:用于区分同一请求内的不同调用点
|
||
- `uid`:用于追踪用户行为
|
||
- `durationMs`:用于性能分析
|
||
- `status`:用于快速判断结果是否成功
|
||
- `exception`:用于快速定位失败类型
|
||
|
||
## 7. 日志级别建议
|
||
|
||
### 7.1 INFO
|
||
|
||
适用于:
|
||
|
||
- 请求正常完成
|
||
- 核心业务动作成功
|
||
- 外部调用成功且有分析价值
|
||
|
||
### 7.2 WARN
|
||
|
||
适用于:
|
||
|
||
- 客户端参数错误
|
||
- 可预期的业务失败
|
||
- 可恢复的下游失败
|
||
- 需要关注但不一定影响主流程的异常
|
||
|
||
### 7.3 ERROR
|
||
|
||
适用于:
|
||
|
||
- 未预期系统异常
|
||
- 无法恢复的故障
|
||
- 需要立即排查的严重错误
|
||
|
||
## 8. 推荐的统一输出格式
|
||
|
||
建议日志内容尽量结构化,方便后续检索和分析。
|
||
|
||
示例结构:
|
||
|
||
```text
|
||
{"traceId":"...","spanId":"...","uid":"...","className":"...","methodName":"...","uri":"...","durationMs":123,"status":"OK","exception":null,"desc":"..."}
|
||
```
|
||
|
||
对于外部调用,可再补充:
|
||
|
||
- `target`
|
||
- `requestSummary`
|
||
- `responseSummary`
|
||
- `retryCount`
|
||
- `remoteCode`
|
||
|
||
## 9. 常见误区
|
||
|
||
### 9.1 过度记录
|
||
|
||
问题表现:
|
||
|
||
- 一个方法内打印很多行
|
||
- 同一异常被多层重复打印
|
||
- 每个中间步骤都记日志
|
||
|
||
后果:
|
||
|
||
- 日志噪音过大
|
||
- 关键问题被淹没
|
||
- 排障效率下降
|
||
|
||
### 9.2 只记异常,不记上下文
|
||
|
||
问题表现:
|
||
|
||
- 只有 `NullPointerException`
|
||
- 没有用户、接口、调用目标、耗时
|
||
|
||
后果:
|
||
|
||
- 无法快速定位具体链路
|
||
|
||
### 9.3 打印敏感信息
|
||
|
||
问题表现:
|
||
|
||
- 直接打印完整请求体
|
||
- 直接打印密码或 token
|
||
|
||
后果:
|
||
|
||
- 安全风险
|
||
- 合规风险
|
||
|
||
## 10. 新增接口或调用点时的检查清单
|
||
|
||
新增代码前,建议逐项确认:
|
||
|
||
- [ ] 这段代码属于 Controller、Service 还是 Utils/外部调用
|
||
- [ ] 是否已经有统一切面或统一处理器
|
||
- [ ] 是否只记录了应该记录的那一层
|
||
- [ ] 是否避免了重复打印下游异常
|
||
- [ ] 是否包含 `traceId` / `spanId`
|
||
- [ ] 是否包含业务关键字段
|
||
- [ ] 是否脱敏了敏感数据
|
||
- [ ] 是否记录了耗时和结果状态
|
||
- [ ] 是否对异常级别做了合理区分
|
||
|
||
## 11. 建议落地顺序
|
||
|
||
如果要逐步完善当前项目,建议按下面顺序推进:
|
||
|
||
1. 统一 `traceId` / `spanId` 输出
|
||
2. 补齐 `uid` 获取逻辑
|
||
3. 完善 Controller 统一访问日志
|
||
4. 补充 Service 业务里程碑日志
|
||
5. 为外部调用增加前后日志模板
|
||
6. 统一异常分类与日志级别
|
||
|
||
## 12. 总结
|
||
|
||
这份规范的核心只有三句话:
|
||
|
||
- **Controller 记录入口和兜底异常**
|
||
- **Service 记录业务里程碑**
|
||
- **Utils / 跨服务调用记录前后摘要、耗时和状态**
|
||
|
||
如果所有日志都遵守“谁负责谁记录、下游异常不重复记、日志只保留关键上下文”这三个原则,后续排障和审计都会轻松很多。
|
||
|