Java异常日志需同时输出业务变量值和完整堆栈,推荐SLF4J+Logback,用占位符传参(异常对象置末位)、MDC注入请求上下文,并封装工具类统一处理。

在 Java 中捕获异常时打印完整上下文参数,关键不是只调用 e.printStackTrace() 或只记录异常消息,而是把「发生异常时的关键业务变量值」和「异常堆栈」一起结构化输出。推荐使用 SLF4J + Logback(或 Log4j2),配合占位符、MDC 和自定义日志格式实现清晰、可排查的上下文记录。
用占位符显式传入上下文参数
避免拼接字符串,用 SLF4J 的参数化日志语法,在日志语句中直接传入变量值。这样既安全(防空指针、防注入),又确保上下文与异常强绑定:
try {
String orderId = "ORD-2024-789";
int timeoutMs = 5000;
processOrder(orderId, timeoutMs);
} catch (IOException e) {
logger.error("订单处理失败 | orderId: {}, timeoutMs: {}, cause: {}",
orderId, timeoutMs, e.getMessage(), e);
}
注意:异常对象 e 必须放在参数列表**末尾**,SLF4J 才会将其识别为 Throwable 并自动打印完整堆栈。
用 MDC 动态注入请求级上下文
对 Web 应用或微服务,单次请求的 traceId、userId、ip 等信息适合用 MDC(Mapped Diagnostic Context)统一注入,无需每个 log 语句重复传参:
立即学习“Java免费学习笔记(深入)”;
// 进入请求时(如 Filter 或 Spring Interceptor)
MDC.put("traceId", UUID.randomUUID().toString());
MDC.put("userId", currentUser.getId());
MDC.put("ip", request.getRemoteAddr());
<p>// 日志配置中启用 MDC 输出(logback.xml)
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level [%X{traceId}, %X{userId}, %X{ip}] %logger{36} - %msg%n</pattern></p><p>// 捕获异常时直接写
logger.error("支付回调验签失败", e); // 自动带上 MDC 字段</p>MDC 是线程绑定的,记得在请求结束时调用 MDC.clear(),避免线程复用导致上下文污染。
封装工具方法统一记录带上下文的异常
当多处需要类似日志逻辑(如记录 method、args、result、exception),可封装一个工具类,避免重复代码:
public class LogUtils {
public static void logException(Logger logger, String operation,
Object... context) {
StringBuilder sb = new StringBuilder(operation).append(" 失败 | ");
for (int i = 0; i < context.length; i += 2) {
if (i + 1 < context.length) {
sb.append(context[i]).append("=").append(context[i+1]).append("; ");
}
}
logger.error(sb.toString().trim(), context[context.length - 1]);
}
}
<p>// 使用
LogUtils.logException(logger, "调用短信网关",
"templateId", templateId,
"phone", phone,
"params", params,
e);</p>更推荐的方式是结合 Lombok 的 @Slf4j 和自定义 Logger 包装器,让上下文字段类型安全、IDE 可提示。
避免常见陷阱
-
不要在日志里调用可能抛异常的方法:比如
obj.toString()或list.size()前没判空,会导致日志语句本身崩溃 -
敏感信息脱敏再记录:手机号、身份证、token 等需先
mask(phone)再传入日志,防止日志泄露 -
异步线程中 MDC 不继承:使用
org.slf4j.MDC.getCopyOfContextMap()手动传递,或选用支持 MDC 透传的线程池(如 Log4j2 的 AsyncLogger) -
不要用
e.toString()替代异常对象本身:那样只会输出一行消息,丢失堆栈和 cause 链



















