关键是要清晰呈现“谁、在什么场景下、因为什么、导致哪一层问题”,需日志、异常结构、线程上下文协同,MDC是最轻量有效机制;应在请求入口(如Spring Filter)统一注入traceId等标识,务必finally清除;异常日志必须用logger.error(String, Throwable)并传完整Throwable;拦截器中禁止静默吞异常;异步与跨服务需用TTL透传MDC并自动注入HTTP头。

关键不是记下“哪里错了”,而是让人一眼看清“谁、在什么场景下、因为什么、导致了哪一层的问题”。这需要日志内容、异常结构、线程上下文三者协同配合。MDC 是实现这一目标最轻量也最有效的机制。
在请求入口统一注入 traceId 等业务标识
所有日志要能串联,前提是每个请求开始时就绑定唯一上下文。推荐在 Spring 的 Filter 或 WebMvcConfigurer 的拦截器中完成:
- 从 HTTP 请求头(如 X-Trace-ID 或 traceparent)读取上游传来的 traceId;若无,则生成新 ID(如 UUID 或雪花 ID)
- 同时注入 userId、orderId、requestUri 等高频排查字段: MDC.put("traceId", id); MDC.put("userId", userId); MDC.put("orderId", orderNo);
- 务必在 finally 块或 afterCompletion 方法中调用 MDC.clear(),避免线程复用污染后续请求
异常日志必须传整个 Throwable 对象,且放参数末尾
SLF4J 的 logger.error(String, Throwable) 是唯一能完整保留堆栈、cause 链、suppressed 异常的入口:
- ✅ 正确:logger.error("支付验签失败,订单号:{}", orderNo, e);
- ❌ 错误:logger.error("支付验签失败:" + e.getMessage(), e);(重复拼接、可能 NPE、掩盖原始语义)
- ❌ 错误:logger.error(e.toString()); 或 e.printStackTrace();(绕过日志框架、丢失结构、不可检索)
Logback 配置中需启用完整堆栈输出,例如 pattern 含 %ex,确保 cause 链逐层展开可读。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
拦截器/切面中严禁静默吞异常或浅层包装
多层拦截器(Auth → Trace → Log)是链路断层高发区。一旦某层 catch 后未透传原始异常,traceId 就会丢失:
- 每个 catch 块末尾必须显式 throw e 或 throw new BizException("msg", e),禁止只 log.warn("ignore", e) 后结束
- 禁用 @SneakyThrows(默认不设 cause),若必须用,需手动传入原始异常
- 检查是否在异常路径中误调 MDC.clear() 或 ThreadLocal.remove()——建议在 catch 中先快照上下文:log.error("异常捕获点上下文: {}", MDC.getCopyOfContextMap(), e);
异步与跨服务场景必须透传 MDC
MDC 基于 ThreadLocal,默认不跨线程。CompletableFuture、@Async、线程池等场景需主动透传:
- 使用 TransmittableThreadLocal(TTL) 替代原生 MDC,它支持线程池任务自动继承上下文
- HTTP 调用下游时,RestTemplate 或 FeignClient 拦截器中自动注入 header:headers.set("X-Trace-ID", MDC.get("traceId"));
- 下游服务收到请求后,立即从 header 提取并写回 MDC,保证链路连续

















