异常需区分cause(根本原因,用于异常链和栈追踪)与context(执行上下文,用于诊断日志);cause须通过构造函数显式设置,context应作为只读字段存于自定义异常中并结构化输出。

异常对象携带额外上下文信息,关键在于区分 cause(根本原因) 和 context(执行上下文):前者用于构建异常链、支持栈追踪回溯;后者用于辅助诊断、日志记录或调试,但不参与异常传播逻辑。
用 cause 表达嵌套异常(真正的“因为”)
当一个异常由另一个异常引发时,应通过构造函数的 cause 参数(如 Java 的 Throwable(Throwable cause),Python 的 raise NewError() from original_exc)显式关联。这会让运行时保留原始异常的完整栈,调用 getCause() 或 __cause__ 可获取它,打印异常时也会自动显示 “Caused by”。
- Java 示例:
throw new IllegalArgumentException("Invalid user ID", originalException); - Python 示例:
raise ValueError("Processing failed") from exc - 避免手动拼接消息代替 cause——那样会丢失栈、无法被工具识别、破坏异常分类逻辑
用 context 字段或属性附加诊断信息(当前“在哪、谁、什么状态”)
cause 解决“为什么发生”,context 解决“当时发生了什么”。推荐在自定义异常类中添加只读字段(如 userId, requestId, inputData),初始化时传入并存为实例属性。
- Java:定义
public final String requestId;,在构造器中赋值 - Python:在
__init__中设self.request_id = request_id - 重写
toString()或__str__时包含这些字段,方便日志直接输出上下文 - 不建议把 context 塞进 message 字符串——难解析、易重复、干扰错误模式匹配
避免混淆 cause 与 context 的常见错误
把本该是 context 的信息(如用户ID、时间戳)当作 cause 抛出,会导致异常链失真;反过来,把底层 I/O 异常仅作为 context 记录而不设为 cause,则丢失关键根因线索。
- ❌ 错误:捕获
IOException后创建新异常时,用message += "user=123"而不设 cause - ✅ 正确:以
IOException为 cause 构造新异常,并单独保存userId字段 - ❌ 错误:为记录请求 ID,包装异常时用
new RuntimeException(original, requestId)——把 context 当 cause - ✅ 正确:自定义异常类同时持有
cause(父异常)和requestId(context 字段)
配合日志与监控高效利用 context
异常对象中的 context 字段本身不自动出现在日志里,需主动提取。在全局异常处理器或日志 AOP 中,检查异常是否含自定义 context 属性,并将其作为结构化字段(如 MDC、log attributes)注入日志事件。
- 例如:Logback 的 MDC 可在捕获异常后
MDC.put("requestId", e.getRequestId()) - 监控系统(如 Sentry)支持 attach extra data,应传入异常的 context 属性而非拼接字符串
- 确保 context 字段是不可变、线程安全的值(如 String、Long),避免运行时被意外修改










