@blueking/open-telemetry 0.0.1

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 (49) hide show
  1. package/README.md +0 -0
  2. package/dist/bk-rum.global.js +11 -0
  3. package/dist/bk-rum.global.js.map +1 -0
  4. package/dist/index.js +1186 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +49 -0
  7. package/playground/App.vue +148 -0
  8. package/playground/bk-ot.ts +110 -0
  9. package/playground/components/DemoLogPanel.vue +89 -0
  10. package/playground/composables/use-demo-log.ts +39 -0
  11. package/playground/index.html +13 -0
  12. package/playground/main.ts +36 -0
  13. package/playground/mock/index.ts +43 -0
  14. package/playground/pages/BlankScreenDemo.vue +97 -0
  15. package/playground/pages/BusinessDemo.vue +74 -0
  16. package/playground/pages/CustomPluginDemo.vue +104 -0
  17. package/playground/pages/ErrorDemo.vue +75 -0
  18. package/playground/pages/Home.vue +133 -0
  19. package/playground/pages/HttpDemo.vue +120 -0
  20. package/playground/pages/LongTaskDemo.vue +66 -0
  21. package/playground/pages/WebSocketDemo.vue +100 -0
  22. package/playground/router/index.ts +92 -0
  23. package/playground/style.css +180 -0
  24. package/src/browser.ts +44 -0
  25. package/src/core/config.ts +327 -0
  26. package/src/core/plugin.ts +97 -0
  27. package/src/core/processor.ts +74 -0
  28. package/src/core/route-observer.ts +128 -0
  29. package/src/core/sampling.ts +64 -0
  30. package/src/core/sdk.ts +327 -0
  31. package/src/core/url.ts +66 -0
  32. package/src/index.ts +55 -0
  33. package/src/plugins/blank-screen.ts +170 -0
  34. package/src/plugins/common.ts +131 -0
  35. package/src/plugins/csp-violation.ts +74 -0
  36. package/src/plugins/device.ts +91 -0
  37. package/src/plugins/error.ts +211 -0
  38. package/src/plugins/http-body.ts +437 -0
  39. package/src/plugins/long-task.ts +99 -0
  40. package/src/plugins/page-view.ts +83 -0
  41. package/src/plugins/route-timing.ts +89 -0
  42. package/src/plugins/session.ts +127 -0
  43. package/src/plugins/web-vitals.ts +159 -0
  44. package/src/plugins/websocket.ts +179 -0
  45. package/tsconfig.app.json +27 -0
  46. package/tsconfig.dts.json +13 -0
  47. package/tsconfig.json +7 -0
  48. package/tsconfig.node.json +26 -0
  49. package/vite.config.ts +70 -0
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making
3
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.
4
+ *
5
+ * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
6
+ *
7
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.
8
+ *
9
+ * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):
10
+ *
11
+ * ---------------------------------------------------
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
13
+ * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
14
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
15
+ * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of
18
+ * the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
21
+ * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
+ * IN THE SOFTWARE.
25
+ */
26
+
27
+ import { type BkOTPlugin, createPlugin, initBkOT } from '../src';
28
+
29
+ // 自定义插件示例:在初始化时挂载一个无副作用的"自定义桥接"插件
30
+ // 用来演示 createPlugin 的写法以及 BkOTRuntimeContext 的用法
31
+ const customBridgePlugin: BkOTPlugin = createPlugin({
32
+ name: 'playground-bridge',
33
+ init(context) {
34
+ // 在 runtime attributes 注入示例标识,所有后续 span / log 都会带上
35
+ context.setRuntimeAttributes({
36
+ 'playground.boot_at': Date.now(),
37
+ 'playground.demo': true,
38
+ });
39
+
40
+ // 暴露一个手动触发的辅助方法到 window,方便在控制台或页面里调试
41
+ Object.assign(globalThis, {
42
+ __bkotEmitDemoSpan: (name = 'playground.manual') => {
43
+ const span = context.startSpan(name, {
44
+ 'playground.manual': true,
45
+ });
46
+ span.end();
47
+ },
48
+ });
49
+ },
50
+ });
51
+
52
+ // initBkOT 会在 autoStart=true(默认)时自动调用 start()
53
+ // 这里 endpoint 指向本机 OTLP collector,没起 collector 时打开 debug 即可在控制台看到产物
54
+ export const bkot = initBkOT({
55
+ environment: 'development',
56
+ endpoint: 'http://localhost:4318',
57
+ token: 'playground-token',
58
+ debug: true,
59
+ sampleRate: 1,
60
+ // 采集能力统一放在 rum 下;默认开启项这里显式写出,便于 playground 展示完整入口
61
+ rum: {
62
+ documentLoad: true,
63
+ fetch: true,
64
+ xhr: true,
65
+ userInteraction: true,
66
+ longTask: { threshold: 50 },
67
+ cspViolation: true,
68
+ routeTiming: true,
69
+ httpBody: {
70
+ maxBodySize: 4 * 1024,
71
+ // redact 用于隐私脱敏:演示如何拦截敏感字段
72
+ redact: ({ body, type }) => {
73
+ if (type === 'request') {
74
+ return body.replace(/("password"\s*:\s*)"[^"]*"/g, '$1"***"');
75
+ }
76
+ return body;
77
+ },
78
+ },
79
+ blankScreen: {
80
+ checkDelay: 2000,
81
+ threshold: 0.85,
82
+ },
83
+ },
84
+ // 用户自定义插件
85
+ plugins: [customBridgePlugin],
86
+ redact: {
87
+ // 全局属性脱敏:示例屏蔽身份证号格式
88
+ attributes: attributes => {
89
+ const result = { ...attributes };
90
+ for (const key of Object.keys(result)) {
91
+ const value = result[key];
92
+ if (typeof value === 'string' && /\b\d{15}|\d{18}\b/.test(value)) {
93
+ result[key] = value.replace(/\d/g, '*');
94
+ }
95
+ }
96
+ return result;
97
+ },
98
+ },
99
+ attributes: {
100
+ // 业务侧可在每个 span 上附加页面级公共属性
101
+ page: () => ({
102
+ 'rum.page.host': window.location.host,
103
+ 'rum.page.path': window.location.pathname,
104
+ 'rum.page.hash': window.location.hash,
105
+ }),
106
+ custom: () => ({
107
+ 'biz.team': 'rum-demo',
108
+ }),
109
+ },
110
+ });
@@ -0,0 +1,89 @@
1
+ <template>
2
+ <div class="log-panel">
3
+ <div class="log-panel__header">
4
+ <span class="log-panel__title">操作日志</span>
5
+ <button
6
+ class="demo-btn demo-btn--ghost log-panel__clear"
7
+ :disabled="!entries.length"
8
+ type="button"
9
+ @click="$emit('clear')"
10
+ >
11
+ 清空
12
+ </button>
13
+ </div>
14
+ <div
15
+ v-if="entries.length"
16
+ class="demo-log"
17
+ >
18
+ <p
19
+ v-for="entry in entries"
20
+ :key="entry.timestamp + entry.message"
21
+ class="demo-log__line"
22
+ :class="`demo-log__line--${entry.level}`"
23
+ >
24
+ [{{ formatTime(entry.timestamp) }}] {{ entry.message }}
25
+ </p>
26
+ </div>
27
+ <p
28
+ v-else
29
+ class="log-panel__empty"
30
+ >
31
+ 尚未触发任何事件,请点击上面的按钮开始。
32
+ </p>
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { type DemoLogEntry, formatTime } from '../composables/use-demo-log';
38
+
39
+ defineProps<{
40
+ entries: DemoLogEntry[];
41
+ }>();
42
+
43
+ defineEmits<{
44
+ clear: [];
45
+ }>();
46
+ </script>
47
+
48
+ <style scoped>
49
+ .log-panel {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: var(--space-sm);
53
+ }
54
+
55
+ .log-panel__header {
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: space-between;
59
+ }
60
+
61
+ .log-panel__title {
62
+ font-size: 13px;
63
+ font-weight: 500;
64
+ color: var(--color-text-secondary);
65
+ }
66
+
67
+ .log-panel__clear {
68
+ padding: 2px 10px;
69
+ font-size: 12px;
70
+ }
71
+
72
+ .log-panel__empty {
73
+ padding: var(--space-md);
74
+ margin: 0;
75
+ font-size: 12px;
76
+ color: var(--color-text-muted);
77
+ text-align: center;
78
+ background: var(--color-bg);
79
+ border-radius: var(--radius-sm);
80
+ }
81
+
82
+ .demo-log__line--warn {
83
+ color: #fbbf24;
84
+ }
85
+
86
+ .demo-log__line--error {
87
+ color: #f87171;
88
+ }
89
+ </style>
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making
3
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.
4
+ *
5
+ * Copyright (C) 2017-2025 Tencent. All rights reserved.
6
+ *
7
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.
8
+ */
9
+
10
+ import { shallowRef, triggerRef } from 'vue';
11
+
12
+ export interface DemoLogEntry {
13
+ level: 'error' | 'info' | 'warn';
14
+ message: string;
15
+ timestamp: number;
16
+ }
17
+
18
+ // 每个 demo 页面用一个独立日志栈,刷新页面即重置
19
+ export const useDemoLog = (max = 20) => {
20
+ const entries = shallowRef<DemoLogEntry[]>([]);
21
+
22
+ const push = (message: string, level: DemoLogEntry['level'] = 'info') => {
23
+ const next = [{ message, level, timestamp: Date.now() }, ...entries.value];
24
+ entries.value = next.slice(0, max);
25
+ triggerRef(entries);
26
+ };
27
+
28
+ const clear = () => {
29
+ entries.value = [];
30
+ triggerRef(entries);
31
+ };
32
+
33
+ return { entries, push, clear };
34
+ };
35
+
36
+ export const formatTime = (timestamp: number) => {
37
+ const date = new Date(timestamp);
38
+ return date.toLocaleTimeString('zh-CN', { hour12: false });
39
+ };
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>bkui-chat-x</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making
3
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.
4
+ *
5
+ * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
6
+ *
7
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.
8
+ *
9
+ * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):
10
+ *
11
+ * ---------------------------------------------------
12
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
13
+ * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
14
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
15
+ * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of
18
+ * the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
21
+ * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
+ * IN THE SOFTWARE.
25
+ */
26
+
27
+ import { createApp } from 'vue';
28
+
29
+ import App from './App.vue';
30
+ // SDK 必须在业务代码之前完成初始化,副作用 import 放在最顶部
31
+ import './bk-ot';
32
+ import { router } from './router';
33
+
34
+ import './style.css';
35
+
36
+ createApp(App).use(router).mount('#app');
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making
3
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.
4
+ *
5
+ * Copyright (C) 2017-2025 Tencent. All rights reserved.
6
+ *
7
+ * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.
8
+ */
9
+
10
+ // playground 演示用的 mock 数据,避免散落在组件文件里
11
+ export const httpEndpoints = {
12
+ // 公共可访问的 echo 接口,可以观察 fetch / xhr 自动埋点
13
+ echoGet: 'https://httpbin.org/get?source=bk-ot',
14
+ echoPost: 'https://httpbin.org/post',
15
+ // 故意 404 用于演示 error 上报
16
+ notFound: 'https://httpbin.org/status/404',
17
+ };
18
+
19
+ export const websocketEndpoint = 'wss://echo.websocket.events';
20
+
21
+ export const customEvents = [
22
+ {
23
+ name: 'login',
24
+ description: '用户登录成功',
25
+ payload: { 'biz.user_id': 'u_demo_1024', 'biz.login_method': 'oauth' },
26
+ },
27
+ {
28
+ name: 'order.checkout',
29
+ description: '提交订单',
30
+ payload: { 'biz.order_id': 'O20260508001', 'biz.order_amount': 199.0 },
31
+ },
32
+ {
33
+ name: 'video.play',
34
+ description: '视频播放',
35
+ payload: { 'biz.video_id': 'v_42', 'biz.video_position_ms': 0 },
36
+ },
37
+ ];
38
+
39
+ export const samplePostBody = {
40
+ username: 'demo_user',
41
+ password: 'should-be-redacted',
42
+ message: '这条 body 会被 http-body 插件捕获',
43
+ };
@@ -0,0 +1,97 @@
1
+ <template>
2
+ <section class="demo-page">
3
+ <header>
4
+ <h2 class="demo-page__title">白屏检测 · blank-screen 插件</h2>
5
+ <p class="demo-page__desc">
6
+ 加载延时(默认
7
+ 3s)后,从根节点采样像素分布判断"是否白屏"。本页面提供一个被监控容器,可手动切换其内容验证检测结果。
8
+ </p>
9
+ </header>
10
+
11
+ <div class="demo-card">
12
+ <h3 class="demo-card__title">监控目标容器</h3>
13
+ <p class="demo-card__hint">
14
+ SDK 启动时已经做了一次全局检测;如需针对子容器复测,请配合 <code>rootSelector</code> 与
15
+ <code>checkDelay</code> 配置(见首页 SDK 配置)。
16
+ </p>
17
+ <div
18
+ ref="targetRef"
19
+ class="blank-target"
20
+ :class="{ 'blank-target--empty': empty }"
21
+ >
22
+ <template v-if="!empty">
23
+ <p>当前页面区域有正常内容渲染。</p>
24
+ <p>点击下方"清空区域"模拟白屏场景,再切换路由即可触发新的页面 PV。</p>
25
+ </template>
26
+ </div>
27
+ <div class="demo-actions">
28
+ <button
29
+ class="demo-btn demo-btn--warning"
30
+ type="button"
31
+ @click="empty = true"
32
+ >
33
+ 清空区域(模拟白屏)
34
+ </button>
35
+ <button
36
+ class="demo-btn"
37
+ type="button"
38
+ @click="empty = false"
39
+ >
40
+ 恢复内容
41
+ </button>
42
+ </div>
43
+ </div>
44
+
45
+ <div class="demo-card">
46
+ <h3 class="demo-card__title">使用建议</h3>
47
+ <ul class="advice">
48
+ <li>SDK 仅在 <code>autoStart</code> 后采样一次根节点,不做后续轮询;如需多次检测需自行触发。</li>
49
+ <li>避免把 loading 占位 / 骨架屏当成空白,可在 <code>ignoreSelectors</code> 中把它们排除。</li>
50
+ <li>对单页应用,每次路由切换可视为一次潜在白屏窗口,可结合 <code>route-timing</code> 联合分析。</li>
51
+ </ul>
52
+ </div>
53
+ </section>
54
+ </template>
55
+
56
+ <script setup lang="ts">
57
+ import { shallowRef } from 'vue';
58
+
59
+ const empty = shallowRef(false);
60
+ const targetRef = shallowRef<HTMLDivElement | null>(null);
61
+ </script>
62
+
63
+ <style scoped>
64
+ .blank-target {
65
+ min-height: 160px;
66
+ padding: var(--space-md);
67
+ margin-bottom: var(--space-md);
68
+ background: var(--color-bg);
69
+ border: 1px dashed var(--color-border);
70
+ border-radius: var(--radius-sm);
71
+ }
72
+
73
+ .blank-target--empty {
74
+ background: #fff;
75
+ border-color: #fff;
76
+ border-style: solid;
77
+ }
78
+
79
+ .blank-target p {
80
+ margin: 0 0 var(--space-sm);
81
+ color: var(--color-text-secondary);
82
+ }
83
+
84
+ .advice {
85
+ padding-left: var(--space-md);
86
+ margin: 0;
87
+ color: var(--color-text-secondary);
88
+ }
89
+
90
+ .advice li + li {
91
+ margin-top: var(--space-xs);
92
+ }
93
+
94
+ .advice code {
95
+ color: var(--color-text-primary);
96
+ }
97
+ </style>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <section class="demo-page">
3
+ <header>
4
+ <h2 class="demo-page__title">自定义事件 · reportCustomEvent</h2>
5
+ <p class="demo-page__desc">
6
+ 通过 <code>BkOTInstance.reportCustomEvent({ name, attributes, error })</code> 上报自定义事件,会同时叠加
7
+ <code>attributes.page()</code> 与 <code>attributes.custom()</code>。
8
+ </p>
9
+ </header>
10
+
11
+ <div class="demo-card">
12
+ <h3 class="demo-card__title">触发自定义事件</h3>
13
+ <div class="demo-actions">
14
+ <button
15
+ v-for="event in customEvents"
16
+ :key="event.name"
17
+ class="demo-btn"
18
+ type="button"
19
+ @click="report(event)"
20
+ >
21
+ {{ event.description }}({{ event.name }})
22
+ </button>
23
+ <button
24
+ class="demo-btn demo-btn--danger"
25
+ type="button"
26
+ @click="reportFailure"
27
+ >
28
+ 上报失败事件(带 error)
29
+ </button>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="demo-card">
34
+ <h3 class="demo-card__title">代码片段</h3>
35
+ <pre class="demo-snippet">{{ snippet }}</pre>
36
+ </div>
37
+
38
+ <div class="demo-card">
39
+ <DemoLogPanel
40
+ :entries="entries"
41
+ @clear="clear"
42
+ />
43
+ </div>
44
+ </section>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { bkot } from '../bk-ot';
49
+ import DemoLogPanel from '../components/DemoLogPanel.vue';
50
+ import { useDemoLog } from '../composables/use-demo-log';
51
+ import { customEvents } from '../mock';
52
+
53
+ const { entries, push, clear } = useDemoLog();
54
+
55
+ const report = (event: (typeof customEvents)[number]) => {
56
+ bkot.reportCustomEvent({ name: event.name, attributes: event.payload });
57
+ push(`已上报 custom.${event.name}`);
58
+ };
59
+
60
+ const reportFailure = () => {
61
+ bkot.reportCustomEvent({
62
+ name: 'order.checkout',
63
+ attributes: { 'biz.order_id': 'O20260508_FAIL' },
64
+ error: new Error('库存不足'),
65
+ });
66
+ push('已上报失败的 custom.order.checkout(含 exception 事件)', 'warn');
67
+ };
68
+
69
+ const snippet = `bkot.reportCustomEvent({
70
+ name: 'order.checkout',
71
+ attributes: { 'biz.order_id': 'O20260508001', 'biz.order_amount': 199 },
72
+ error: optionalErrorObject,
73
+ });`;
74
+ </script>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <section class="demo-page">
3
+ <header>
4
+ <h2 class="demo-page__title">自定义插件 · createPlugin</h2>
5
+ <p class="demo-page__desc">
6
+ SDK 通过 <code>createPlugin</code> 暴露统一的扩展点。本 playground 在初始化时挂载了一个名为
7
+ <code>playground-bridge</code> 的演示插件:注入 runtime attributes,并在 window 上暴露
8
+ <code>__bkotEmitDemoSpan</code> 用于手动产生 span。
9
+ </p>
10
+ </header>
11
+
12
+ <div class="demo-card">
13
+ <h3 class="demo-card__title">代码片段</h3>
14
+ <pre class="demo-snippet">{{ snippet }}</pre>
15
+ </div>
16
+
17
+ <div class="demo-card">
18
+ <h3 class="demo-card__title">手动触发自定义 span</h3>
19
+ <p class="demo-card__hint">
20
+ 查看控制台 <code>[bk-ot][traces]</code> 中带 <code>playground.manual=true</code> 的 span。
21
+ </p>
22
+ <div class="demo-actions">
23
+ <input
24
+ v-model="spanName"
25
+ class="span-input"
26
+ placeholder="span 名称"
27
+ />
28
+ <button
29
+ class="demo-btn"
30
+ :disabled="!spanName.trim()"
31
+ type="button"
32
+ @click="emitSpan"
33
+ >
34
+ 发射 span
35
+ </button>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="demo-card">
40
+ <DemoLogPanel
41
+ :entries="entries"
42
+ @clear="clear"
43
+ />
44
+ </div>
45
+ </section>
46
+ </template>
47
+
48
+ <script setup lang="ts">
49
+ import { shallowRef } from 'vue';
50
+
51
+ import DemoLogPanel from '../components/DemoLogPanel.vue';
52
+ import { useDemoLog } from '../composables/use-demo-log';
53
+
54
+ type EmitDemoSpan = (name?: string) => void;
55
+
56
+ const { entries, push, clear } = useDemoLog();
57
+ const spanName = shallowRef('playground.custom_event');
58
+
59
+ const emitSpan = () => {
60
+ const fn = (globalThis as unknown as { __bkotEmitDemoSpan?: EmitDemoSpan }).__bkotEmitDemoSpan;
61
+ if (typeof fn !== 'function') {
62
+ push('__bkotEmitDemoSpan 未注册,请检查 SDK 是否成功启动', 'error');
63
+ return;
64
+ }
65
+ fn(spanName.value.trim());
66
+ push(`已通过自定义插件发射 span:${spanName.value}`);
67
+ };
68
+
69
+ const snippet = `import { initBkOT, createPlugin, type BkOTPlugin } from '@blueking/open-telemetry';
70
+
71
+ const myPlugin: BkOTPlugin = createPlugin({
72
+ name: 'playground-bridge',
73
+ init(context) {
74
+ context.setRuntimeAttributes({ 'playground.demo': true });
75
+
76
+ // 自定义业务 span / metric / log,统一走 context 提供的 API
77
+ const span = context.startSpan('boot.bridge_ready', { 'plugin.boot': true });
78
+ span.end();
79
+ },
80
+ });
81
+
82
+ initBkOT({
83
+ plugins: [myPlugin],
84
+ });`;
85
+ </script>
86
+
87
+ <style scoped>
88
+ .span-input {
89
+ flex: 1;
90
+ min-width: 200px;
91
+ padding: 8px 12px;
92
+ font-family: inherit;
93
+ font-size: 14px;
94
+ color: var(--color-text-primary);
95
+ background: var(--color-bg);
96
+ border: 1px solid var(--color-border);
97
+ border-radius: var(--radius-sm);
98
+ }
99
+
100
+ .span-input:focus {
101
+ outline: 2px solid var(--color-primary);
102
+ outline-offset: 1px;
103
+ }
104
+ </style>
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <section class="demo-page">
3
+ <header>
4
+ <h2 class="demo-page__title">错误捕获 · error 插件</h2>
5
+ <p class="demo-page__desc">
6
+ 监听 <code>window.error</code> 与 <code>unhandledrejection</code>,按错误堆栈 hash 节流(默认 60s 内同 hash 最多
7
+ 5 条)。
8
+ </p>
9
+ </header>
10
+
11
+ <div class="demo-card">
12
+ <h3 class="demo-card__title">触发错误</h3>
13
+ <p class="demo-card__hint">触发后查看控制台 <code>[bk-ot][traces]</code> 中 <code>browser.error</code> span。</p>
14
+ <div class="demo-actions">
15
+ <button
16
+ class="demo-btn demo-btn--danger"
17
+ type="button"
18
+ @click="throwSync"
19
+ >
20
+ 同步抛错
21
+ </button>
22
+ <button
23
+ class="demo-btn demo-btn--danger"
24
+ type="button"
25
+ @click="throwAsync"
26
+ >
27
+ 异步 Promise 拒绝
28
+ </button>
29
+ <button
30
+ class="demo-btn demo-btn--warning"
31
+ type="button"
32
+ @click="throwBurst"
33
+ >
34
+ 连续抛 8 次(演示节流)
35
+ </button>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="demo-card">
40
+ <DemoLogPanel
41
+ :entries="entries"
42
+ @clear="clear"
43
+ />
44
+ </div>
45
+ </section>
46
+ </template>
47
+
48
+ <script setup lang="ts">
49
+ import DemoLogPanel from '../components/DemoLogPanel.vue';
50
+ import { useDemoLog } from '../composables/use-demo-log';
51
+
52
+ const { entries, push, clear } = useDemoLog();
53
+
54
+ const throwSync = () => {
55
+ push('准备触发同步异常', 'warn');
56
+ // 用 setTimeout 让 error 真正进入 window.error 而不是被 click handler 吞掉
57
+ setTimeout(() => {
58
+ throw new Error('Demo: 同步异常 ' + Date.now());
59
+ }, 0);
60
+ };
61
+
62
+ const throwAsync = () => {
63
+ push('准备触发未捕获 Promise 拒绝', 'warn');
64
+ void Promise.reject(new Error('Demo: 未处理 promise rejection ' + Date.now()));
65
+ };
66
+
67
+ const throwBurst = () => {
68
+ push('一秒内连续抛 8 次相同错误,预期前 5 条上报、后 3 条被节流', 'warn');
69
+ for (let i = 0; i < 8; i += 1) {
70
+ setTimeout(() => {
71
+ throw new Error('Demo: 节流测试错误');
72
+ }, i * 50);
73
+ }
74
+ };
75
+ </script>