@blueking/open-telemetry 0.0.1 → 0.0.4
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/package.json +5 -3
- package/playground/App.vue +0 -148
- package/playground/bk-ot.ts +0 -110
- package/playground/components/DemoLogPanel.vue +0 -89
- package/playground/composables/use-demo-log.ts +0 -39
- package/playground/index.html +0 -13
- package/playground/main.ts +0 -36
- package/playground/mock/index.ts +0 -43
- package/playground/pages/BlankScreenDemo.vue +0 -97
- package/playground/pages/BusinessDemo.vue +0 -74
- package/playground/pages/CustomPluginDemo.vue +0 -104
- package/playground/pages/ErrorDemo.vue +0 -75
- package/playground/pages/Home.vue +0 -133
- package/playground/pages/HttpDemo.vue +0 -120
- package/playground/pages/LongTaskDemo.vue +0 -66
- package/playground/pages/WebSocketDemo.vue +0 -100
- package/playground/router/index.ts +0 -92
- package/playground/style.css +0 -180
- package/src/browser.ts +0 -44
- package/src/core/config.ts +0 -327
- package/src/core/plugin.ts +0 -97
- package/src/core/processor.ts +0 -74
- package/src/core/route-observer.ts +0 -128
- package/src/core/sampling.ts +0 -64
- package/src/core/sdk.ts +0 -327
- package/src/core/url.ts +0 -66
- package/src/index.ts +0 -55
- package/src/plugins/blank-screen.ts +0 -170
- package/src/plugins/common.ts +0 -131
- package/src/plugins/csp-violation.ts +0 -74
- package/src/plugins/device.ts +0 -91
- package/src/plugins/error.ts +0 -211
- package/src/plugins/http-body.ts +0 -437
- package/src/plugins/long-task.ts +0 -99
- package/src/plugins/page-view.ts +0 -83
- package/src/plugins/route-timing.ts +0 -89
- package/src/plugins/session.ts +0 -127
- package/src/plugins/web-vitals.ts +0 -159
- package/src/plugins/websocket.ts +0 -179
- package/tsconfig.app.json +0 -27
- package/tsconfig.dts.json +0 -13
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -70
package/src/plugins/long-task.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
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
|
-
* 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 { BkOTRumConfig } from '../core/config';
|
|
28
|
-
import type { BkOTPlugin } from '../core/plugin';
|
|
29
|
-
|
|
30
|
-
const DEFAULT_THRESHOLD_MS = 50;
|
|
31
|
-
|
|
32
|
-
interface LongTaskAttribution {
|
|
33
|
-
containerId?: string;
|
|
34
|
-
containerName?: string;
|
|
35
|
-
containerSrc?: string;
|
|
36
|
-
containerType?: string;
|
|
37
|
-
name?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface LongTaskEntry extends PerformanceEntry {
|
|
41
|
-
attribution?: LongTaskAttribution[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Long Task 监控:通过 PerformanceObserver type=longtask 捕获 >= threshold 的主线程长任务。
|
|
46
|
-
* 默认关闭,建议在性能敏感模块按需开启。
|
|
47
|
-
*/
|
|
48
|
-
export const createLongTaskPlugin = (option: BkOTRumConfig['longTask']): BkOTPlugin => {
|
|
49
|
-
let observer: PerformanceObserver | undefined;
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
name: 'long-task',
|
|
53
|
-
enabled: Boolean(option),
|
|
54
|
-
init(context) {
|
|
55
|
-
if (typeof PerformanceObserver === 'undefined') {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const entryTypes = (PerformanceObserver as unknown as { supportedEntryTypes?: string[] }).supportedEntryTypes;
|
|
59
|
-
if (!entryTypes?.includes('longtask')) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const threshold = typeof option === 'object' ? (option.threshold ?? DEFAULT_THRESHOLD_MS) : DEFAULT_THRESHOLD_MS;
|
|
64
|
-
const counter = context.meter.createCounter('browser.long_task.count', {
|
|
65
|
-
description: 'Number of long tasks observed (duration >= threshold)',
|
|
66
|
-
});
|
|
67
|
-
const durationHistogram = context.meter.createHistogram('browser.long_task.duration', {
|
|
68
|
-
unit: 'ms',
|
|
69
|
-
description: 'Duration of long tasks',
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
observer = new PerformanceObserver(list => {
|
|
74
|
-
for (const entry of list.getEntries() as LongTaskEntry[]) {
|
|
75
|
-
if (entry.duration < threshold) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
const attribution = entry.attribution?.[0];
|
|
79
|
-
const dimensions = context.applyRedact({
|
|
80
|
-
...context.config.getPageAttributes(),
|
|
81
|
-
...context.config.getMetricAttributes(),
|
|
82
|
-
'long_task.name': entry.name,
|
|
83
|
-
'long_task.attribution': attribution?.name ?? 'unknown',
|
|
84
|
-
});
|
|
85
|
-
counter.add(1, dimensions);
|
|
86
|
-
durationHistogram.record(entry.duration, dimensions);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
observer.observe({ type: 'longtask', buffered: true });
|
|
90
|
-
} catch {
|
|
91
|
-
observer = undefined;
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
shutdown() {
|
|
95
|
-
observer?.disconnect();
|
|
96
|
-
observer = undefined;
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
};
|
package/src/plugins/page-view.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
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
|
-
* 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 { subscribeRouteChange } from '../core/route-observer';
|
|
28
|
-
|
|
29
|
-
import type { BkOTRumConfig } from '../core/config';
|
|
30
|
-
import type { BkOTPlugin } from '../core/plugin';
|
|
31
|
-
|
|
32
|
-
const getCurrentUrl = () => {
|
|
33
|
-
if (typeof location === 'undefined') {
|
|
34
|
-
return '';
|
|
35
|
-
}
|
|
36
|
-
return location.href;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const createPageViewPlugin = (enabled: BkOTRumConfig['pageView']): BkOTPlugin => {
|
|
40
|
-
const teardownCallbacks: Array<() => void> = [];
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
name: 'page-view',
|
|
44
|
-
enabled: Boolean(enabled),
|
|
45
|
-
init(context) {
|
|
46
|
-
if (typeof window === 'undefined') {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 同 URL 去重:路由库常常因 query/hash 微调反复 push 同一 URL,去重避免重复上报
|
|
51
|
-
let lastUrl = '';
|
|
52
|
-
|
|
53
|
-
const emitPageView = (source: string, previousUrl = lastUrl) => {
|
|
54
|
-
const currentUrl = getCurrentUrl();
|
|
55
|
-
if (currentUrl === lastUrl) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
lastUrl = currentUrl;
|
|
59
|
-
|
|
60
|
-
const span = context.startSpan('browser.page_view', {
|
|
61
|
-
...context.config.getPageAttributes(),
|
|
62
|
-
'url.full': context.config.redactUrl(currentUrl),
|
|
63
|
-
'url.previous': context.config.redactUrl(previousUrl),
|
|
64
|
-
'document.referrer': context.config.redactUrl(document.referrer || ''),
|
|
65
|
-
'bk.rum.event.source': source,
|
|
66
|
-
});
|
|
67
|
-
span.end();
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const unsubscribe = subscribeRouteChange(event => {
|
|
71
|
-
emitPageView(event.source, event.fromUrl);
|
|
72
|
-
});
|
|
73
|
-
emitPageView('load');
|
|
74
|
-
|
|
75
|
-
teardownCallbacks.push(unsubscribe);
|
|
76
|
-
},
|
|
77
|
-
shutdown() {
|
|
78
|
-
while (teardownCallbacks.length) {
|
|
79
|
-
teardownCallbacks.pop()?.();
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
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
|
-
* 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 { subscribeRouteChange } from '../core/route-observer';
|
|
28
|
-
|
|
29
|
-
import type { BkOTRumConfig } from '../core/config';
|
|
30
|
-
import type { BkOTPlugin } from '../core/plugin';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 估算 SPA 路由切换耗时:路由变更 → 双 raf + 一个宏任务后视为"渲染完成"。
|
|
34
|
-
* 不依赖具体框架,但仅是粗粒度估算;如需精确,业务可走自定义 plugin 在框架钩子内 startSpan/end。
|
|
35
|
-
*/
|
|
36
|
-
export const createRouteTimingPlugin = (enabled: BkOTRumConfig['routeTiming']): BkOTPlugin => {
|
|
37
|
-
const teardownCallbacks: Array<() => void> = [];
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
name: 'route-timing',
|
|
41
|
-
enabled: Boolean(enabled),
|
|
42
|
-
init(context) {
|
|
43
|
-
if (typeof window === 'undefined' || typeof history === 'undefined') {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const histogram = context.meter.createHistogram('browser.route_change.duration', {
|
|
48
|
-
unit: 'ms',
|
|
49
|
-
description: 'Estimated SPA route change duration (route start to next idle)',
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const measure = (source: string, fromUrl: string, toUrl: string) => {
|
|
53
|
-
const startedAt = performance.now();
|
|
54
|
-
const finalize = () => {
|
|
55
|
-
const duration = performance.now() - startedAt;
|
|
56
|
-
const dimensions = context.applyRedact({
|
|
57
|
-
...context.config.getPageAttributes(),
|
|
58
|
-
...context.config.getMetricAttributes(),
|
|
59
|
-
'route.change.source': source,
|
|
60
|
-
});
|
|
61
|
-
histogram.record(duration, dimensions);
|
|
62
|
-
const span = context.startSpan('browser.route_change', {
|
|
63
|
-
...dimensions,
|
|
64
|
-
'url.full': context.config.redactUrl(toUrl),
|
|
65
|
-
'url.previous': context.config.redactUrl(fromUrl),
|
|
66
|
-
'route.change.duration_ms': duration,
|
|
67
|
-
});
|
|
68
|
-
span.end();
|
|
69
|
-
};
|
|
70
|
-
// 双 raf 等到下一帧渲染后,再补一个宏任务,覆盖大多数同步组件挂载场景
|
|
71
|
-
requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(finalize, 0)));
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const unsubscribe = subscribeRouteChange(event => {
|
|
75
|
-
if (event.fromUrl === event.toUrl) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
measure(event.source, event.fromUrl, event.toUrl);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
teardownCallbacks.push(unsubscribe);
|
|
82
|
-
},
|
|
83
|
-
shutdown() {
|
|
84
|
-
while (teardownCallbacks.length) {
|
|
85
|
-
teardownCallbacks.pop()?.();
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
};
|
package/src/plugins/session.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
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
|
-
* 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 { BkOTRumConfig } from '../core/config';
|
|
28
|
-
import type { BkOTPlugin } from '../core/plugin';
|
|
29
|
-
|
|
30
|
-
const DEFAULT_SESSION_STORAGE_KEY = 'bk_ot_session';
|
|
31
|
-
const DEFAULT_INACTIVITY_MS = 30 * 60 * 1000;
|
|
32
|
-
|
|
33
|
-
interface SessionRecord {
|
|
34
|
-
id: string;
|
|
35
|
-
// 会话上次活跃时间戳
|
|
36
|
-
ts: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const createId = () => {
|
|
40
|
-
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
41
|
-
return crypto.randomUUID();
|
|
42
|
-
}
|
|
43
|
-
return `session-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const readRecord = (storageKey: string): null | SessionRecord => {
|
|
47
|
-
try {
|
|
48
|
-
const raw = window.sessionStorage.getItem(storageKey) ?? window.localStorage.getItem(storageKey);
|
|
49
|
-
if (!raw) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const parsed = JSON.parse(raw) as SessionRecord;
|
|
53
|
-
if (typeof parsed?.id === 'string' && typeof parsed?.ts === 'number') {
|
|
54
|
-
return parsed;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
} catch {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const writeRecord = (storageKey: string, record: SessionRecord) => {
|
|
63
|
-
try {
|
|
64
|
-
const serialized = JSON.stringify(record);
|
|
65
|
-
window.sessionStorage.setItem(storageKey, serialized);
|
|
66
|
-
// 同时写到 localStorage,让多 tab / 关闭页后短期内复用同一个 session
|
|
67
|
-
window.localStorage.setItem(storageKey, serialized);
|
|
68
|
-
} catch {
|
|
69
|
-
/* storage 不可用时忽略,运行期仍能继续 */
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const resolveSessionId = (storageKey: string, inactivityMs: number) => {
|
|
74
|
-
const now = Date.now();
|
|
75
|
-
const existed = readRecord(storageKey);
|
|
76
|
-
if (existed && now - existed.ts < inactivityMs) {
|
|
77
|
-
const record: SessionRecord = { id: existed.id, ts: now };
|
|
78
|
-
writeRecord(storageKey, record);
|
|
79
|
-
return record.id;
|
|
80
|
-
}
|
|
81
|
-
const record: SessionRecord = { id: createId(), ts: now };
|
|
82
|
-
writeRecord(storageKey, record);
|
|
83
|
-
return record.id;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* 会话级标识:带不活跃过期时间,超过 inactivityMs 视为新会话。
|
|
88
|
-
* 与 device 插件互补:device 是设备终身标识,session 反映"用户连续访问"语义。
|
|
89
|
-
*/
|
|
90
|
-
export const createSessionPlugin = (option: BkOTRumConfig['session']): BkOTPlugin => {
|
|
91
|
-
let teardown: (() => void) | undefined;
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
name: 'session',
|
|
95
|
-
enabled: Boolean(option),
|
|
96
|
-
init(context) {
|
|
97
|
-
const storageKey =
|
|
98
|
-
typeof option === 'object' ? (option.storageKey ?? DEFAULT_SESSION_STORAGE_KEY) : DEFAULT_SESSION_STORAGE_KEY;
|
|
99
|
-
const inactivityMs =
|
|
100
|
-
typeof option === 'object' ? (option.inactivityMs ?? DEFAULT_INACTIVITY_MS) : DEFAULT_INACTIVITY_MS;
|
|
101
|
-
|
|
102
|
-
const refresh = () => {
|
|
103
|
-
const sessionId = typeof window === 'undefined' ? createId() : resolveSessionId(storageKey, inactivityMs);
|
|
104
|
-
context.setRuntimeAttributes({ 'session.id': sessionId });
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
refresh();
|
|
108
|
-
|
|
109
|
-
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 用户回到页面时检查是否过期,过期则轮换 sessionId
|
|
114
|
-
const onVisibilityChange = () => {
|
|
115
|
-
if (document.visibilityState === 'visible') {
|
|
116
|
-
refresh();
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
120
|
-
teardown = () => document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
121
|
-
},
|
|
122
|
-
shutdown() {
|
|
123
|
-
teardown?.();
|
|
124
|
-
teardown = undefined;
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
};
|
|
@@ -1,159 +0,0 @@
|
|
|
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
|
-
* 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 { BkOTRumConfig } from '../core/config';
|
|
28
|
-
import type { BkOTPlugin } from '../core/plugin';
|
|
29
|
-
import type { Attributes } from '@opentelemetry/api';
|
|
30
|
-
|
|
31
|
-
interface VitalLike {
|
|
32
|
-
attribution?: Record<string, unknown>;
|
|
33
|
-
id?: string;
|
|
34
|
-
name: string;
|
|
35
|
-
rating?: string;
|
|
36
|
-
value: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const getNavigationType = () => {
|
|
40
|
-
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;
|
|
41
|
-
|
|
42
|
-
return navigation?.type || 'unknown';
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// 仅挑选低基数、可枚举的字段进 metric attributes,避免指标维度爆炸
|
|
46
|
-
const getMetricDimensions = (metric: VitalLike): Attributes => ({
|
|
47
|
-
'rum.navigation.type': getNavigationType(),
|
|
48
|
-
'web_vital.name': metric.name,
|
|
49
|
-
'web_vital.rating': metric.rating,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// 把 attribution 中价值最高的字段抽出来(仅放进 span/log,不放进 metric)
|
|
53
|
-
const pickAttribution = (metric: VitalLike, redactUrl: (url: string) => string): Attributes => {
|
|
54
|
-
const attr = metric.attribution ?? {};
|
|
55
|
-
const result: Attributes = {};
|
|
56
|
-
|
|
57
|
-
const setIfDefined = (key: string, value: unknown, transform?: (value: string) => string) => {
|
|
58
|
-
if (value === undefined || value === null) return;
|
|
59
|
-
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
60
|
-
result[key] = typeof value === 'string' && transform ? transform(value) : value;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
switch (metric.name) {
|
|
65
|
-
case 'LCP':
|
|
66
|
-
setIfDefined('web_vital.lcp.url', attr.url, redactUrl);
|
|
67
|
-
setIfDefined('web_vital.lcp.target', attr.target);
|
|
68
|
-
setIfDefined('web_vital.lcp.element_render_delay', attr.elementRenderDelay);
|
|
69
|
-
setIfDefined('web_vital.lcp.resource_load_duration', attr.resourceLoadDuration);
|
|
70
|
-
setIfDefined('web_vital.lcp.time_to_first_byte', attr.timeToFirstByte);
|
|
71
|
-
break;
|
|
72
|
-
case 'CLS':
|
|
73
|
-
setIfDefined('web_vital.cls.largest_shift_target', attr.largestShiftTarget);
|
|
74
|
-
setIfDefined('web_vital.cls.largest_shift_value', attr.largestShiftValue);
|
|
75
|
-
setIfDefined('web_vital.cls.load_state', attr.loadState);
|
|
76
|
-
break;
|
|
77
|
-
case 'INP':
|
|
78
|
-
setIfDefined('web_vital.inp.interaction_target', attr.interactionTarget);
|
|
79
|
-
setIfDefined('web_vital.inp.interaction_type', attr.interactionType);
|
|
80
|
-
setIfDefined('web_vital.inp.input_delay', attr.inputDelay);
|
|
81
|
-
setIfDefined('web_vital.inp.processing_duration', attr.processingDuration);
|
|
82
|
-
setIfDefined('web_vital.inp.presentation_delay', attr.presentationDelay);
|
|
83
|
-
break;
|
|
84
|
-
case 'FCP':
|
|
85
|
-
setIfDefined('web_vital.fcp.time_to_first_byte', attr.timeToFirstByte);
|
|
86
|
-
setIfDefined('web_vital.fcp.load_state', attr.loadState);
|
|
87
|
-
break;
|
|
88
|
-
case 'TTFB':
|
|
89
|
-
setIfDefined('web_vital.ttfb.waiting_duration', attr.waitingDuration);
|
|
90
|
-
setIfDefined('web_vital.ttfb.dns_duration', attr.dnsDuration);
|
|
91
|
-
setIfDefined('web_vital.ttfb.connection_duration', attr.connectionDuration);
|
|
92
|
-
setIfDefined('web_vital.ttfb.request_duration', attr.requestDuration);
|
|
93
|
-
break;
|
|
94
|
-
default:
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export const createWebVitalsPlugin = (enabled: BkOTRumConfig['webVitals']): BkOTPlugin => ({
|
|
101
|
-
name: 'web-vitals',
|
|
102
|
-
enabled: Boolean(enabled),
|
|
103
|
-
async init(context) {
|
|
104
|
-
if (typeof window === 'undefined') {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const { onCLS, onFCP, onINP, onLCP, onTTFB } = await import('web-vitals/attribution');
|
|
109
|
-
const clsHistogram = context.meter.createHistogram('browser.web_vital.cls', {
|
|
110
|
-
unit: '1',
|
|
111
|
-
description: 'Cumulative Layout Shift',
|
|
112
|
-
});
|
|
113
|
-
const durationHistogram = context.meter.createHistogram('browser.web_vital.duration', {
|
|
114
|
-
unit: 'ms',
|
|
115
|
-
description: 'Web Vitals duration metrics, including FCP, INP, LCP and TTFB',
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const recordSpan = (metric: VitalLike) => {
|
|
119
|
-
const span = context.startSpan('browser.web_vital', {
|
|
120
|
-
...context.config.getPageAttributes(),
|
|
121
|
-
...context.config.getMetricAttributes(),
|
|
122
|
-
...getMetricDimensions(metric),
|
|
123
|
-
// 单条度量唯一 ID 仅放进 span,避免污染 metric 维度
|
|
124
|
-
'web_vital.id': metric.id,
|
|
125
|
-
'web_vital.value': metric.value,
|
|
126
|
-
...pickAttribution(metric, context.config.redactUrl),
|
|
127
|
-
});
|
|
128
|
-
span.end();
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const recordDurationMetric = (metric: VitalLike) => {
|
|
132
|
-
durationHistogram.record(
|
|
133
|
-
metric.value,
|
|
134
|
-
context.applyRedact({
|
|
135
|
-
...context.config.getPageAttributes(),
|
|
136
|
-
...context.config.getMetricAttributes(),
|
|
137
|
-
...getMetricDimensions(metric),
|
|
138
|
-
})
|
|
139
|
-
);
|
|
140
|
-
recordSpan(metric);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
onCLS(metric => {
|
|
144
|
-
clsHistogram.record(
|
|
145
|
-
metric.value,
|
|
146
|
-
context.applyRedact({
|
|
147
|
-
...context.config.getPageAttributes(),
|
|
148
|
-
...context.config.getMetricAttributes(),
|
|
149
|
-
...getMetricDimensions(metric),
|
|
150
|
-
})
|
|
151
|
-
);
|
|
152
|
-
recordSpan(metric);
|
|
153
|
-
});
|
|
154
|
-
onFCP(recordDurationMetric);
|
|
155
|
-
onINP(recordDurationMetric);
|
|
156
|
-
onLCP(recordDurationMetric);
|
|
157
|
-
onTTFB(recordDurationMetric);
|
|
158
|
-
},
|
|
159
|
-
});
|