全局异常处理器中必须将异常对象作为最后一个参数传给logger.error(),并配置日志pattern包含%ex,结合mdc补充traceid等上下文,禁止无效包装或吞异常。

全局异常处理器里必须传异常对象
用 @ControllerAdvice 或 @RestControllerAdvice 拦截异常后,记录日志时一定要把捕获到的异常对象作为 最后一个参数 传给 logger.error()。框架才能自动展开完整堆栈、嵌套异常(Caused by)和抑制异常(Suppressed)。
- ✅ 正确:
logger.error("订单创建失败, orderId:{}", orderId, ex) - ❌ 错误:
logger.error("订单创建失败:" + ex.getMessage())→ 无堆栈 - ❌ 错误:
logger.error("订单创建失败", ex.getMessage())→ 第二个参数不是 Throwable,堆栈被忽略
日志配置要显式启用堆栈输出
即使代码传对了异常对象,如果日志 pattern 里没声明堆栈占位符,文件中依然只有一行错误消息。
- Logback(
logback.xml)需包含%ex或%xEx:%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%ex%n - Log4j2(
log4j2.xml)需包含%ex或%throwable:%d{HH:mm:ss.SSS} [%t] %-5level %c{36} - %m%ex%n
用 MDC 补齐关键上下文再打日志
全局处理器通常在请求入口统一注入 MDC 字段,比如 traceId、userId、method 等。这样每条 error 日志天然带业务现场,不用在每个 catch 块里重复拼。
- 在过滤器或拦截器中:
MDC.put("traceId", request.getHeader("X-Trace-ID"))、MDC.put("userId", userId) - 日志 pattern 中加入:
%X{traceId} %X{userId} - 最终日志形如:
2026-10-14 07:05:22.112 [http-nio-8080-exec-3] ERROR c.e.c.GlobalExceptionHandler - 订单创建失败, orderId:20261014001 java.lang.NullPointerException%ex
避免在全局处理器里做无效包装或吞异常
全局异常处理器是最后一道防线,不是中间层。不要在这里把异常再包一层 RuntimeException 后抛出,也不要空 catch 后静默返回。
- 别写:
throw new RuntimeException("全局兜底异常", ex)→ 导致堆栈重复、根因模糊 - 别写:
catch (Exception e) { /* 什么都不干 */ }→ 异常消失,问题无法追踪 - 建议对不同异常类型分级别处理:第三方调用失败用 warn,空指针或数据库连接中断才用 error
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











