在Java里异常应该记录日志还是抛出_Java异常处理策略说明

异常处理应区分日志与抛出:仅在终止传播链时记录error日志,转换异常时不记录;必须抛出外部依赖失败类异常,编程错误应暴露;日志须带业务上下文并脱敏。

异常既不该无脑记录日志,也不该盲目抛出——关键看它是否属于「当前方法能处理的上下文」。

捕获后记录日志再抛出,通常是个坏主意

常见错误是这样写:

try {
    doSomething();
} catch (IOException e) {
    logger.error("doSomething failed", e);
    throw e; // 或 throw new RuntimeException(e)
}

这会造成日志重复:上游若也记录,同一异常被刷两次;若上游继续包装再抛,堆栈还可能被截断。更严重的是,日志里出现「已知但未处理」的异常,会掩盖真正需要人工介入的问题。

  • 只在你**终止异常传播链**时记录日志(比如在 controller 层兜底或 finally 清理资源)
  • 若只是做转换(如 SQLExceptionDataAccessException),别记日志——交给最终消费者决定
  • logger.debug(..., e) 替代 error 级别做诊断性输出,避免污染 error 日志

哪些异常必须抛出(而非吞掉或仅记录)

Java 异常分 checked 和 unchecked,但决策依据不是分类,而是语义:

  • IOExceptionSQLException 这类表示「外部依赖失败」,调用方大概率要重试、降级或提示用户,必须抛出(或封装后抛出)
  • IllegalArgumentExceptionNullPointerException 属于编程错误,应尽早暴露,通常不捕获——除非你在写 API 门面,需转为友好的 BadRequestException
  • 吞掉异常(空 catch)或只打日志不抛出,等于把故障静默化,线上排查时根本找不到源头

日志内容要带上下文,不能只记异常类名

单独一行 logger.error("File read failed", e) 几乎没用。出问题时你不知道是哪个文件、什么参数、重试了几次。

  • 记录关键业务变量:logger.error("Failed to parse config for tenantId={}, version={}", tenantId, version, e)
  • 敏感字段(密码、token)必须脱敏,别让日志成漏洞出口
  • 异步任务中,异常堆栈可能丢失线程上下文,建议在 RunnableCompletableFuture 的异常回调里补全 trace ID

最常被忽略的一点:日志

和抛出不是二选一,而是分工。记录是为了可观测,抛出是为了可控流转。混淆这两者,系统就会一边疯狂刷 error 日志,一边在某个角落默默丢数据。