您现在的位置: 365建站网 > 365文章 > Java 异常日志:printStackTrace 与 getMessage 的差别及正确写法

Java 异常日志:printStackTrace 与 getMessage 的差别及正确写法

文章来源:365jz.com 点击数:11 更新时间:2025-09-14 22:59

printStackTrace() 和 getMessage() 是 Java 异常处理的两个基础方法,但很多人把它们当成“打日志”的唯一手段,结果线上出问题只能看到一行 NullPointerException,既没有原因也没有上下文。本文说明两者的信息差异、正确的日志写法与性能注意点。

Java异常处理中printStackTrace和getMessage获取错误日志

一、三个方法各自输出什么

try {
    int r = 10 / divisor;
} catch (ArithmeticException e) {
    e.getMessage();        // "/ by zero"  仅异常消息文本
    e.toString();          // "java.lang.ArithmeticException: / by zero"
    e.printStackTrace();   // 完整堆栈,输出到 System.err
}

关键差异:getMessage() 只有一句话,甚至可能返回 null(很多异常没带消息,如未经构造参数创建的 NPE);printStackTrace() 才能看到“哪个类的哪一行抛出、经由哪些调用栈、是否由其他异常引起”。只看 message 排查,等于丢掉 90% 的定位信息。

二、printStackTrace 的三个问题

  1. 输出目标是 System.err,不走日志框架,容器/线上环境常被丢弃或与其他日志混排。
  2. 没有时间与级别,无法按 traceId 关联业务请求。
  3. 吞掉 cause 的风险:自定义异常包装时若没传 cause,堆栈链条就断了。

三、正确做法:交给日志框架

// SLF4J / Logback:把异常对象作为最后一个参数传入,不要拼接字符串
private static final Logger log = LoggerFactory.getLogger(XxxService.class);

try {
    orderMapper.insert(order);
} catch (DataAccessException e) {
    log.error("下单失败, userId={}, orderId={}", userId, orderId, e);
    // 注意:e 放在最后且不占位,Logback 会自动打印完整堆栈
}

// 反例(会丢掉堆栈)
log.error("下单失败: " + e.getMessage());
log.error("下单失败: {}", e);      // 只调 toString,堆栈不输出

这是 Java 日志最经典的误用:把异常写进占位符而不是作为最后一个参数,结果日志里只有一行 toString()。SLF4J 的规则是:最后一个参数为 Throwable 且未与之对应的占位符时,输出完整堆栈。

四、异常链与 cause 的正确写法

public class OrderException extends RuntimeException {
    public OrderException(String msg, Throwable cause) { super(msg, cause); }
}

try {
    pay();
} catch (IOException e) {
    throw new OrderException("支付阶段失败", e);   // 必须传 cause
}

传了 cause,日志会打印 Caused by: java.io.IOException: ... 及其原始堆栈,形成完整链条;漏传则真正的错误来源永远看不到。

五、性能与线上注意事项

  • 填充堆栈是昂贵的:Throwable.fillInStackTrace() 需要遍历调用栈,在高频循环里抛异常性能损失显著。可用重写 fillInStackTrace 返回 this 的轻量异常(例如高频校验类异常),或改用返回值/Optional。
  • 不要吞异常:catch (Exception e) {} 空捕获是最危险的写法,至少要 log.warn。
  • 日志级别:可恢复的用 warn,影响业务的用 error;不要把正常的“未找到记录”打成 error 造成告警噪音。
  • 输出到字符串(测试/上报场景):
public static String stackToString(Throwable t) {
    StringWriter sw = new StringWriter();
    t.printStackTrace(new PrintWriter(sw));
    return sw.toString();
}

Web 场景常用它把堆栈写入监控上报,但注意敏感信息脱敏后再外发。

六、JDK 新特性简述

Java 14 起默认开启有用异常信息(Helpful NullPointerException),NPE 消息会明确指出哪个变量为 null(形如 Cannot invoke "String.length()" because "s" is null),老版本只给一个空消息。getMessage() 的实用度因此大幅提升;若跑在 JDK 11 及以下且需要该特性,可加 -XX:+ShowCodeDetailsInExceptionMessages(Java 15 后默认开启)。

七、常见问题 FAQ

问:printStackTrace 和 log.error 可以混用吗?
答:生产代码应统一走日志框架,printStackTrace 只适合本地临时调试。

问:怎么让日志带 traceId?
答:用 MDC:MDC.put("traceId", id),并在 appender pattern 里加 %X{traceId}。

问:catch 到 Exception 再抛 RuntimeException 行不行?
答:可以,但一定传 cause;同时注意事务回滚行为(Spring 默认只对 RuntimeException 回滚)。

结论:getMessage() 只用于给用户看的提示文案,绝不能用它替代堆栈定位;日志请写成 log.error("业务描述, k1={}", v1, e),包装异常务必传 cause,高频路径少抛异常。

生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »