不能只用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 中避免堆栈日志被截断或乱码
默认配置下,长堆栈可能被截断,中文路径或消息可能显示为 ???。关键在 encoder 和 pattern 设置。
立即学习“Java免费学习笔记(深入)”;
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n%ex{full} < charset>UTF-8
-
%ex{full}确保输出完整堆栈(默认只打一层) - 必须显式声明
,否则 Windows 控制台或某些 ELK 接入端会乱码UTF-8 - 如果用异步 Appender(
AsyncAppender),记得设置includeCallerData="true",否则%class、%line会为空
实际项目中最容易被忽略的是:在 CompletableFuture 或 @Async 方法里,未将 MDC 上下文手动传递,导致日志丢失 traceId;还有就是把异常吞掉后只记了一行 warn 却没留任何可定位的现场信息(比如缺失 requestId、参数摘要)。这些不是配置问题,是日志意识问题。










