8.9 KiB
8.9 KiB
日志规范完善版 (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 当前项目中的对应实现
项目里已有:
DefaultControllerLogAspectControllerLogBOBusinessControllerExceptionHandlerTraceContextUtil
这套实现已经具备 Controller 统一记录的基础,后续建议补齐:
uid的获取逻辑traceId/spanId在日志中的统一输出- 对不同异常类型的日志级别区分
4. Service 层日志规范
Service 层是业务核心,不负责“每一步都打日志”,而是负责记录对业务有里程碑意义的节点。
4.1 应记录的场景
建议在以下场景记录业务日志:
- 核心对象创建
- 核心对象删除
- 业务状态变化
- 数据同步完成
- 流程节点切换
- 核心任务开始与结束
- 影响用户结果的关键决策点
4.2 不建议记录的场景
以下情况一般不应在 Service 层重复记录:
- 下游抛出的异常已经在下游记录过
- 单纯的参数透传
- 没有业务意义的中间变量变化
- 每次循环都打印相同信息
4.3 Service 层建议关注的日志内容
一条好的业务日志通常包含:
- 业务对象 ID
- 业务动作
- 状态前后变化
- 处理结果
- 关键条件分支
示例字段:
orderIduserIdbeforeStatusafterStatusactionresult
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. 推荐的统一输出格式
建议日志内容尽量结构化,方便后续检索和分析。
示例结构:
{"traceId":"...","spanId":"...","uid":"...","className":"...","methodName":"...","uri":"...","durationMs":123,"status":"OK","exception":null,"desc":"..."}
对于外部调用,可再补充:
targetrequestSummaryresponseSummaryretryCountremoteCode
9. 常见误区
9.1 过度记录
问题表现:
- 一个方法内打印很多行
- 同一异常被多层重复打印
- 每个中间步骤都记日志
后果:
- 日志噪音过大
- 关键问题被淹没
- 排障效率下降
9.2 只记异常,不记上下文
问题表现:
- 只有
NullPointerException - 没有用户、接口、调用目标、耗时
后果:
- 无法快速定位具体链路
9.3 打印敏感信息
问题表现:
- 直接打印完整请求体
- 直接打印密码或 token
后果:
- 安全风险
- 合规风险
10. 新增接口或调用点时的检查清单
新增代码前,建议逐项确认:
- 这段代码属于 Controller、Service 还是 Utils/外部调用
- 是否已经有统一切面或统一处理器
- 是否只记录了应该记录的那一层
- 是否避免了重复打印下游异常
- 是否包含
traceId/spanId - 是否包含业务关键字段
- 是否脱敏了敏感数据
- 是否记录了耗时和结果状态
- 是否对异常级别做了合理区分
11. 建议落地顺序
如果要逐步完善当前项目,建议按下面顺序推进:
- 统一
traceId/spanId输出 - 补齐
uid获取逻辑 - 完善 Controller 统一访问日志
- 补充 Service 业务里程碑日志
- 为外部调用增加前后日志模板
- 统一异常分类与日志级别
12. 总结
这份规范的核心只有三句话:
- Controller 记录入口和兜底异常
- Service 记录业务里程碑
- Utils / 跨服务调用记录前后摘要、耗时和状态
如果所有日志都遵守“谁负责谁记录、下游异常不重复记、日志只保留关键上下文”这三个原则,后续排障和审计都会轻松很多。