在Java中如何记录异常日志_Java异常日志处理解析

不能只用e.printStackTrace()记录异常,因其输出到System.err、不可控且不支持结构化日志;应使用logger.error("msg", throwable)配合SLF4J+Logback/Log4j2,并注意MDC透传与上下文传递。

为什么不能只用 e.printStackTrace() 记录异常

它把堆栈输出到 System.err,无法控制输出位置、格式和级别,也不支持异步写入或日志轮转。生产环境里,这类日志会丢失、难检索、干扰标准输出,且无法按 ERROR 级别被监控系统捕获。

  • 调试时临时用可以,上线必须替换
  • Spring Boot 默认日志框架(Logback/Log4j2)不接管 printStackTrace() 输出
  • 微服务中跨线程或异步调用时,printStackTrace() 可能打印在错误的线程上下文中

logger.error(String, Throwable) 正确记录异常

这是 SLF4J +

Logback/Log4j2 的标准做法:消息和异常对象分开传入,框架自动将堆栈合并进日志行,保留结构化字段(如 traceId),并支持 MDC 上下文透传。

try {
    riskyOperation();
} catch (IOException e) {
    logger.error("文件读取失败,路径:{}", filePath, e); // 注意:e 是第三个参数,不是拼在字符串里
}
  • 不要写成 logger.error("文件读取失败:" + e) —— 这会丢失堆栈,只剩 toString() 结果
  • 异常对象必须作为最后一个独立参数传入,否则框架无法识别并格式化堆栈
  • 如果用了 MDC(如 MDC.put("traceId", id)),这个 traceId 会自动附加到整条日志中

哪些异常需要显式记录,哪些该抛出

核心原则:只记录你「处理了」但依然需要留痕的异常;对无法恢复的、上游应感知的异常,优先抛出(或包装后抛出),由更高层统一记录。

  • 捕获 SQLException 后重试了三次仍失败 → 记录 ERROR,并抛出自定义业务异常 DataAccessException
  • 解析用户上传的 JSON 失败 → 记录 WARN(含原始内容前100字符),返回 400,不抛出底层 JsonProcessingException
  • 空指针(NullPointerException)或数组越界(ArrayIndexOutOfBoundsException)通常代表代码缺陷 → 不要静默捕获记录,应让其崩溃并触发告警

Logback 中避免堆栈日志被截断或乱码

默认配置下,长堆栈可能被截断,中文路径或消息可能显示为 ???。关键在 encoderpattern 设置。


  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n%ex{full}
  < charset>UTF-8

  • %ex{full} 确保输出完整堆栈(默认只打一层)
  • 必须显式声明 UTF-8,否则 Windows 控制台或某些 ELK 接入端会乱码
  • 如果用异步 Appender(AsyncAppender),记得设置 includeCallerData="true",否则 %class%line 会为空

实际项目中最容易被忽略的是:在 CompletableFuture 或 @Async 方法里,未将 MDC 上下文手动传递,导致日志丢失 traceId;还有就是把异常吞掉后只记了一行 warn 却没留任何可定位的现场信息(比如缺失 requestId、参数摘要)。这些不是配置问题,是日志意识问题。