@blueking/open-telemetry 0.0.22-beta.1 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +258 -70
  2. package/dist/bk-rum.global.js +2 -2
  3. package/dist/bk-rum.global.js.map +1 -1
  4. package/dist/core/config.d.ts +10 -10
  5. package/dist/core/runtime-context.d.ts +1 -1
  6. package/dist/core/sdk.d.ts +1 -1
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.js +608 -662
  9. package/dist/index.js.map +1 -1
  10. package/dist/plugins/blank-screen.d.ts +1 -1
  11. package/dist/plugins/common.d.ts +2 -2
  12. package/dist/plugins/device.d.ts +2 -2
  13. package/dist/plugins/resource.d.ts +1 -0
  14. package/dist/plugins/web-vitals.d.ts +1 -3
  15. package/dist/schema/attributes.d.ts +6 -7
  16. package/dist/schema/span.d.ts +24 -15
  17. package/package.json +1 -1
  18. package/skills/bk-rum-otele/SKILL.md +31 -30
  19. package/skills/bk-rum-otele/references/actions.md +166 -0
  20. package/skills/bk-rum-otele/references/api.md +248 -215
  21. package/skills/bk-rum-otele/references/architecture.md +139 -37
  22. package/skills/bk-rum-otele/references/blank-screen.md +190 -0
  23. package/skills/bk-rum-otele/references/capabilities.md +175 -83
  24. package/skills/bk-rum-otele/references/configuration.md +226 -151
  25. package/skills/bk-rum-otele/references/data-model.md +157 -24
  26. package/skills/bk-rum-otele/references/errors-security.md +168 -0
  27. package/skills/bk-rum-otele/references/extensions.md +237 -0
  28. package/skills/bk-rum-otele/references/framework-integration.md +318 -75
  29. package/skills/bk-rum-otele/references/getting-started.md +186 -70
  30. package/skills/bk-rum-otele/references/network.md +258 -0
  31. package/skills/bk-rum-otele/references/performance.md +198 -0
  32. package/skills/bk-rum-otele/references/privacy.md +236 -67
  33. package/skills/bk-rum-otele/references/production.md +266 -0
  34. package/skills/bk-rum-otele/references/session-context.md +185 -0
  35. package/skills/bk-rum-otele/references/telemetry-reference.md +500 -51
  36. package/skills/bk-rum-otele/references/transport-sampling.md +321 -32
  37. package/skills/bk-rum-otele/references/troubleshooting.md +341 -151
  38. package/skills/bk-rum-otele/references/views-routing.md +211 -0
  39. package/skills/bk-rum-otele/references/concepts.md +0 -60
  40. package/skills/bk-rum-otele/references/custom-plugin.md +0 -170
  41. package/skills/bk-rum-otele/references/plugins-basic.md +0 -135
  42. package/skills/bk-rum-otele/references/plugins-error-security.md +0 -86
  43. package/skills/bk-rum-otele/references/plugins-experience.md +0 -124
  44. package/skills/bk-rum-otele/references/plugins-network.md +0 -32
  45. package/skills/bk-rum-otele/references/plugins.md +0 -62
@@ -1,39 +1,172 @@
1
1
  <!-- 本文件由 packages/open-telemetry/scripts/gen-skill-references.mjs 从 wikis 自动生成,请勿手改。 -->
2
2
 
3
- # 数据模型
3
+ # RUM 数据模型
4
4
 
5
- 数据模型遵循“浏览器上报原始事件,服务端派生聚合”的边界。
5
+ SDK 的数据模型遵循一个明确边界:浏览器上报可重放、可关联的原始 RUM Span,服务端负责跨事件聚合。本文解释 OTLP 层级、字段分层、关联关系、时间语义和派生规则;逐字段定义见[遥测字段字典](./telemetry-reference.md)。
6
6
 
7
- ## 四层字段
7
+ ## OTLP 层级
8
8
 
9
- 1. OpenTelemetry Resource/Span 标准字段。
10
- 2. RUM 事件字段:`event.id/type/phase/timestamp`。
11
- 3. 关联上下文:`user.id`、`session.id`、`view.id`、`action.id`。
12
- 4. 单事件语义:`span_type`、`span_subtype`、`result`、`error_type` 和插件原始字段。
9
+ Exporter 使用 OTLP/HTTP JSON。请求主体遵循 OpenTelemetry Trace 导出结构:
13
10
 
14
- ## Span 类型
11
+ ```text
12
+ resourceSpans[]
13
+ └── resource.attributes[] 应用、环境、SDK、设备、采样率
14
+ scopeSpans[]
15
+ ├── scope.name = "bk-rum"
16
+ └── spans[]
17
+ ├── traceId / spanId / parentSpanId
18
+ ├── name / kind / startTimeUnixNano / endTimeUnixNano
19
+ ├── attributes[] RUM 上下文与事件字段
20
+ ├── events[] exception、Long Task 归因等
21
+ └── status OpenTelemetry Span Status
22
+ ```
15
23
 
16
- `document`、`route`、`session`、`resource`、`vital`、`error`、`longtask`、`action`、`websocket`、`custom`。
24
+ `/v1/rum` 是本 SDK 约定的接收路径,但 Body 仍是 OTLP Trace JSON,不是自定义扁平 JSON 数组。
17
25
 
18
- ## 浏览器直接产生
26
+ ## 五层字段
19
27
 
20
- - Session/View/Action 标识和生命周期;
21
- - URL、HTTP 状态、资源体积与 Resource Timing;
22
- - Error/CSP 原始字段和标准 exception event;
23
- - Web Vitals 值与 attribution;
24
- - WebSocket 连接生命周期;
25
- - 设备、浏览器、系统和网络上下文。
28
+ | 层级 | 示例 | 生命周期 | 负责方 |
29
+ | ------------------ | ----------------------------------------------------------- | ------------- | -------------------------- |
30
+ | Resource | `service.name`、`rum.schema.version`、`session.sample_rate` | SDK 实例固定 | 配置解析和 Device Resource |
31
+ | OpenTelemetry Span | `traceId`、`spanId`、`kind`、开始/结束时间、Status | 单个 Span | OpenTelemetry Provider |
32
+ | RUM 运行时上下文 | `session.id`、`view.id`、`action.id`、`user.id` | 随运行时变化 | `RumRuntimeContext` |
33
+ | RUM 分类与结果 | `span_type`、`outcome.type`、`outcome.reason` | 单个原始 Span | 内置插件或手动 API |
34
+ | 领域字段 | `resource.*`、`vital.*`、`error.*`、`blank_screen.*` | 单项能力决定 | 对应插件 |
26
35
 
27
- ## 浏览器本地语义
36
+ 服务端派生字段不属于以上五层,不应伪装成浏览器原始字段。
28
37
 
29
- CLS、INP、View Loading Time、Action Loading Time、挫败信号和白屏判断依赖 DOM、Performance API 或页面活动窗口,由 SDK 计算后上报。
38
+ ## Resource 属性
39
+
40
+ Resource 在构造时创建,同一 SDK 实例的全部 Span 共用。基础字段包括:
41
+
42
+ | 字段 | 来源 |
43
+ | ----------------------------- | -------------------------------------------- |
44
+ | `service.name` | `application.name` |
45
+ | `service.version` | `application.version`,未配置时不存在 |
46
+ | `deployment.environment.name` | `application.environment`,默认 `production` |
47
+ | `rum.schema.version` | 固定 `3` |
48
+ | `session.sample_rate` | 解析后的 `session.sampleRate` |
49
+ | `telemetry.sdk.name` | 固定 `@blueking/open-telemetry` |
50
+ | `telemetry.sdk.language` | 固定 `webjs` |
51
+ | `telemetry.sdk.version` | 当前 npm 包版本 |
52
+
53
+ 默认开启 Device 时,还会写入浏览器、操作系统、设备类型、内存和逻辑处理器字段。Resource 会在构造时经过一次 `privacy.redactAttributes`,之后不会随运行时状态更新。
54
+
55
+ ## 关联模型
56
+
57
+ ```text
58
+ User(业务提供,可变)
59
+ └── Session(15 分钟无活动 / 最长 4 小时)
60
+ ├── View A(初始加载)
61
+ │ ├── Action 1
62
+ │ │ ├── Request / Resource
63
+ │ │ └── Error / Long Task
64
+ │ ├── Vital
65
+ │ └── Blank Screen
66
+ └── View B(SPA、手动、BFCache 或 Session 续期)
67
+ └── ...
68
+ ```
69
+
70
+ - `user.id`:业务方传入,可在 Session 内变化。
71
+ - `session.id`:Session 插件生成,是采样和访问会话边界。
72
+ - `view.id`:每个 View 新生成,是普通 RUM Span 的必要关联字段。
73
+ - `action.id`:自动 Action 活动窗口内为数组,Action 自身 Span 上为当前 Action ID;手动 Action 生成独立 ID。
74
+
75
+ 普通 Span 没有有效 Session 和 View 时不会导出。Session 生命周期 Span允许没有 View。
76
+
77
+ ## Span 标识与时间
78
+
79
+ | 字段 | 含义 |
80
+ | ------------------------- | ---------------------------------------------------- |
81
+ | OTLP `traceId` / `spanId` | OpenTelemetry 调用链标识,也是原始 Span 的唯一标识 |
82
+ | Span start/end | 事件在 Trace 时间轴上的区间,OTLP 序列化为纳秒字符串 |
83
+
84
+ 服务端使用 `traceId + spanId` 做原始 Span 幂等和调用链还原,不再维护额外的 `event.id`。瞬时事件的开始时间就是事件发生时间;Resource 等区间 Span 的 start/end 表示真实持续区间。
85
+
86
+ PerformanceObserver 的回调时间可能晚于事件发生时间,因此 Resource、Vital 和 Long Task 会使用 Performance Entry 对应的时间创建 Span,并按该时间回查 Session/View/Action。
87
+
88
+ ## 生命周期事件
89
+
90
+ Session、View 和 WebSocket 使用瞬时生命周期 Span:
91
+
92
+ - `browser.session`:`session.phase=start|rotate|end`
93
+ - `browser.view`:`view.phase=start|update|end`
94
+ - `browser.websocket`:`websocket.phase=connect|open|error|close`
95
+
96
+ 这些 Span 的开始时间与结束时间相同;持续时间由相邻生命周期事件的时间戳在服务端推导,不通过一个数小时不结束的长 Span 表达。
97
+
98
+ View 每次发出 start/update/end 都会递增 `view.version`。服务端应以 `view.id + view.version` 幂等处理,并允许较低版本晚于较高版本到达。
99
+
100
+ ## RUM 分类字段
101
+
102
+ 每个内置事件使用统一分类:
103
+
104
+ | 字段 | 说明 |
105
+ | ---------------- | --------------------------------------------------------------------------------------------------- |
106
+ | `span_type` | 大类:`session`、`view`、`resource`、`vital`、`error`、`long_task`、`action`、`websocket`、`custom` |
107
+ | `outcome.type` | `success`、`warning`、`error`、`timeout`、`abort` |
108
+ | `outcome.reason` | 可选结果原因,例如 `network`、`network_timeout`、`http_4xx`、`http_5xx` |
109
+
110
+ `span_type` 表示 Span 类别,`outcome.type` 表示结果,两者不要混用。例如带异常的 `custom.checkout` 仍是 `span_type=custom`,但 `outcome.type=error` 且 Span Status 为 ERROR。具体子类由 `resource.type`、`vital.metric`、`error.source` 等领域字段表达。
111
+
112
+ HTTP Result 规则:
113
+
114
+ - `408`、`504` → `outcome.type=timeout`、`outcome.reason=network_timeout`
115
+ - 其他 `4xx` → `outcome.type=error`、`outcome.reason=http_4xx`
116
+ - `5xx` → `outcome.type=error`、`outcome.reason=http_5xx`
117
+ - Fetch AbortError 或 XHR `abort` → `outcome.type=abort`,不写 reason
118
+ - Fetch TimeoutError 或 XHR `timeout` → `outcome.type=timeout`、`outcome.reason=network_timeout`
119
+ - Fetch 网络异常或 XHR `error` → `outcome.type=error`、`outcome.reason=network`
120
+ - 其他状态 → `outcome.type=success`,不写 reason
121
+
122
+ ## Span Kind 与 Status
123
+
124
+ - Fetch、XHR、静态资源和 WebSocket 生命周期使用 `SpanKind.CLIENT`。
125
+ - 其他内置 RUM Span 默认使用 `SpanKind.INTERNAL`。
126
+ - HTTP 状态码大于等于 400、请求超时和网络异常会设置 Span Status ERROR;主动 abort 保持 UNSET。
127
+ - Error Span 和带 `error` 的 Custom Event 会设置 Span Status ERROR 并记录标准 exception Event。
128
+ - `outcome.type=warning` 不等于 OpenTelemetry Status ERROR;例如非 good Web Vital 和挫败 Action 使用 warning 分类。
129
+
130
+ ## 属性合并与保留字段
131
+
132
+ `context.startSpan()` 先读取运行时属性,再合并调用方显式传入的属性,因此显式属性可以覆盖同名运行时值。Processor 只补充缺失字段,不覆盖已经存在的字段。
133
+
134
+ 内置 API 的保护程度不同:
135
+
136
+ - `startView({ attributes })` 会删除所有 SDK 保留字段,不能覆盖 Session、View、分类和采样字段。
137
+ - `reportAction()` 的固定 Action/分类字段在自定义 attributes 之后写入,不能被 payload 覆盖。
138
+ - `reportCustomEvent()` 的固定分类字段和 `rum.custom.name` 在 payload attributes 之后写入,业务不能覆盖这些 SDK 保留字段。
139
+ - 直接调用 `startSpan()` 属于高级 API,调用方对字段正确性负责。
140
+
141
+ ## URL 与属性脱敏
142
+
143
+ 默认 `redactUrl` 删除 username、password、query 和 fragment,保留 scheme、host、port 和 path。它用于 SDK 明确认定为 URL 的字段。
144
+
145
+ `redactAttributes` 用于 Resource 和 SDK 创建的 Span 顶层属性;`tracking.beforeSend` 是 Span 结束后的顶层 attributes 兜底。第三方 Instrumentation 自己创建的字段不保证在创建时经过 `redactAttributes`,可在 `beforeSend` 中统一处理顶层字段。
146
+
147
+ 两者都不能读取或改写已经记录的 Span Event。自动 Error、Fetch 网络异常和带 Error 的 Custom Event 产生的 `exception` Event,需要在错误产生前避免敏感内容,或由 Collector 做二次清洗。Long Task 插件会在写入自己的 script/attribution Event 前显式执行 `redactAttributes`。
148
+
149
+ 详见[隐私、Consent 与字段治理](./privacy.md)。
150
+
151
+ ## Span Event
152
+
153
+ 部分多值或标准语义不放在 Span 顶层:
154
+
155
+ - `recordException()` 产生名为 `exception` 的标准 Span Event,包含 `exception.type`、`exception.message` 和可用时的 `exception.stacktrace`。
156
+ - LoAF 的每个脚本产生 `long_task.script` Event。
157
+ - Legacy Long Tasks 的每条 attribution 产生 `long_task.attribution` Event。
158
+
159
+ Span Event 继承所属 Span 的 Session/View/Action 关系,不需要重复写这些关联字段。
30
160
 
31
161
  ## 服务端派生
32
162
 
33
- - Session/View duration 和各类 count;
34
- - 错误率、错误 fingerprint/group;
35
- - Histogram 与 P50/P75/P95/P99;
36
- - 影响用户数、地理信息和采样修正;
37
- - 迟到/乱序关联和 View 版本合并。
163
+ SDK 当前不直接生成以下数据:
164
+
165
+ - Session/View duration 和事件数;
166
+ - 请求量、资源量、错误率、Action 数和 Long Task 数;
167
+ - Counter、Histogram、P50/P75/P95/P99;
168
+ - 错误 fingerprint/group 和风暴控制;
169
+ - 影响用户数、地理信息和采样率修正后的总体量;
170
+ - Trace 关键路径、父子层级和服务拓扑。
38
171
 
39
- 服务端应使用 `event.id` 去重,使用 `view.id + view.version` 幂等处理 View 更新。
172
+ 这些数据应从原始 Span 派生。浏览器 SDK 不应为了减少传输而在本地丢弃可用于准确聚合的原始错误或 CSP 事件。
@@ -0,0 +1,168 @@
1
+ <!-- 本文件由 packages/open-telemetry/scripts/gen-skill-references.mjs 从 wikis 自动生成,请勿手改。 -->
2
+
3
+ # 错误与 CSP 安全事件
4
+
5
+ 错误能力默认捕获 JavaScript 运行时错误、未处理 Promise Rejection 和资源加载失败。CSP Violation 默认关闭。所有事件均保留为原始 Error Span;SDK 不做客户端指纹、分组、节流或计数。
6
+
7
+ ## 默认状态
8
+
9
+ | 能力 | 配置 | 默认 |
10
+ | --- | --- | --- |
11
+ | JS / Promise / Resource Error | `tracking.error` | 开启 |
12
+ | CSP Violation | `tracking.cspViolation` | 关闭 |
13
+
14
+ 关闭 `tracking.error` 不影响 Blank Screen 和 CSP,它们是独立插件。
15
+
16
+ ## JavaScript 运行时错误
17
+
18
+ 监听冒泡阶段的 `window.error`,创建 `browser.error`:
19
+
20
+ - `span_type=error`
21
+ - `outcome.type=error`
22
+ - `error.source=window.error`
23
+ - `error.handled=false`
24
+ - `error.message`
25
+ - `code.filepath`
26
+ - `code.lineno`
27
+ - `code.column`
28
+
29
+ 跨域脚本只暴露 `Script error.` 且没有 Error/filename 时,增加 `error.cross_origin=true`。要获得完整错误和 Stack,脚本资源需要正确配置 CORS,并使用合适的 `crossorigin` 属性。
30
+
31
+ ## 未处理 Promise Rejection
32
+
33
+ 监听 `unhandledrejection`,创建 `browser.unhandledrejection`:
34
+
35
+ - `outcome.type=error`
36
+ - `error.source=unhandledrejection`
37
+ - `error.message` 从 reason 归一
38
+
39
+ Reason 是 Error 时保留其名称;其他对象尝试 JSON stringify,失败时使用 `String(value)`。
40
+
41
+ ## 资源加载错误
42
+
43
+ 在捕获阶段监听非 ErrorEvent 的 `error`,只处理 Event target 是 Element 的情况,创建 `browser.resource_error`:
44
+
45
+ - `outcome.type=error`
46
+ - `error.source=resource`
47
+ - `url.full`:从 `src`、`href` 或 `currentSrc` 读取并脱敏
48
+ - `html.tag`:目标标签名
49
+
50
+ 该事件与 Performance Resource Span 不同:它表示浏览器资源元素加载失败,可能没有 HTTP Status 或 Resource Timing。
51
+
52
+ ## 标准 Exception Event
53
+
54
+ 三类自动错误都会调用 `recordException()`,在 Span 内增加标准 `exception` Event:
55
+
56
+ | Event 字段 | 说明 |
57
+ | --- | --- |
58
+ | `exception.type` | Error name 或归一名称 |
59
+ | `exception.message` | 完整消息 |
60
+ | `exception.stacktrace` | Error 存在 Stack 时 |
61
+
62
+ Span Status 同时设为 ERROR,message 为归一错误消息。顶层 `error.message` 便于直接检索,Exception Event 保留标准异常语义。
63
+
64
+ ## 页面和 Action 关联
65
+
66
+ 错误发生时先通知 Activity Bus,再创建 Span:
67
+
68
+ - 当前 View 自动写入 `view.id`。
69
+ - 当前自动 Action 活动窗口内写入 `action.id`。
70
+ - Action 插件据此标记 `error_click`。
71
+ - Error 不延长 View Loading Time,但会更新当前 Action 的活动时间并标记 `error_click`。
72
+
73
+ ## 错误公共属性
74
+
75
+ `context.attributes.error` 只应用于自动 Error 插件:
76
+
77
+ ```ts
78
+ context: {
79
+ attributes: {
80
+ error: () => ({
81
+ 'biz.release_channel': releaseChannel,
82
+ }),
83
+ },
84
+ }
85
+ ```
86
+
87
+ 页面公共属性也会合并。Hook 抛错不会被 Error 插件单独捕获,插件事件回调可能中断本次上报,因此 Hook 应保持纯函数和低风险。
88
+
89
+ ## 手动业务错误
90
+
91
+ SDK 没有独立 `reportError()`。业务捕获的错误使用 `reportCustomEvent()`:
92
+
93
+ ```ts
94
+ try {
95
+ await submitOrder();
96
+ } catch (error) {
97
+ bkOT.reportCustomEvent({
98
+ name: 'submit_order_failed',
99
+ error: error instanceof Error ? error : new Error(String(error)),
100
+ attributes: {
101
+ 'biz.scene': 'checkout',
102
+ },
103
+ });
104
+ }
105
+ ```
106
+
107
+ 它生成 `custom.submit_order_failed`,`span_type` 仍为 `custom`,但 `outcome.type=error`、Status=ERROR,并记录 exception Event。
108
+
109
+ ## CSP Violation
110
+
111
+ 启用:
112
+
113
+ ```ts
114
+ tracking: {
115
+ cspViolation: true,
116
+ }
117
+ ```
118
+
119
+ 插件监听 `document.securitypolicyviolation`,每个浏览器事件创建 `csp.violation` Error Span:
120
+
121
+ - `outcome.type=error`
122
+ - `outcome.reason=csp`
123
+ - `error.source=csp`
124
+ - `csp.blocked_uri`
125
+ - `csp.violated_directive`
126
+ - `csp.effective_directive`
127
+ - `csp.disposition`
128
+ - `csp.source_file`
129
+ - `csp.line_number`
130
+ - `csp.column_number`
131
+ - `csp.status_code`
132
+ - `csp.original_policy`
133
+
134
+ 当前实现不做去重、指纹或窗口节流,`csp.original_policy` 每条都携带。CSP 配置错误可能产生大量 Span,应配合 Session Sampling 和服务端风暴控制。
135
+
136
+ ## CSP URL 脱敏
137
+
138
+ `sourceFile` 经过 `redactUrl`。`blockedURI` 只有以 `http:`、`https:` 或 `//` 开头时按 URL 脱敏;`inline`、`eval`、`wasm-eval`、`self` 等 CSP 关键字原样保留。
139
+
140
+ ## 数据量与服务端职责
141
+
142
+ 浏览器不丢弃重复错误,这保证服务端可以准确计算次数和影响范围。服务端应:
143
+
144
+ - 基于异常类型、消息、Stack、文件位置等生成 fingerprint;
145
+ - 建立错误组;
146
+ - 对告警和存储做风暴控制;
147
+ - 保留采样修正前后的口径;
148
+ - 使用 `session.id`、`view.id`、`action.id` 关联影响范围。
149
+
150
+ ## 隐私风险
151
+
152
+ 错误消息、Stack、文件 URL、CSP Policy 和业务自定义属性都可能包含敏感信息。默认 URL 脱敏不处理任意错误消息。`tracking.beforeSend` 只能删除或截断顶层 `error.message` 等 attributes,不能读取或修改 `recordException()` 已产生的 `exception` Span Event;异常消息和 Stack 的二次清洗必须在错误产生前或 Collector 端完成。
153
+
154
+ ## 常见问题
155
+
156
+ ### 同一错误上报两次
157
+
158
+ 可能同时触发自动 `window.error` 和业务 `reportCustomEvent()`。两条分别代表未处理浏览器错误和业务捕获事件;如果不需要双份,应只保留一种。
159
+
160
+ ### 只有 Script error.
161
+
162
+ 这是浏览器跨域错误信息保护,不是 SDK 截断。检查脚本的 CORS Header 与 `crossorigin`。
163
+
164
+ ### CSP 没有事件
165
+
166
+ 确认浏览器实际触发 `securitypolicyviolation`、配置 `tracking.cspViolation=true`、不是只依赖服务端 `report-uri/report-to`,且 Session/View 有效。
167
+
168
+ 完整字段见[遥测字段字典](./telemetry-reference.md#错误字段)和[CSP 字段](./telemetry-reference.md#csp-字段)。
@@ -0,0 +1,237 @@
1
+ <!-- 本文件由 packages/open-telemetry/scripts/gen-skill-references.mjs 从 wikis 自动生成,请勿手改。 -->
2
+
3
+ # 自定义 Plugin 与 Instrumentation
4
+
5
+ 扩展分为两类:RUM Plugin 使用 SDK Runtime Context,适合需要 Session/View/Action 语义的浏览器事件;OpenTelemetry Instrumentation 使用 SDK 私有 Trace Provider,适合复用标准生态实现。
6
+
7
+ ## 如何选择
8
+
9
+ | 场景 | 推荐 |
10
+ | --- | --- |
11
+ | 需要读取 Session/View、按事件时间回查上下文 | RUM Plugin |
12
+ | 需要通知 View/Action 页面活动 | RUM Plugin |
13
+ | 需要跟随 Consent 安装和释放 DOM/API 副作用 | 两者均可,Plugin 更直接 |
14
+ | 已有标准 OTel Instrumentation | Instrumentation |
15
+ | 需要 Metric 或 Log | 当前 SDK 不支持 |
16
+
17
+ 不要重复注册 Fetch/XHR、用户点击等与内置插件重叠的 Instrumentation,否则可能重复 Patch 和重复上报。
18
+
19
+ ## Plugin 契约
20
+
21
+ ```ts
22
+ interface BkOTPlugin {
23
+ name: string;
24
+ enabled?: boolean | ((config: ResolvedBkOTConfig) => boolean);
25
+ init(context: BkOTRuntimeContext): void | Promise<void>;
26
+ flush?(): void | Promise<void>;
27
+ shutdown?(): void | Promise<void>;
28
+ }
29
+ ```
30
+
31
+ - `name` 用于诊断,不要求全局唯一,但推荐唯一。
32
+ - `enabled=false` 或函数返回 false 时不启动。
33
+ - `init` 按插件数组顺序等待执行。
34
+ - `flush` 对所有已启动插件并行执行。
35
+ - `shutdown` 按启动顺序反向、逐个等待执行。
36
+ - 单个 init 抛错会被隔离;`debug=true` 时输出 warning。
37
+ - init 失败的插件不会执行 shutdown。
38
+
39
+ ## 注册时机
40
+
41
+ 配置式:
42
+
43
+ ```ts
44
+ new BkOpenTelemetry({
45
+ application: { name: 'demo' },
46
+ transport: { endpoint: 'https://rum.example.com' },
47
+ extensions: {
48
+ plugins: [visibilityPlugin],
49
+ instrumentations: [someInstrumentation],
50
+ },
51
+ });
52
+ ```
53
+
54
+ 链式注册必须禁用自动启动:
55
+
56
+ ```ts
57
+ const bkOT = new BkOpenTelemetry({
58
+ application: { name: 'demo' },
59
+ autoStart: false,
60
+ transport: { endpoint: 'https://rum.example.com' },
61
+ });
62
+
63
+ bkOT.use(visibilityPlugin);
64
+ bkOT.useInstrumentation(someInstrumentation);
65
+ await bkOT.start();
66
+ ```
67
+
68
+ `start()` 后调用 `use()` 或 `useInstrumentation()` 会抛错。默认 `autoStart=true` 没有可靠的动态注册窗口。
69
+
70
+ ## Runtime Context 能力
71
+
72
+ `init(context)` 可使用:
73
+
74
+ | API | 用途 |
75
+ | --- | --- |
76
+ | `context.config` | 已解析配置 |
77
+ | `context.startSpan()` | 创建带 RUM 上下文和脱敏的 Span |
78
+ | `context.tracer` | 直接使用 OTel Tracer |
79
+ | `getRuntimeAttributes()` | 当前上下文快照 |
80
+ | `getRuntimeAttributesAt(timestamp)` | 按事件时间回查上下文 |
81
+ | `setRuntimeAttributes()` | 更新高级公共属性 |
82
+ | `isSampled()` | 当前 Session 采样决定 |
83
+ | `isSessionActive()` / `isViewActive()` | 有效上下文判断 |
84
+ | `onViewChange()` | 订阅 View |
85
+ | `onSessionLifecycle()` | 订阅 Session renewed/expired |
86
+ | `onActivity()` / `notifyActivity()` | 页面活动总线 |
87
+ | `applyRedact()` | 执行属性脱敏 |
88
+ | `flush()` | 请求 SDK 刷新 |
89
+ | `registerInstrumentations()` | 插件内注册额外 Instrumentation |
90
+
91
+ ## 创建正确的自定义 Span
92
+
93
+ ```ts
94
+ import {
95
+ RUM_SPAN_TYPE,
96
+ buildRumSpanAttributes,
97
+ createPlugin,
98
+ } from '@blueking/open-telemetry';
99
+
100
+ let teardown = () => {};
101
+
102
+ const visibilityPlugin = createPlugin({
103
+ name: 'visibility-change',
104
+ init(context) {
105
+ const onVisibilityChange = () => {
106
+ if (!context.isSampled() || !context.isViewActive()) return;
107
+ const occurredAt = Date.now();
108
+ const span = context.startSpan(
109
+ 'custom.visibility_change',
110
+ {
111
+ ...buildRumSpanAttributes({
112
+ spanType: RUM_SPAN_TYPE.custom,
113
+ }),
114
+ 'rum.custom.name': 'visibility_change',
115
+ 'document.visibility_state': document.visibilityState,
116
+ },
117
+ { startTime: occurredAt },
118
+ );
119
+ span.end(occurredAt);
120
+ };
121
+
122
+ document.addEventListener('visibilitychange', onVisibilityChange);
123
+ teardown = () => document.removeEventListener('visibilitychange', onVisibilityChange);
124
+ },
125
+ shutdown() {
126
+ teardown();
127
+ },
128
+ });
129
+ ```
130
+
131
+ `context.startSpan()` 会:
132
+
133
+ - 未采样时返回 NonRecording Span;
134
+ - 合并事件时刻的 Runtime Attributes;
135
+ - 默认使用 `SpanKind.INTERNAL`;
136
+ - 执行 `redactAttributes`;
137
+ - 进入统一 Processor 和 `beforeSend`。
138
+
139
+ 自定义 Plugin 负责写入 `span_type`、`outcome.*` 和有意义的领域字段。事件标识与时间使用 OTLP Span ID 和 start/end;SDK 不会根据 Span Name 自动补齐分类字段。
140
+
141
+ ## 迟到事件与 contextTime
142
+
143
+ PerformanceObserver 等回调可能延迟:
144
+
145
+ ```ts
146
+ const occurredAt = performance.timeOrigin + entry.startTime;
147
+ const span = context.startSpan(
148
+ 'custom.performance_entry',
149
+ attributes,
150
+ {
151
+ startTime: occurredAt,
152
+ contextTime: occurredAt,
153
+ },
154
+ );
155
+ span.end(occurredAt + entry.duration);
156
+ ```
157
+
158
+ 数字类型 `startTime` 默认也作为 `contextTime`;显式传入可读性更强。非数字 OpenTelemetry `TimeInput` 不会自动用于历史查询。
159
+
160
+ ## 清理副作用
161
+
162
+ Plugin 必须释放:
163
+
164
+ - Event Listener
165
+ - Performance/Mutation Observer
166
+ - Timer / Interval
167
+ - 全局 API Patch
168
+ - 未结束 Span
169
+ - 内部队列和订阅
170
+
171
+ 推荐闭包保存 teardown,不依赖 `this` 动态修改 Plugin 对象:
172
+
173
+ ```ts
174
+ const createResizePlugin = () => {
175
+ let teardown = () => {};
176
+
177
+ return createPlugin({
178
+ name: 'resize',
179
+ init(context) {
180
+ const listener = () => context.startSpan('custom.resize').end();
181
+ window.addEventListener('resize', listener);
182
+ teardown = () => window.removeEventListener('resize', listener);
183
+ },
184
+ shutdown() {
185
+ teardown();
186
+ teardown = () => {};
187
+ },
188
+ });
189
+ };
190
+ ```
191
+
192
+ ## Consent 与重复初始化
193
+
194
+ 初始 Consent 未授权时 Plugin 不执行 init。运行期流程:
195
+
196
+ ```text
197
+ granted → init
198
+ not-granted → shutdown + flush
199
+ granted → 再次 init
200
+ ```
201
+
202
+ 因此 Plugin 必须允许同一对象经历多次 init/shutdown,不应把“一生只初始化一次”作为假设。每次 init 前应确保上次状态已经清空。
203
+
204
+ ## Instrumentation 行为
205
+
206
+ SDK 对每个 Instrumentation:
207
+
208
+ 1. 设置私有 `WebTracerProvider`。
209
+ 2. 如果 `getConfig().enabled` 为 false,则调用 `enable()`。
210
+ 3. Consent 撤销或 SDK 停止时调用 `disable()`。
211
+
212
+ Instrumentation 创建的 Span 由 `FilteringSpanProcessor.onStart()` 补充缺失的 RUM 上下文。它显式设置的同名属性优先,不会被 Processor 覆盖。
213
+
214
+ 限制:
215
+
216
+ - 当前稳定边界只有 Trace;
217
+ - SDK 不注册全局 Tracer Provider;
218
+ - 不提供 MeterProvider、LoggerProvider、`emitLog()`;
219
+ - Instrumentation 私有 attributes 不保证在创建时经过 `redactAttributes`;可用 `tracking.beforeSend` 统一处理顶层 attributes,但该 Hook 看不到 Span Events。扩展自行写入 Event 时必须在写入前完成脱敏。
220
+
221
+ ## Plugin Manager 的错误模型
222
+
223
+ Plugin init 异常不会让 `start()` 失败,因为 Manager 逐个隔离;但 SDK 建立 Plugin Manager 或全局注册过程的其他异常仍可能使 start 回滚。需要让业务感知 Plugin 初始化失败时,应在插件内部暴露状态或自行上报诊断,不要依赖 `start()` reject。
224
+
225
+ ## 发布扩展前检查
226
+
227
+ - 是否只在采样有效且有 View 时上报普通 Span;
228
+ - 是否使用稳定 Span Name,并让 `traceId + spanId` 唯一标识原始 Span;
229
+ - 是否写入正确分类和时间;
230
+ - 是否调用 URL/属性脱敏;
231
+ - 是否避免覆盖 SDK 保留字段;
232
+ - 是否在 shutdown 完整释放;
233
+ - 是否能承受 Consent 多次重启;
234
+ - 是否与内置 Patch 重复;
235
+ - 是否为高频事件设置合理的数据量边界。
236
+
237
+ 公共 Plugin 类型和工厂见[API 参考](./api.md)。