Java里异常处理如何配合日志框架_Java日志与异常协作说明

捕获异常时应使用日志框架的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 id", e),SLF4J 会自动识别末尾的 Throwable
  • Log4j2 的 logger.error("msg", e) 行为一致;但若误写成 logger.error("msg {} ", e),同样丢失堆栈

不要在 finally 或通用工具类里“默默吞掉”异常

常见反模式:写一个 safeClose(Closeable c),里面 try-catch 了 IOException 却只打一行 warn 日志甚至不记录。问题在于:

  • 调用方完全感知不到资源关闭失败,可能引发后续 NPE 或数据不一致
  • warn 级别日志容易被过滤,且没包含原始异常堆栈,无法定位底层原因(比如 NFS 挂载点失效、磁盘只读)
  • 更糟的是 catch Exceptionreturn,等于屏蔽了所有中断信号和 unchecked 异常

正确做法:要么声明抛出,要么记录 error 并 re-throw 包装后的异常(如 new RuntimeException("Failed to close DB connection", e))。

自定义异常要重写 getCause() 和构造函数链

日志框架靠遍历 getCause() 打印完整异常链。如果自定义异常没正确委托 cause,或者用了无参构造函数再手动 set,会导致堆栈截断。

  • 继承 RuntimeExceptionException

    后,至少提供 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 构造链。漏掉一次,后面的就全断了。