捕获异常时应使用日志框架的error(String, Throwable)方法而非e.printStackTrace(),传入异常对象以保留堆栈和cause链,避免占位符语法误用、重复记录、吞掉异常或构造函数未正确委托cause。
printStackTrace()
直接调用 e.printStackTrace() 会把堆栈输出到 System.err,无法被日志框架接管,更没法做分级、异步、归档或对接 ELK。日志框架(如 Logback、Log4j2)的 error(String msg, Throwable t) 方法才是正确入口。
e.toString() —— 否则丢失堆栈帧和 cause 链"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 id", e)
Throwable
logger.error("msg", e) 行为一致;但若误写成 logger.error("msg {} ", e),同样丢失堆栈常见反模式:写一个 safeClose(Closeable c),里面 try-catch 了 IOException 却只打一行 warn 日志甚至不记录。问题在于:
Exception 后 return,等于屏蔽了所有中断信号和 unchecked 异常正确做法:要么声明抛出,要么记录 error 并 re-throw 包装后的异常(如 new RuntimeException("Failed to close DB connection", e))。
getCause() 和构造函数链日志框架靠遍历 getCause() 打印完整异常链。如果自定义异常没正确委托 cause,或者用了无参构造函数再手动 set,会导致堆栈截断。
RuntimeException 或 Exception 后,至少提供 public MyException(String msg, Throwable cause) 构造器super(msg, cause),而不是 super(msg) + this.cause = cause
getCause() 返回 null 或静态值;JDK 默认实现已能穿透嵌套 causepublic class DataValidationException extends RuntimeException {
public DataValidationException(String message, Throwable cause) {
super(message, cause); // ← 关键:必须调用父类双参构造
}
}
日志里看到 “Caused by:” 缩进堆栈,前提是每一层都严格走 Throwable 构造链。漏掉一次,后面的就全断了。