核心是让每条日志回答“谁、什么场景、执行何操作、错在哪一层”,需MDC自动注入traceId/userId/orderId等上下文,catch块补充业务参数,保留完整异常链,全局统一在@RestControllerAdvice中结构化记录。

要在异常处理中记录完整的错误上下文参数,核心不是“多记点信息”,而是让每条日志都能回答四个关键问题:谁触发的?在什么业务场景下?执行了什么操作?错在哪一层?这需要结构化、自动化和分层协同,而不是靠人工拼字符串。
用 MDC 自动注入请求级上下文
MDC(Mapped Diagnostic Context)是实现上下文自动携带最轻量也最可靠的方式。它把 traceId、userId、orderId 这类标识绑定到当前线程,后续所有日志都会自动带上,无需在每个 catch 块里手动写。
- 在请求入口(如 Spring Filter 或 WebMvcConfigurer 的拦截器)中统一注入:
MDC.put("traceId", request.getHeader("X-Trace-ID") != null ? request.getHeader("X-Trace-ID") : UUID.randomUUID().toString());
MDC.put("userId", getCurrentUserId());
MDC.put("orderId", getOrderIdFromRequest(request)); - 务必在请求结束时调用 MDC.clear(),否则线程池复用会导致上下文串扰
- Logback 配置 pattern 示例:%d{HH:mm:ss.SSS} [%X{traceId}] [%X{userId}] [%X{orderId}] %msg%n
在 catch 块中显式补充关键业务状态
MDC 提供的是“横向”上下文(谁、哪次请求),而 catch 块要补上“纵向”现场快照(当时到底发生了什么)。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 记录当前方法的关键入参或中间变量,比如:logger.error("订单支付回调失败,orderNo={}, status={}, amount={}", orderNo, currentStatus, amount, e);
- 避免只记 e.getMessage() —— 它可能为空,也可能被脱敏截断;更不能写 "error: " + e 这种拼接
- 对敏感字段做前置脱敏,例如用 MaskUtil.maskMobile(userPhone),而不是把原始手机号直接打进去
保留完整异常链并正确传给日志框架
堆栈本身是静态的,但异常链才是动态的“问题演进路径”。切断它,就等于抹掉了从数据库超时→服务降级→前端展示空白的完整因果链。
立即学习“Java免费学习笔记(深入)”;
- 自定义异常必须提供 public XxxException(String msg, Throwable cause) 构造器,并调用 super(msg, cause)
- 捕获 SQLException 后,应写成 throw new PaymentException("支付确认失败", e),而不是 throw new PaymentException(e.toString())
- 日志必须用 logger.error("消息模板 {}", param, e) 形式——只有这个重载方法能完整提取 cause、suppressed 异常和嵌套堆栈
全局统一收口,避免日志散落和重复记录
每个 try-catch 都记一次 error,只会制造噪音。真正有效的做法是:只在最外层(如 @RestControllerAdvice)做一次结构化记录,其他地方专注传递和包装。
- Controller 层不写 try-catch,让异常自然上抛
- 全局异常处理器中,统一提取 traceId、用户身份、请求 URL、入参摘要,填充到日志模板里
- 同一请求内,禁止对同一个异常多次调用 logger.error;若需不同模块记录,用 warn 或 info 级别,并注明角色(如“网关层拦截日志”)

















