log.error("msg", e) 比 e.getmessage() 更有用,因其完整输出异常堆栈,包含出错位置和调用链,而后者仅返回首行错误描述;线上排查依赖堆栈,缺失则难以定位根因。

log.error("msg", e) 为什么比 e.getMessage() 更有用
因为 log.error("msg", e) 会完整输出异常堆栈(stack trace),而 e.getMessage() 只返回第一行错误描述,丢掉最关键的位置信息和调用链。线上出问题时,没有堆栈等于盲人摸象。
- 常见错误现象:
log.error("处理失败: " + e.getMessage())—— 日志里只看到 “Null pointer”,但不知道是哪行代码、哪个方法、哪个参数为空 - 使用场景:所有捕获异常后需要记录日志的地方,尤其是 service 层、controller 层、定时任务等关键路径
- 性能影响:几乎无额外开销;SLF4J / Logback 对
Throwable参数做了优化,不会提前字符串拼接 - 兼容性:只要日志框架支持标准 SLF4J 接口(Logback、Log4j2、slf4j-simple),
log.error(String, Throwable)都能正常打印堆栈
什么时候不能只写 log.error("msg", e)
当 e 是包装异常(比如 RuntimeException 包着 SQLException),直接传 e 可能掩盖根因——默认只打印最外层异常的堆栈,内层原因藏在 getCause() 里。
- 常见错误现象:日志显示 “Failed to process: java.lang.RuntimeException”,但真正问题是数据库连接超时,这个信息被吞了
- 实操建议:对可能嵌套的异常,优先用
log.error("msg", e.getCause() != null ? e.getCause() : e) - 更稳妥做法:用 Apache Commons Lang 的
ExceptionUtils.getRootCause(e)或自己写个简单递归找 root cause - 注意:不要在日志里拼接
e.toString()或e.getStackTrace()—— 这会破坏日志框架对堆栈的格式化控制,也可能触发 toString 死循环
log.error("msg", e) 在不同日志框架下的实际表现
行为基本一致,但细节有差异:Logback 默认展开全部嵌套异常(caused by),Log4j2 默认只打最外层,需配置 %xEx 或启用 includeLocation 才能补全。
- Logback 用户:确认
logback.xml中的%ex或%xExpattern 存在(%xEx会递归打印所有 cause) - Log4j2 用户:如果只看到一层堆栈,检查是否用了
%m%n%throwable而不是%m%n%ex;必要时加includeLocation="true"获取行号 - Spring Boot 默认用 Logback,所以多数情况下开箱即用,但自定义 logger 实例时容易绕过默认配置
- 避免手动 new Logger:用
LoggerFactory.getLogger(YourClass.class),否则可能丢失 MDC 上下文或配置
别在 catch 块里同时写 log.error 和 throw e
重复记录同一异常,造成日志爆炸,还容易让人误判问题发生位置——你以为是这里抛的,其实是上游已经记过一次了。
立即学习“Java免费学习笔记(深入)”;
- 常见错误现象:一个请求触发 3 次 “NullPointerException”,分散在 service、dao、utils 三层,但实际只有最底层该记
- 正确做法:只在「异常边界」处记录,比如 controller 层兜底、全局异常处理器(
@ControllerAdvice)、或者明确要告警的业务断点 - 如果必须 re-throw,用
throw new BusinessException("xxx", e)包装,并在新异常构造时传入原e作为 cause,让日志仍能追溯 - 特别注意:不要写
log.error("xxx", e); throw e;—— 这是典型冗余,除非你有特殊审计需求且已评估日志量
e 可能被截断或丢失上下文,这时候光靠 log.error("msg", e) 不够,得配合 Thread.currentThread().getStackTrace() 或显式传递 MDC。










