核心是按异常性质分类处理、控制记录层级、保留关键上下文:业务异常用 warn 级并带业务标识;系统异常用 error 级且堆栈完整;逻辑错误由全局处理器统一记录;只在终结传播处记一次;日志须含 traceid 等 mdc 信息及脱敏关键变量;堆栈需全量输出。

捕获并记录生产环境的异常,核心不是“全记”或“不记”,而是分清哪些该记、在哪记、怎么记才能真正帮人快速定位问题。重点在于按异常性质分类处理、控制记录层级、保留关键上下文。
区分异常类型,决定是否记录及日志级别
不是所有异常都值得打 ERROR 或单独记日志。关键看它代表的是业务规则拒绝,还是系统真实故障:
-
业务异常(如
IllegalArgumentException、自定义InsufficientBalanceException):属于可预期流程分支,通常用logger.warn(),带业务标识(如订单号、用户ID),便于统计和监控,但不触发告警 -
系统异常(如
IOException、SQLException、TimeoutException):反映外部依赖失败或运行时缺陷,必须用error级别,并确保堆栈完整 -
逻辑错误类 RuntimeException(如
NullPointerException、ArrayIndexOutOfBoundsException):上线后不应捕获并静默处理,应暴露给全局处理器——日志由最外层统一记录,否则掩盖 bug
只在真正终结异常传播的位置记录一次
重复记录同一异常是日志噪音的主要来源,既浪费磁盘又干扰根因定位:
- DAO 层捕获
SQLException后包装为ServiceException并抛出 → 不记录日志,只保留异常链:throw new ServiceException("库存扣减失败", e) - Service 层做重试或降级(如查不到用户头像就返回默认图)→ 此处才记录
logger.warn("头像CDN加载失败,启用默认头像", e) - Controller 层或全局
@ExceptionHandler拦截未处理异常 → 统一记录error日志,且仅此一处
记录时必须带上可追溯的业务上下文
光有堆栈无法复现问题,需让每条异常日志自带“现场感”:
- 使用 MDC 注入请求级字段:
MDC.put("traceId", tid); MDC.put("userId", userId); MDC.put("orderId", orderId); - 日志语句中显式拼入关键变量:
logger.error("支付回调验签失败, orderNo={}, sign={}", orderNo, sign, e) - 敏感信息必须脱敏:
userToken记为"userToken present: true",而非明文打印
确保堆栈完整输出,避免被日志框架截断
生产环境常因配置不当导致嵌套异常只显示最外层,根本原因被隐藏:
- Logback 中使用
%xEx{full}或至少%xEx{10}(RPC 框架一般封装 3–4 层) - Log4j2 对应使用
%throwable{full} - 禁用无参数的
%ex—— 它默认只打印顶层异常,极易丢掉Cause - 始终把
Throwable作为logger.error()的最后一个参数,不可拼进字符串
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











