java异常日志需用mdc注入traceid、userid等上下文,配合完整堆栈记录与脱敏分级,确保“谁、在什么场景下、因何导致哪层问题”一目了然。

保留完整的上下文链条,关键不是记下“哪里错了”,而是让人一眼看清“谁、在什么场景下、因为什么、导致了哪一层的问题”。这需要日志内容、异常结构、线程上下文三者协同配合。
用 MDC 注入业务标识,让日志自动带上下文
单靠堆栈无法回答“这个异常属于哪个用户、哪个订单、哪次请求”。MDC(Mapped Diagnostic Context)是解决这个问题最轻量也最有效的机制:
- 在请求入口(如 Spring 的拦截器或 Filter)中,提前注入 traceId、userId、orderId 等字段:
MDC.put("traceId", generateTraceId()); MDC.put("userId", user.getId()); - 后续所有 logger.error() 输出的日志,都会自动携带这些键值对,无需手动拼接
- 务必在请求结束时调用
MDC.clear(),避免线程复用导致上下文污染 - 日志配置中需启用 MDC 支持,例如 Logback 的 pattern 可设为:
%d{HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}] [%X{userId}] %logger{36} - %msg%n
异常必须保留 cause 链,禁止切断原始根因
业务异常包装技术异常时,堆栈链一旦断裂,排查就只能靠猜:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 自定义异常类必须提供
public XxxException(String msg, Throwable cause)构造器,并调用super(msg, cause) - 捕获 SQLException 后,应写成
throw new OrderCreateException("创建订单失败", e),而不是throw new OrderCreateException("DB error: " + e.getMessage()) - 若只是透传不改语义,直接
throw e;最安全——它不新增堆栈帧,原始抛出点保持可见 - 绝对禁用
e.initCause(...)或new RuntimeException(e)这类单参构造,它们会覆盖原始 cause 或丢失堆栈
日志记录必须传整个异常对象,且放在参数末尾
SLF4J/Logback 的 error(String, Throwable) 重载方法是唯一能完整提取堆栈、cause、suppressed 异常的入口:
- ✅ 正确写法:
logger.error("支付回调验签失败,订单号:{}", orderNo, e) - ❌ 错误写法:
logger.error("支付回调验签失败:" + e.getMessage(), e)(重复 message、可能 NPE、无意义拼接) - ❌ 错误写法:
logger.error(e.toString())或e.printStackTrace()(丢失结构、不可检索、不走日志框架) - 确保日志配置启用完整堆栈输出,Logback 中使用
%ex,Log4j2 中使用%throwable{full}
脱敏 + 分级 + 不重复,让日志真正可读可用
上下文链条再完整,如果混入敏感信息或被噪声淹没,依然无法快速定位问题:
- 手机号、身份证、token 等字段,在写入日志前统一脱敏,例如替换为
1XXXXXXXXX或仅记录存在性:"userToken present: true" - 区分 error 和 warn:数据库连接中断、NPE 属于 error;参数校验不通过、第三方接口降级返回,用 warn 更合理
- 同一异常只在最外层记录一次。Service 层 catch 了又 throw 出去,Controller 层再记录——这是冗余;应在最终决定如何响应的位置(通常是 Controller 或全局异常处理器)统一打点
- 前端响应体中绝不透传原始异常 message 或堆栈,统一由 ErrorCode 枚举填充 message,code 对应业务含义,traceId 全链路透传用于关联分析
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










