@blueking/open-telemetry 0.0.22 → 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.
- package/README.md +258 -70
- package/dist/bk-rum.global.js +1 -1
- package/dist/bk-rum.global.js.map +1 -1
- package/dist/core/config.d.ts +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +327 -397
- package/dist/index.js.map +1 -1
- package/dist/plugins/blank-screen.d.ts +1 -1
- package/dist/plugins/common.d.ts +2 -2
- package/dist/schema/attributes.d.ts +6 -7
- package/dist/schema/span.d.ts +24 -15
- package/package.json +1 -1
- package/skills/bk-rum-otele/SKILL.md +26 -23
- package/skills/bk-rum-otele/references/actions.md +166 -0
- package/skills/bk-rum-otele/references/api.md +248 -215
- package/skills/bk-rum-otele/references/architecture.md +139 -37
- package/skills/bk-rum-otele/references/blank-screen.md +190 -0
- package/skills/bk-rum-otele/references/capabilities.md +175 -83
- package/skills/bk-rum-otele/references/configuration.md +226 -151
- package/skills/bk-rum-otele/references/data-model.md +157 -24
- package/skills/bk-rum-otele/references/errors-security.md +168 -0
- package/skills/bk-rum-otele/references/extensions.md +237 -0
- package/skills/bk-rum-otele/references/framework-integration.md +318 -75
- package/skills/bk-rum-otele/references/getting-started.md +186 -70
- package/skills/bk-rum-otele/references/network.md +258 -0
- package/skills/bk-rum-otele/references/performance.md +198 -0
- package/skills/bk-rum-otele/references/privacy.md +236 -67
- package/skills/bk-rum-otele/references/production.md +266 -0
- package/skills/bk-rum-otele/references/session-context.md +185 -0
- package/skills/bk-rum-otele/references/telemetry-reference.md +500 -51
- package/skills/bk-rum-otele/references/transport-sampling.md +321 -32
- package/skills/bk-rum-otele/references/troubleshooting.md +341 -151
- package/skills/bk-rum-otele/references/views-routing.md +211 -0
- package/skills/bk-rum-otele/references/concepts.md +0 -60
- package/skills/bk-rum-otele/references/custom-plugin.md +0 -170
- package/skills/bk-rum-otele/references/plugins-basic.md +0 -135
- package/skills/bk-rum-otele/references/plugins-error-security.md +0 -83
- package/skills/bk-rum-otele/references/plugins-experience.md +0 -124
- package/skills/bk-rum-otele/references/plugins-network.md +0 -32
- package/skills/bk-rum-otele/references/plugins.md +0 -62
|
@@ -1,31 +1,109 @@
|
|
|
1
1
|
<!-- 本文件由 packages/open-telemetry/scripts/gen-skill-references.mjs 从 wikis 自动生成,请勿手改。 -->
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# 框架与路由集成
|
|
4
4
|
|
|
5
|
-
SDK
|
|
5
|
+
SDK 不绑定 Vue、React 或具体 Router。框架集成主要解决四件事:单例初始化、View 边界、低基数路由模板、登录/卸载生命周期。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 先选择 View 模式
|
|
8
|
+
|
|
9
|
+
### 自动模式
|
|
10
|
+
|
|
11
|
+
适合大多数 History/hash SPA:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
tracking: {
|
|
15
|
+
view: {
|
|
16
|
+
tracking: 'auto',
|
|
17
|
+
getPathGroup: groupRoute,
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
SDK 监听 `history.pushState/replaceState`、`popstate` 和 `hashchange`。pathname/hash 变化创建新 View,query-only 变化不会创建。优点是接入少、能较早观测;缺点是无法直接读取 Router 的 route record 模板。
|
|
23
|
+
|
|
24
|
+
### 手动模式
|
|
25
|
+
|
|
26
|
+
适合 Router route record 是唯一可信边界、非 URL 页面状态或微前端统一路由:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
tracking: {
|
|
30
|
+
view: { tracking: 'manual' },
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
每次路由确认后调用 `startView()`。必须先 `await rum.start()` 让 Session 建立;不要同时使用自动模式,否则会重复 View。
|
|
35
|
+
|
|
36
|
+
## 通用单例模块
|
|
8
37
|
|
|
9
38
|
```ts
|
|
10
|
-
//
|
|
39
|
+
// rum.ts
|
|
11
40
|
import { BkOpenTelemetry } from '@blueking/open-telemetry';
|
|
12
41
|
|
|
13
|
-
export const
|
|
14
|
-
autoStart: false,
|
|
42
|
+
export const rum = new BkOpenTelemetry({
|
|
15
43
|
application: {
|
|
16
|
-
name: '
|
|
44
|
+
name: 'frontend-app',
|
|
17
45
|
environment: import.meta.env.MODE,
|
|
46
|
+
version: __APP_VERSION__,
|
|
18
47
|
},
|
|
19
48
|
transport: {
|
|
20
|
-
endpoint:
|
|
49
|
+
endpoint: window.__RUM_ENDPOINT__,
|
|
50
|
+
token: window.__RUM_TOKEN__,
|
|
21
51
|
},
|
|
52
|
+
tracking: {
|
|
53
|
+
blankScreen: { rootSelector: '#app' },
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
模块必须只在应用入口导入一次。组件 render/setup 中不要创建实例;热更新时也要避免重复执行构造逻辑。
|
|
59
|
+
|
|
60
|
+
## Vue 3:自动模式
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// rum.ts
|
|
64
|
+
import { BkOpenTelemetry } from '@blueking/open-telemetry';
|
|
65
|
+
|
|
66
|
+
const routeGroups = [
|
|
67
|
+
{ pattern: /^\/orders\/[^/]+$/, group: '/orders/:id' },
|
|
68
|
+
{ pattern: /^\/users\/[^/]+\/edit$/, group: '/users/:id/edit' },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
export const rum = new BkOpenTelemetry({
|
|
72
|
+
application: { name: 'vue-order-center' },
|
|
73
|
+
transport: { endpoint: window.__RUM_ENDPOINT__ },
|
|
22
74
|
tracking: {
|
|
23
75
|
view: {
|
|
24
|
-
|
|
76
|
+
getPathGroup(rawUrl) {
|
|
77
|
+
const path = new URL(rawUrl, location.href).pathname;
|
|
78
|
+
return routeGroups.find(item => item.pattern.test(path))?.group ?? path;
|
|
79
|
+
},
|
|
25
80
|
},
|
|
26
|
-
blankScreen: {
|
|
27
|
-
|
|
81
|
+
blankScreen: { rootSelector: '#app' },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Vue Router 不需要额外 `afterEach`。如果业务使用 query 代表独立页面,自动模式不会划界,应改用手动模式。
|
|
87
|
+
|
|
88
|
+
## Vue Router:手动模式
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// rum.ts
|
|
92
|
+
import { BkOpenTelemetry } from '@blueking/open-telemetry';
|
|
93
|
+
|
|
94
|
+
export const rum = new BkOpenTelemetry({
|
|
95
|
+
application: { name: 'vue-order-center' },
|
|
96
|
+
transport: { endpoint: window.__RUM_ENDPOINT__ },
|
|
97
|
+
autoStart: false,
|
|
98
|
+
tracking: {
|
|
99
|
+
view: {
|
|
100
|
+
tracking: 'manual',
|
|
101
|
+
getPathGroup(rawUrl) {
|
|
102
|
+
const path = new URL(rawUrl, location.href).pathname;
|
|
103
|
+
return path.replace(/\/orders\/[^/]+/, '/orders/:id');
|
|
104
|
+
},
|
|
28
105
|
},
|
|
106
|
+
blankScreen: { rootSelector: '#app' },
|
|
29
107
|
},
|
|
30
108
|
});
|
|
31
109
|
```
|
|
@@ -33,125 +111,290 @@ export const bkOT = new BkOpenTelemetry({
|
|
|
33
111
|
```ts
|
|
34
112
|
// main.ts
|
|
35
113
|
import { createApp } from 'vue';
|
|
36
|
-
import { bkOT } from './bk-ot';
|
|
37
114
|
import App from './App.vue';
|
|
38
115
|
import router from './router';
|
|
116
|
+
import { rum } from './rum';
|
|
117
|
+
|
|
118
|
+
const app = createApp(App).use(router);
|
|
39
119
|
|
|
40
|
-
router.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
120
|
+
const syncView = (route: typeof router.currentRoute.value) => {
|
|
121
|
+
const record = route.matched.at(-1);
|
|
122
|
+
rum.startView({
|
|
123
|
+
name: String(record?.name ?? record?.path ?? route.path),
|
|
124
|
+
url: location.href,
|
|
125
|
+
attributes: {
|
|
126
|
+
'app.route.name': String(record?.name ?? ''),
|
|
127
|
+
},
|
|
44
128
|
});
|
|
45
|
-
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
async function bootstrap() {
|
|
132
|
+
await router.isReady();
|
|
133
|
+
await rum.start();
|
|
46
134
|
|
|
47
|
-
|
|
135
|
+
syncView(router.currentRoute.value);
|
|
136
|
+
router.afterEach(to => syncView(to));
|
|
137
|
+
app.mount('#app');
|
|
138
|
+
}
|
|
48
139
|
|
|
49
|
-
|
|
140
|
+
void bootstrap().catch(error => console.error('application bootstrap failed', error));
|
|
50
141
|
```
|
|
51
142
|
|
|
52
|
-
|
|
143
|
+
这里先同步一次当前 route,再注册后续导航,避免依赖 Router 初始导航 hook 的时序。`route.fullPath` 可能包含 query,不应直接作为 View name。
|
|
53
144
|
|
|
54
|
-
|
|
145
|
+
如果 blank screen 首检发生在应用 mount 之前,确保 HTML 已有 `#app` 根元素;否则应在初始化顺序或 `checkDelay` 上校准。
|
|
55
146
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{ group: '/strategy/:id/edit', pattern: /^\/strategy\/[^/]+\/edit$/ },
|
|
60
|
-
];
|
|
147
|
+
## React Router:自动模式
|
|
148
|
+
|
|
149
|
+
如果 Router 使用浏览器 History,通常只需初始化 SDK:
|
|
61
150
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
151
|
+
```ts
|
|
152
|
+
export const rum = new BkOpenTelemetry({
|
|
153
|
+
application: { name: 'react-order-center' },
|
|
154
|
+
transport: { endpoint: window.__RUM_ENDPOINT__ },
|
|
65
155
|
tracking: {
|
|
66
156
|
view: {
|
|
67
|
-
getPathGroup
|
|
68
|
-
|
|
69
|
-
return routeGroups.find(route => route.pattern.test(pathname))?.group || pathname.replace(/\/\d+/g, '/:id');
|
|
157
|
+
getPathGroup(rawUrl) {
|
|
158
|
+
return new URL(rawUrl, location.href).pathname.replace(/\/orders\/[^/]+/, '/orders/:id');
|
|
70
159
|
},
|
|
71
160
|
},
|
|
161
|
+
blankScreen: { rootSelector: '#root' },
|
|
72
162
|
},
|
|
73
163
|
});
|
|
74
164
|
```
|
|
75
165
|
|
|
76
|
-
|
|
166
|
+
不要再用 `useLocation()` 调 `startView()`。
|
|
167
|
+
|
|
168
|
+
## React Router:手动模式
|
|
169
|
+
|
|
170
|
+
先在 render 前完成启动:
|
|
77
171
|
|
|
78
172
|
```tsx
|
|
79
|
-
|
|
80
|
-
import {
|
|
81
|
-
import {
|
|
173
|
+
// main.tsx
|
|
174
|
+
import { createRoot } from 'react-dom/client';
|
|
175
|
+
import { RouterProvider } from 'react-router-dom';
|
|
176
|
+
import { rum } from './rum';
|
|
177
|
+
import { router } from './router';
|
|
178
|
+
|
|
179
|
+
async function bootstrap() {
|
|
180
|
+
await rum.start();
|
|
181
|
+
|
|
182
|
+
createRoot(document.getElementById('root')!).render(
|
|
183
|
+
<RouterProvider router={router} />,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
void bootstrap().catch(error => console.error('application bootstrap failed', error));
|
|
188
|
+
```
|
|
82
189
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
190
|
+
```ts
|
|
191
|
+
// rum.ts
|
|
192
|
+
export const rum = new BkOpenTelemetry({
|
|
193
|
+
application: { name: 'react-order-center' },
|
|
194
|
+
transport: { endpoint: window.__RUM_ENDPOINT__ },
|
|
195
|
+
autoStart: false,
|
|
86
196
|
tracking: {
|
|
87
|
-
view: {
|
|
88
|
-
|
|
89
|
-
|
|
197
|
+
view: {
|
|
198
|
+
tracking: 'manual',
|
|
199
|
+
getPathGroup: rawUrl =>
|
|
200
|
+
new URL(rawUrl, location.href).pathname.replace(/\/orders\/[^/]+/, '/orders/:id'),
|
|
90
201
|
},
|
|
202
|
+
blankScreen: { rootSelector: '#root' },
|
|
91
203
|
},
|
|
92
204
|
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
在 Router context 内放置同步组件:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { useEffect, useRef } from 'react';
|
|
211
|
+
import { useLocation, useMatches } from 'react-router-dom';
|
|
212
|
+
import { rum } from './rum';
|
|
93
213
|
|
|
94
|
-
export
|
|
214
|
+
export function RumRouteSync() {
|
|
95
215
|
const location = useLocation();
|
|
96
216
|
const matches = useMatches();
|
|
217
|
+
const lastBoundary = useRef<string | undefined>(undefined);
|
|
218
|
+
|
|
219
|
+
const route = matches.at(-1);
|
|
220
|
+
const routeHandle = route?.handle as { rumViewName?: string } | undefined;
|
|
221
|
+
const routeTemplate = routeHandle?.rumViewName ?? route?.id ?? location.pathname;
|
|
222
|
+
const boundary = `${location.key}:${routeTemplate}`;
|
|
97
223
|
|
|
98
224
|
useEffect(() => {
|
|
99
|
-
|
|
100
|
-
|
|
225
|
+
if (lastBoundary.current === boundary) return;
|
|
226
|
+
lastBoundary.current = boundary;
|
|
227
|
+
|
|
228
|
+
rum.startView({
|
|
229
|
+
name: routeTemplate,
|
|
101
230
|
url: window.location.href,
|
|
231
|
+
attributes: {
|
|
232
|
+
'app.route.template': routeTemplate,
|
|
233
|
+
},
|
|
102
234
|
});
|
|
103
|
-
}, [
|
|
235
|
+
}, [boundary, routeTemplate]);
|
|
104
236
|
|
|
105
237
|
return null;
|
|
106
|
-
}
|
|
238
|
+
}
|
|
107
239
|
```
|
|
108
240
|
|
|
109
|
-
|
|
241
|
+
在 Data Router 的根 Layout 中挂载同步组件,确保它处于 Router Context 内,并先于页面 Outlet 渲染:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
// router.tsx
|
|
245
|
+
import { Outlet, createBrowserRouter } from 'react-router-dom';
|
|
246
|
+
import { RumRouteSync } from './RumRouteSync';
|
|
247
|
+
|
|
248
|
+
function RootLayout() {
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
<RumRouteSync />
|
|
252
|
+
<Outlet />
|
|
253
|
+
</>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const router = createBrowserRouter([
|
|
258
|
+
{
|
|
259
|
+
id: 'root',
|
|
260
|
+
element: <RootLayout />,
|
|
261
|
+
children: [
|
|
262
|
+
{ id: 'home', path: '/', lazy: () => import('./routes/home') },
|
|
263
|
+
{
|
|
264
|
+
id: 'order-detail',
|
|
265
|
+
path: '/orders/:id',
|
|
266
|
+
handle: { rumViewName: '/orders/:id' },
|
|
267
|
+
lazy: () => import('./routes/order-detail'),
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
]);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
可在 route `handle.rumViewName` 中提供稳定模板名;缺省时使用 route id。开发环境 React Strict Mode 可能重复运行 effect,示例用 ref 对同一 boundary 去重。是否将 query/search 视作新 boundary 由业务决定;上例使用 React Router 的 `location.key`,浏览器 history entry 变化会创建新 View。
|
|
275
|
+
|
|
276
|
+
## Hash Router
|
|
277
|
+
|
|
278
|
+
自动模式会观察 hash。默认 URL 清洗会移除 hash,所以 `view.url` 看不到 hash 内容,但 `getPathGroup(rawUrl)` 仍可在清洗前解析它:
|
|
110
279
|
|
|
111
280
|
```ts
|
|
112
|
-
|
|
281
|
+
getPathGroup(rawUrl) {
|
|
282
|
+
const url = new URL(rawUrl, location.href);
|
|
283
|
+
const hashPath = url.hash.replace(/^#/, '').split('?')[0] || '/';
|
|
284
|
+
return hashPath.replace(/\/orders\/[^/]+/, '/orders/:id');
|
|
285
|
+
}
|
|
286
|
+
```
|
|
113
287
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
288
|
+
纯页面锚点如 `#section-a` 可能被自动模式视为 View 变化。大量锚点页面更适合 manual,或在路由层明确过滤。
|
|
289
|
+
|
|
290
|
+
## Next.js / SSR
|
|
291
|
+
|
|
292
|
+
SDK 是浏览器 SDK,必须只在客户端构造。不要在 Node SSR 阶段创建:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
let rum: BkOpenTelemetry | undefined;
|
|
296
|
+
|
|
297
|
+
export async function getRum() {
|
|
298
|
+
if (typeof window === 'undefined') return undefined;
|
|
299
|
+
if (rum) return rum;
|
|
119
300
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
301
|
+
const { BkOpenTelemetry } = await import('@blueking/open-telemetry');
|
|
302
|
+
rum = new BkOpenTelemetry({
|
|
303
|
+
application: { name: 'next-portal' },
|
|
304
|
+
transport: { endpoint: window.__RUM_ENDPOINT__ },
|
|
305
|
+
tracking: { blankScreen: { rootSelector: '#__next' } },
|
|
123
306
|
});
|
|
124
|
-
|
|
307
|
+
return rum;
|
|
308
|
+
}
|
|
125
309
|
```
|
|
126
310
|
|
|
127
|
-
|
|
311
|
+
这只是环境隔离模式,不绑定某个 Next.js 版本。App Router/Pages Router 的手动 View 边界应使用对应 Router 的官方客户端导航 API,并确保每次确认导航只调用一次。能被 History API 正确识别时优先 auto。
|
|
128
312
|
|
|
129
|
-
|
|
313
|
+
## 登录态同步
|
|
130
314
|
|
|
131
315
|
```ts
|
|
132
|
-
|
|
133
|
-
|
|
316
|
+
export function onLogin(user: { rumId: string }) {
|
|
317
|
+
rum.setUser({ id: user.rumId });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function onLogout() {
|
|
321
|
+
rum.setUser({ id: undefined });
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
更新只影响后续 Span。应用首屏已经知道用户时,应放进 `context.user`,减少初始事件缺失用户关联。ID 需要业务预先脱敏。
|
|
326
|
+
|
|
327
|
+
## Consent 异步初始化
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
const rum = new BkOpenTelemetry({
|
|
331
|
+
application: { name: 'consent-app' },
|
|
332
|
+
transport: { endpoint: window.__RUM_ENDPOINT__ },
|
|
333
|
+
privacy: { trackingConsent: 'not-granted' },
|
|
334
|
+
});
|
|
134
335
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
transport: { endpoint: 'https://rum-collector.example.com' },
|
|
336
|
+
consentManager.onChange(async allowed => {
|
|
337
|
+
await rum.setTrackingConsent(allowed ? 'granted' : 'not-granted');
|
|
138
338
|
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
如果使用 manual View,授权后需要确认新的 Session 已创建,再按当前 Router 状态调用 `startView()`。自动模式会由 View 插件建立当前 View。
|
|
342
|
+
|
|
343
|
+
这个示例适用于“授权前可以构造、但不能采集和发送”的策略。若合规要求授权前连 UA/UA-CH、内存等 Device Resource 快照都不能读取,应把 `new BkOpenTelemetry()` 本身也延迟到首次授权后;只设置 `trackingConsent: 'not-granted'` 不会推迟构造阶段的 Device Resource 读取。
|
|
344
|
+
|
|
345
|
+
## 微前端
|
|
346
|
+
|
|
347
|
+
推荐主应用持有唯一实例:
|
|
139
348
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
349
|
+
```ts
|
|
350
|
+
// host
|
|
351
|
+
const rumBridge = {
|
|
352
|
+
reportAction: rum.reportAction,
|
|
353
|
+
reportCustomEvent: rum.reportCustomEvent,
|
|
354
|
+
setUser: rum.setUser,
|
|
355
|
+
startView: rum.startView,
|
|
356
|
+
setViewName: rum.setViewName,
|
|
146
357
|
};
|
|
358
|
+
|
|
359
|
+
window.__BK_RUM__ = rumBridge;
|
|
147
360
|
```
|
|
148
361
|
|
|
362
|
+
子应用只调用受控桥接:
|
|
363
|
+
|
|
149
364
|
```ts
|
|
150
|
-
// 子应用
|
|
151
365
|
window.__BK_RUM__?.reportCustomEvent({
|
|
152
366
|
name: 'sub_app_mounted',
|
|
153
|
-
attributes: {
|
|
154
|
-
'biz.sub_app': 'alarm',
|
|
155
|
-
},
|
|
367
|
+
attributes: { 'app.sub_app': 'alarm' },
|
|
156
368
|
});
|
|
157
369
|
```
|
|
370
|
+
|
|
371
|
+
路由边界由一个责任方维护:
|
|
372
|
+
|
|
373
|
+
- 主应用知道所有 route:主应用 manual `startView()`;
|
|
374
|
+
- 子应用拥有内部 route:通过桥接请求主应用创建 View;
|
|
375
|
+
- 不要主应用 auto、子应用 manual 同时工作。
|
|
376
|
+
|
|
377
|
+
不要向子应用暴露 `setRuntimeAttributes` 或原始 tracer,避免破坏 Session/View 语义。
|
|
378
|
+
|
|
379
|
+
## 应用卸载与重新挂载
|
|
380
|
+
|
|
381
|
+
普通 SPA 生命周期不需要 `shutdown()`。只有整个应用永久卸载、测试清理或容器销毁时调用:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
await rum.shutdown();
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
实例 shutdown 后不可 restart。微前端子应用卸载时不应关闭主应用实例。
|
|
388
|
+
|
|
389
|
+
## 框架接入验收
|
|
390
|
+
|
|
391
|
+
1. 同一页面只有一个 `BkOpenTelemetry`;
|
|
392
|
+
2. auto/manual 只选一种路由边界;
|
|
393
|
+
3. 首个 View 与每次导航各一组生命周期,不重复;
|
|
394
|
+
4. route name/path group 不含动态 ID 和 query;
|
|
395
|
+
5. blank root 对应真实挂载节点;
|
|
396
|
+
6. 登录/登出正确更新或清除 user;
|
|
397
|
+
7. SSR 不在服务端构造浏览器 SDK;
|
|
398
|
+
8. React Strict Mode/HMR 不重复初始化或创建 View;
|
|
399
|
+
9. 微前端只有一个责任方管理 View;
|
|
400
|
+
10. 页面退出前不依赖 `shutdown()`,SDK 自身会在 hidden/pagehide 尽力 flush。
|