应主动采集并结构化传递业务上下文参数,利用mdc绑定请求标识,结合slf4j/logback或log4j2输出结构化日志,统一异常拦截点补充全局信息,并避免堆栈截断、消息拼接、mdc污染及错误日志级别。

Java 异常处理中,仅打印堆栈(e.printStackTrace())或简单记录日志(log.error("xxx", e))无法还原异常发生时的业务上下文。要完整保留环境现场参数,关键在于:在异常捕获点主动采集并结构化传递关键变量、请求上下文、线程信息等,再结合日志框架能力输出可追溯的完整现场。
捕获异常时显式收集现场参数
不要依赖异常对象自带的信息——它不包含业务变量。应在 catch 块中主动提取当前作用域内有意义的数据,并封装进日志上下文或日志参数中:
- 提取方法入参(如
userId、orderId、requestId)、局部计算结果、状态标志位等; - 避免记录敏感数据(密码、token、身份证号),可用脱敏工具(如
StringUtils.substring(...))处理; - 用 MDC(Mapped Diagnostic Context)临时绑定请求级标识,例如:
MDC.put("reqId", requestId); MDC.put("userId", String.valueOf(userId));
后续所有同一线程内的日志自动携带这些字段;
使用结构化日志框架输出带上下文的异常
推荐使用 SLF4J + Logback 或 Log4j2,并配置支持 JSON 输出和 MDC 自动注入的 appender:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- Logback 示例(
logback-spring.xml)中启用%X{reqId}、%X{userId}等 MDC 变量; - 搭配
log.error("订单支付失败,用户{},订单{}", userId, orderId, e)—— 参数位置与异常对象e必须放在最后,SLF4J 才能正确识别为异常; - 若用 Log4j2,可启用
ThrowablePatternConverter并配合JsonLayout,将异常堆栈、MDC 字段、日志参数一并序列化为结构化 JSON;
统一异常拦截点补充全局上下文
在 Spring 环境中,利用 @ControllerAdvice + @ExceptionHandler 统一处理未捕获异常,此时可补充跨切面信息:
- 从
RequestContextHolder获取当前 HTTP 请求的 URL、Header、IP; - 读取
SecurityContextHolder中的认证主体(用户名、角色); - 调用
Thread.currentThread().getStackTrace()获取触发异常的具体调用链(谨慎使用,性能开销大,建议仅调试期开启); - 将上述信息合并进 MDC 或直接作为日志参数传入
log.error();
避免常见陷阱
以下做法会削弱现场还原能力:
- 在多层嵌套
catch中反复throw new RuntimeException(e)—— 原始堆栈被截断,丢失最初异常位置; - 用
e.getMessage()拼接日志字符串(如"失败:" + e.getMessage())—— 堆栈丢失,且可能因 null 导致 NPE; - MDC 未及时清理(尤其在线程池复用场景)—— 下一个请求误带前一个请求的
reqId,造成日志污染;务必在 finally 或 filter 中MDC.clear(); - 日志级别设为 WARN 或 INFO 记录异常 —— 多数监控系统只抓 ERROR 级别,导致告警漏报;
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










