3、异常事件定义:什么是 Span 异常事件、Exception 语义约定、异常事件的属性结构
在 OpenTelemetry 体系中,异常事件(Exception Event)是 Span 上承载错误信息的关键载体。它并非直接修改 Span 状态,而是通过事件机制将异常细节附加到 Span 上,从而保留完整的错误上下文。本章将深入解析异常事件的定义、语义约定及其属性结构,为后续的路由策略打下基础。
3.1 什么是 Span 异常事件
Span 异常事件是 OpenTelemetry 规范中预定义的一种特殊事件类型,用于记录应用程序中发生的异常。与简单的日志不同,异常事件具有标准化的属性结构,能够被 Collector 和后端系统统一解析和处理。
核心要点:异常事件是 Span 上的一个“时间戳 + 结构化属性”的组合,它记录了异常发生时的快照,包括异常类型、消息、堆栈轨迹等关键信息。
异常事件与 Span 状态的关系如下表所示:
| 特性 | Span 状态(Status) | 异常事件(Exception Event) |
|---|---|---|
| 目的 | 标记 Span 的成功/失败状态 | 记录异常发生的详细上下文 |
| 数据结构 | 简单枚举 + 描述字符串 | 结构化属性集合(类型、消息、堆栈等) |
| 数量限制 | 每个 Span 只能设置一次 | 一个 Span 可附加多个异常事件 |
| 典型用途 | 表示请求处理结果 | 记录重试、降级、异常链等场景 |
实战建议:在微服务调用链中,建议同时设置 Span Status 为 ERROR 并附加异常事件,这样既能快速识别失败 Span,又能获取完整的错误详情。
3.2 Exception 语义约定
OpenTelemetry 为异常事件定义了严格的语义约定(Semantic Conventions),确保不同语言 SDK 生成的异常事件具有一致的属性名称和含义。这些约定是 Collector 进行异常路由和聚合的基础。
异常事件的事件名称固定为 exception,其核心属性包括:
| 属性名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
exception.type |
string | 推荐 | 异常的全限定类名,如 java.lang.IllegalArgumentException |
exception.message |
string | 推荐 | 异常的描述信息,如 Invalid parameter: userId |
exception.stacktrace |
string | 推荐 | 完整的堆栈轨迹字符串 |
exception.escaped |
boolean | 可选 | 异常是否逃逸出当前 Span 边界(默认 false) |
注意:虽然
exception.type 和 exception.message 被标记为“推荐”,但在生产环境中强烈建议同时填充这两个属性。缺少类型或消息的异常事件,在路由和告警时可能无法被正确识别。
以下是一个符合语义约定的异常事件示例(以 JSON 格式表示):
{
"name": "exception",
"timeUnixNano": "1698765432123456789",
"attributes": [
{
"key": "exception.type",
"value": { "stringValue": "java.lang.NullPointerException" }
},
{
"key": "exception.message",
"value": { "stringValue": "Cannot invoke method on null object" }
},
{
"key": "exception.stacktrace",
"value": { "stringValue": "java.lang.NullPointerException\n\tat com.example.service.UserService.getUser(UserService.java:42)\n\tat ..." }
},
{
"key": "exception.escaped",
"value": { "boolValue": true }
}
]
}
3.3 异常事件的属性结构
异常事件的属性结构遵循 OpenTelemetry 的通用属性模型,每个属性由 键(Key)和 值(Value)组成。值可以是字符串、布尔、整数、浮点数、数组或键值列表等类型。
在 Collector 内部,异常事件被表示为 plog.LogRecord 的一部分,其属性结构可以通过以下 SVG 图直观理解:
3.4 异常事件与 Span 的关联方式
异常事件通过 Span 的事件列表(Events)与 Span 关联。每个事件包含时间戳、名称和属性集合。Collector 在处理时,可以通过以下方式识别和提取异常事件:
- 按事件名称过滤:筛选
name == "exception"的事件 - 按属性键值路由:根据
exception.type或exception.message的值进行分发 - 按时间范围聚合:利用事件的时间戳进行时序分析
关键洞察:异常事件的属性结构是 Collector 路由策略的“数据燃料”。只有充分理解属性的层级和类型,才能编写出精准的路由规则。例如,基于
exception.type 的前缀匹配可以将所有 java.lang.* 异常路由到 Java 专家团队。
下表总结了异常事件在不同场景下的典型属性组合:
| 场景 | exception.type | exception.message | exception.escaped |
|---|---|---|---|
| 业务参数校验失败 | com.example.ValidationException |
Invalid email format |
false |
| 数据库连接超时 | java.sql.SQLTimeoutException |
Connection timed out after 5000ms |
true |
| 第三方 API 调用失败 | io.grpc.StatusRuntimeException |
UNAVAILABLE: Service unavailable |
true |
| 内存溢出(捕获后记录) | java.lang.OutOfMemoryError |
Java heap space |
false |
最佳实践:在 SDK 端记录异常事件时,尽量使用标准属性名,避免自定义属性。如果确实需要附加业务上下文(如订单号、用户 ID),建议使用
exception.* 之外的命名空间,例如 business.orderId,以免与语义约定冲突。