捕获异常时应使用日志框架的error(String, Throwable)方法而非e.printStackTrace(),传入异常对象以保留堆栈和cause链,避免占位符语法误用、重复记录、吞掉异常或构造函数未正确委托cause。

捕获异常时别只打printStackTrace()
直接调用 e.printStackTrace() 会把堆栈输出到 System.err,无法被日志框架接管,更没法做分级、异步、归档或对接 ELK。日志框架(如 Logback、Log4j2)的 error(String msg, Throwable t) 方法才是正确入口。
- 必须传入异常对象本身,不能只拼接
e.toString()—— 否则丢失堆栈帧和 cause 链 - 消息字符串建议包含上下文:操作意图、关键参数、ID 等,例如
"Failed to parse JSON for order id=orderId" - 避免在
catch块里重复记录同一异常:上层再 catch 并 log 一次,会造成日志爆炸
log.error("msg", e) 和 log.error("msg: {}", e) 区别很大
后者是 SLF4J 的占位符语法,{} 只接受 Object 参数,Throwable 类型会被当成普通对象处理,**不会解析为堆栈日志**——最终只打印 e.toString(),无堆栈。
- 正确写法只有:
log.error("Failed to save user", e)(两个参数,第二个是Throwable) - 带变量又需异常:用
log.error("Save failed for user,SLF4J 会自动识别末尾的id", e)Throwable - Log4j2 的
logger.error("msg", e)行为一致;但若误写成logger.error("msg {} ", e),同样丢失堆栈
不要在 finally 或通用工具类里“默默吞掉”异常
常见反模式:写一个 safeClose(Closeable c),里面 try-catch 了 IOException 却只打一行 warn 日志甚至不记录。问题在于:
- 调用方完全感知不到资源关闭失败,可能引发后续 NPE 或数据不一致
- warn 级别日志容易被过滤,且没包含原始异常堆栈,无法定位底层原因(比如 NFS 挂载点失效、磁盘只读)
- 更糟的是 catch
Exception后return,等于屏蔽了所有中断信号和 unchecked 异常
正确做法:要么声明抛出,要么记录 error 并 re-throw 包装后的异常(如 new RuntimeException("Failed to close DB connection", e))。
立即学习“Java免费学习笔记(深入)”;
自定义异常要重写 getCause() 和构造函数链
日志框架靠遍历 getCause() 打印完整异常链。如果自定义异常没正确委托 cause,或者用了无参构造函数再手动 set,会导致堆栈截断。
- 继承
RuntimeException或Exception后,至少提供public MyException(String msg, Throwable cause)构造器 - 务必调用
super(msg, cause),而不是super(msg)+this.cause = cause - 不要重写
getCause()返回 null 或静态值;JDK 默认实现已能穿透嵌套 cause
public class DataValidationException extends RuntimeException {
public DataValidationException(String message, Throwable cause) {
super(message, cause); // ← 关键:必须调用父类双参构造
}
}
日志里看到 “Caused by:” 缩进堆栈,前提是每一层都严格走 Throwable 构造链。漏掉一次,后面的就全断了。










