应使用 logger.error("消息", e) 而非拼接 e.getMessage(),避免 null 导致 NPE、丢失堆栈、破坏结构化日志;需传 Throwable 实例并配合 MDC 注入上下文。

直接用 e.getMessage() 记录日志,容易丢失关键排查信息,甚至引发新异常。
getMessage() 可能返回 null 或空字符串
很多异常(如无参构造的 Exception()、NullPointerException)内部 detailMessage 字段未被初始化,调用 e.getMessage() 就返回 null。日志中拼接字符串时(如 "error: " + e.getMessage()),会触发 null.toString(),抛出新的 NullPointerException,掩盖原始异常。
- 常见于框架自动抛出的异常,或自定义异常未显式传入 message
- 线上日志里只看到 “java.lang.NullPointerException”,完全看不出原始问题在哪
丢弃完整堆栈,无法定位根因
e.getMessage() 仅含一句话描述,不含类名、行号、调用链。而生产问题排查最依赖的是堆栈轨迹——哪一行代码抛的?上游是谁调的?参数是什么?这些信息全在 Throwable 对象里,但拼接字符串或只传 getMessage() 会彻底丢弃。
- 错误写法:
logger.error("支付失败: " + e.getMessage())→ 日志里只有文字,没有堆栈 - 正确写法:
logger.error("支付失败", e)→ 日志框架自动展开完整堆栈
破坏日志结构化与可过滤性
日志框架(Logback/Log4j2)对 logger.error(String, Throwable) 有专门优化:支持异步写入、MDC 上下文透传、按异常类型过滤、ELK 中做聚合分析。而字符串拼接把异常信息“扁平化”进消息字段,丧失所有结构能力。
立即学习“Java免费学习笔记(深入)”;
- 无法在 Kibana 中按
exception.class: "NullPointerException"筛选 - MDC 中的 traceId、userId 等上下文,在拼接日志时容易遗漏或错位
- 异步日志器可能还没来得及序列化就丢弃了拼接后的字符串
替代方案:安全又完整的记录方式
不依赖 getMessage(),也不用 toString() 或 printStackTrace(),而是交由日志框架处理:
- 始终使用双参数重载:
logger.error("业务动作失败,orderId={}", orderId, e) - 带业务标识占位符,便于关联请求;第三个参数传
Throwable实例本身 - 若需额外字段(如 reqId、userId),统一通过 MDC 注入,而非硬编码进日志消息
- 降级逻辑中若要记录异常,同样遵守该规范,避免降级自身再抛异常


















