核心是获取完整结构化调用链:printStackTrace()仅限开发调试,输出异常类型、精确抛出点(文件+行号)及逐层调用链;生产环境必须用logger.error("msg", e)记录全栈并注入MDC上下文;字符串化堆栈应使用StringWriter+PrintWriter,遇包装异常需递归getCause()挖掘根源。

要获取详细的错误堆栈信息,核心是拿到完整的、结构化的调用链路,而不是只看异常类型或一句话描述。关键不在于“有没有堆栈”,而在于“是否保留了源头位置、每一层调用关系、以及嵌套原因”。
用 printStackTrace() 快速看全貌(仅限开发调试)
在 catch 块里直接调用 e.printStackTrace() 是最直观的方式。它会输出三部分:
- 第一行:异常类名 + getMessage() 内容(可能为空)
- 第二行:抛出点——精确到文件名、行号、方法名(这才是真正出问题的地方)
- 后续各行:从内到外的完整调用链,每行一个 StackTraceElement
注意:它默认写入 System.err,控制台能看到,但生产环境不能依赖它——没上下文、不可配置、不进日志系统。
生产环境必须用日志框架记录全栈
正确做法是使用 SLF4J、Log4j 等日志器的 error(String msg, Throwable t) 方法:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
- ✅ logger.error("订单处理失败", e); —— 自动展开完整堆栈,含缩进、换行、cause 链
- ❌ logger.error("订单处理失败: " + e.getMessage()); —— 只有文字,没有堆栈
- ❌ logger.error("失败", e.toString()); —— 类名+消息,依然无堆栈
日志框架还能自动注入 MDC 上下文(如 traceId、userId),这对排查线上问题至关重要。
需要字符串形式堆栈时,别拼接,用 StringWriter + PrintWriter
当你要把堆栈存数据库、发监控、或拼进 JSON 字段时,必须转成字符串,且不能丢信息:
- 用 StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String stackTrace = sw.toString(); - 别用
Arrays.toString(e.getStackTrace()):格式难读,无类名缩进,漏 cause 和 suppressed 异常 - 也别用
e.toString():它不含任何堆栈行,只是 “java.lang.NullPointerException: null” 这种静态描述
遇到包装异常,得一层层挖根源
很多异常是被层层包装的(比如 WebException → RuntimeException → SQLException → IOException)。只看最外层,容易误判:
- 用 e.getCause() 拿到下一层异常
- 递归调用直到
getCause() == null,或用 Apache Commons Lang 的ThrowableUtils.getRootCause(e) - 日志中若只记外层异常,等于把“用户登录失败”记成“HTTP 500”,却没写清其实是数据库连接超时

















