@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/core/processor.ts
DELETED
|
@@ -1,74 +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 { isBkOTEndpoint } from './url';
|
|
28
|
-
|
|
29
|
-
import type { NormalizedBkOTConfig } from './config';
|
|
30
|
-
import type { Context } from '@opentelemetry/api';
|
|
31
|
-
import type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
32
|
-
|
|
33
|
-
const SELF_TELEMETRY_URL_KEYS = ['http.url', 'url.full'];
|
|
34
|
-
|
|
35
|
-
const getSpanUrl = (span: ReadableSpan): string | undefined => {
|
|
36
|
-
for (const key of SELF_TELEMETRY_URL_KEYS) {
|
|
37
|
-
const value = span.attributes?.[key];
|
|
38
|
-
if (typeof value === 'string') {
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 包装 SpanProcessor,在 onEnd 阶段过滤指向自身上报 endpoint 的 span,
|
|
47
|
-
* 兜底防止 official fetch/xhr 拦截器对 OTel 自身请求产生回环 span。
|
|
48
|
-
*/
|
|
49
|
-
export class FilteringSpanProcessor implements SpanProcessor {
|
|
50
|
-
public constructor(
|
|
51
|
-
private readonly inner: SpanProcessor,
|
|
52
|
-
private readonly config: NormalizedBkOTConfig,
|
|
53
|
-
) {}
|
|
54
|
-
|
|
55
|
-
public forceFlush(): Promise<void> {
|
|
56
|
-
return this.inner.forceFlush();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public onEnd(span: ReadableSpan): void {
|
|
60
|
-
const url = getSpanUrl(span);
|
|
61
|
-
if (url && isBkOTEndpoint(this.config, url)) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
this.inner.onEnd(span);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public onStart(span: Span, parentContext: Context): void {
|
|
68
|
-
this.inner.onStart(span, parentContext);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
public shutdown(): Promise<void> {
|
|
72
|
-
return this.inner.shutdown();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,128 +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
|
-
export interface RouteChangeEvent {
|
|
28
|
-
fromUrl: string;
|
|
29
|
-
source: RouteChangeSource;
|
|
30
|
-
toUrl: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type RouteChangeSource = 'hashchange' | 'popstate' | 'pushState' | 'replaceState';
|
|
34
|
-
|
|
35
|
-
type RouteChangeHandler = (event: RouteChangeEvent) => void;
|
|
36
|
-
|
|
37
|
-
const subscribers = new Set<RouteChangeHandler>();
|
|
38
|
-
|
|
39
|
-
let lastUrl = '';
|
|
40
|
-
let originalPushState: typeof history.pushState | undefined;
|
|
41
|
-
let originalReplaceState: typeof history.replaceState | undefined;
|
|
42
|
-
let patched = false;
|
|
43
|
-
|
|
44
|
-
const getCurrentUrl = () => (typeof location === 'undefined' ? '' : location.href);
|
|
45
|
-
|
|
46
|
-
const emitRouteChange = (source: RouteChangeSource, fromUrl: string) => {
|
|
47
|
-
const toUrl = getCurrentUrl();
|
|
48
|
-
lastUrl = toUrl;
|
|
49
|
-
const event: RouteChangeEvent = { fromUrl, source, toUrl };
|
|
50
|
-
|
|
51
|
-
for (const subscriber of Array.from(subscribers)) {
|
|
52
|
-
subscriber(event);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const onPopState = () => {
|
|
57
|
-
emitRouteChange('popstate', lastUrl || getCurrentUrl());
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const onHashChange = () => {
|
|
61
|
-
emitRouteChange('hashchange', lastUrl || getCurrentUrl());
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const patchHistory = () => {
|
|
65
|
-
if (patched || typeof window === 'undefined' || typeof history === 'undefined') {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
patched = true;
|
|
70
|
-
lastUrl = getCurrentUrl();
|
|
71
|
-
originalPushState = history.pushState;
|
|
72
|
-
originalReplaceState = history.replaceState;
|
|
73
|
-
|
|
74
|
-
history.pushState = function patchedPushState(this: History, data: any, title: string, url?: null | string | URL) {
|
|
75
|
-
const fromUrl = lastUrl || getCurrentUrl();
|
|
76
|
-
const result = originalPushState?.apply(this, [data, title, url]);
|
|
77
|
-
emitRouteChange('pushState', fromUrl);
|
|
78
|
-
return result;
|
|
79
|
-
};
|
|
80
|
-
history.replaceState = function patchedReplaceState(
|
|
81
|
-
this: History,
|
|
82
|
-
data: any,
|
|
83
|
-
title: string,
|
|
84
|
-
url?: null | string | URL,
|
|
85
|
-
) {
|
|
86
|
-
const fromUrl = lastUrl || getCurrentUrl();
|
|
87
|
-
const result = originalReplaceState?.apply(this, [data, title, url]);
|
|
88
|
-
emitRouteChange('replaceState', fromUrl);
|
|
89
|
-
return result;
|
|
90
|
-
};
|
|
91
|
-
window.addEventListener('popstate', onPopState);
|
|
92
|
-
window.addEventListener('hashchange', onHashChange);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const restoreHistory = () => {
|
|
96
|
-
if (!patched || typeof window === 'undefined' || typeof history === 'undefined') {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (originalPushState) {
|
|
101
|
-
history.pushState = originalPushState;
|
|
102
|
-
}
|
|
103
|
-
if (originalReplaceState) {
|
|
104
|
-
history.replaceState = originalReplaceState;
|
|
105
|
-
}
|
|
106
|
-
window.removeEventListener('popstate', onPopState);
|
|
107
|
-
window.removeEventListener('hashchange', onHashChange);
|
|
108
|
-
originalPushState = undefined;
|
|
109
|
-
originalReplaceState = undefined;
|
|
110
|
-
patched = false;
|
|
111
|
-
lastUrl = '';
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
export const subscribeRouteChange = (handler: RouteChangeHandler) => {
|
|
115
|
-
if (typeof window === 'undefined' || typeof history === 'undefined') {
|
|
116
|
-
return () => {};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
patchHistory();
|
|
120
|
-
subscribers.add(handler);
|
|
121
|
-
|
|
122
|
-
return () => {
|
|
123
|
-
subscribers.delete(handler);
|
|
124
|
-
if (subscribers.size === 0) {
|
|
125
|
-
restoreHistory();
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
};
|
package/src/core/sampling.ts
DELETED
|
@@ -1,64 +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 { NormalizedBkOTConfig } from './config';
|
|
28
|
-
|
|
29
|
-
const SAMPLE_STORAGE_KEY = '__bk_ot_sampled__';
|
|
30
|
-
|
|
31
|
-
// 在 sessionStorage 不可用时降级到模块内单例缓存,避免同会话内多次结果不一致
|
|
32
|
-
const memoryCache = new Map<string, boolean>();
|
|
33
|
-
|
|
34
|
-
const readMemory = (key: string) => memoryCache.get(key);
|
|
35
|
-
const writeMemory = (key: string, value: boolean) => {
|
|
36
|
-
memoryCache.set(key, value);
|
|
37
|
-
return value;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const isBkOTSampled = ({ sampleRate }: Pick<NormalizedBkOTConfig, 'sampleRate'>) => {
|
|
41
|
-
if (sampleRate <= 0) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if (sampleRate >= 1) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const cached = sessionStorage.getItem(SAMPLE_STORAGE_KEY);
|
|
50
|
-
if (cached) {
|
|
51
|
-
return cached === '1';
|
|
52
|
-
}
|
|
53
|
-
const sampled = Math.random() < sampleRate;
|
|
54
|
-
sessionStorage.setItem(SAMPLE_STORAGE_KEY, sampled ? '1' : '0');
|
|
55
|
-
writeMemory(SAMPLE_STORAGE_KEY, sampled);
|
|
56
|
-
return sampled;
|
|
57
|
-
} catch {
|
|
58
|
-
const cached = readMemory(SAMPLE_STORAGE_KEY);
|
|
59
|
-
if (typeof cached === 'boolean') {
|
|
60
|
-
return cached;
|
|
61
|
-
}
|
|
62
|
-
return writeMemory(SAMPLE_STORAGE_KEY, Math.random() < sampleRate);
|
|
63
|
-
}
|
|
64
|
-
};
|
package/src/core/sdk.ts
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
/*
|
|
3
|
-
* Tencent is pleased to support the open source community by making
|
|
4
|
-
* 蓝鲸智云PaaS平台 (BlueKing PaaS) available.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (C) 2017-2025 Tencent. All rights reserved.
|
|
7
|
-
*
|
|
8
|
-
* 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.
|
|
9
|
-
*
|
|
10
|
-
* License for 蓝鲸智云PaaS平台 (BlueKing PaaS):
|
|
11
|
-
*
|
|
12
|
-
* ---------------------------------------------------
|
|
13
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
14
|
-
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
|
15
|
-
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
|
16
|
-
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
17
|
-
*
|
|
18
|
-
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
|
19
|
-
* the Software.
|
|
20
|
-
*
|
|
21
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
22
|
-
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
24
|
-
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
25
|
-
* IN THE SOFTWARE.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import {
|
|
29
|
-
type Attributes,
|
|
30
|
-
type Span,
|
|
31
|
-
context,
|
|
32
|
-
metrics,
|
|
33
|
-
propagation,
|
|
34
|
-
SpanKind,
|
|
35
|
-
SpanStatusCode,
|
|
36
|
-
trace,
|
|
37
|
-
} from '@opentelemetry/api';
|
|
38
|
-
import { logs } from '@opentelemetry/api-logs';
|
|
39
|
-
import { ZoneContextManager } from '@opentelemetry/context-zone';
|
|
40
|
-
import { W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
41
|
-
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
|
|
42
|
-
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
43
|
-
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
44
|
-
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
45
|
-
import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';
|
|
46
|
-
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
47
|
-
import { BatchSpanProcessor, ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
|
|
48
|
-
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
|
|
49
|
-
|
|
50
|
-
import { createBlankScreenPlugin } from '../plugins/blank-screen';
|
|
51
|
-
import { createCommonInstrumentationsPlugin } from '../plugins/common';
|
|
52
|
-
import { createCspViolationPlugin } from '../plugins/csp-violation';
|
|
53
|
-
import { createDevicePlugin } from '../plugins/device';
|
|
54
|
-
import { createErrorPlugin } from '../plugins/error';
|
|
55
|
-
import { createHttpBodyPlugin } from '../plugins/http-body';
|
|
56
|
-
import { createLongTaskPlugin } from '../plugins/long-task';
|
|
57
|
-
import { createPageViewPlugin } from '../plugins/page-view';
|
|
58
|
-
import { createRouteTimingPlugin } from '../plugins/route-timing';
|
|
59
|
-
import { createSessionPlugin } from '../plugins/session';
|
|
60
|
-
import { createWebVitalsPlugin } from '../plugins/web-vitals';
|
|
61
|
-
import { createWebSocketPlugin } from '../plugins/websocket';
|
|
62
|
-
import { type BkOTConfig, type BkOTCustomEventPayload, type NormalizedBkOTConfig, normalizeConfig } from './config';
|
|
63
|
-
import { type BkOTPlugin, type BkOTRuntimeContext, PluginManager } from './plugin';
|
|
64
|
-
import { FilteringSpanProcessor } from './processor';
|
|
65
|
-
import { isBkOTSampled } from './sampling';
|
|
66
|
-
|
|
67
|
-
import type { LogRecord } from '@opentelemetry/api-logs';
|
|
68
|
-
|
|
69
|
-
export interface BkOTInstance extends BkOTRuntimeContext {
|
|
70
|
-
flush: () => Promise<void>;
|
|
71
|
-
reportCustomEvent: (payload: BkOTCustomEventPayload) => void;
|
|
72
|
-
shutdown: () => Promise<void>;
|
|
73
|
-
start: () => Promise<void>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const createExporterOptions = (config: NormalizedBkOTConfig['traces']) => ({
|
|
77
|
-
url: config.endpoint,
|
|
78
|
-
headers: config.headers,
|
|
79
|
-
concurrencyLimit: config.concurrencyLimit,
|
|
80
|
-
timeoutMillis: config.timeoutMillis,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
type DebugSignal = 'logs' | 'metrics' | 'traces';
|
|
84
|
-
|
|
85
|
-
const BK_OT_INSTRUMENTATION_NAME = 'bk-rum';
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 用 Proxy 包装一层 debug 旁路,避免直接 mutate 第三方 exporter 实例(其内部可能含 #private fields)。
|
|
89
|
-
*/
|
|
90
|
-
const wrapWithDebug = <TExporter extends { export: (...args: any[]) => any }>(
|
|
91
|
-
exporter: TExporter,
|
|
92
|
-
signal: DebugSignal,
|
|
93
|
-
enabled: boolean,
|
|
94
|
-
): TExporter => {
|
|
95
|
-
if (!enabled) {
|
|
96
|
-
return exporter;
|
|
97
|
-
}
|
|
98
|
-
return new Proxy(exporter, {
|
|
99
|
-
get(target, prop, receiver) {
|
|
100
|
-
if (prop === 'export') {
|
|
101
|
-
return (records: unknown, callback: unknown) => {
|
|
102
|
-
globalThis.console?.log(`[bk-ot][${signal}]`, records);
|
|
103
|
-
return (target as { export: (...args: unknown[]) => unknown }).export(records, callback);
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
const value = Reflect.get(target, prop, receiver);
|
|
107
|
-
return typeof value === 'function' ? value.bind(target) : value;
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// 负责给原生 fetch/xhr 注入 W3C traceparent 头;仅 SDK 启用时装载,避免 disabled 状态仍 patch 全局 API
|
|
113
|
-
const getCorePlugins = (config: NormalizedBkOTConfig): BkOTPlugin[] => [
|
|
114
|
-
createCommonInstrumentationsPlugin(config.instrumentations),
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
// 仅在采样命中时启用的"会主动产生数据"的 RUM 插件
|
|
118
|
-
const getRumPlugins = (config: NormalizedBkOTConfig): BkOTPlugin[] => [
|
|
119
|
-
createDevicePlugin(config.rum.device),
|
|
120
|
-
createHttpBodyPlugin(config.rum.httpBody),
|
|
121
|
-
createSessionPlugin(config.rum.session),
|
|
122
|
-
createPageViewPlugin(config.rum.pageView),
|
|
123
|
-
createErrorPlugin(config.rum.error),
|
|
124
|
-
createWebVitalsPlugin(config.rum.webVitals),
|
|
125
|
-
createBlankScreenPlugin(config.rum.blankScreen),
|
|
126
|
-
createWebSocketPlugin(config.rum.websocket),
|
|
127
|
-
createLongTaskPlugin(config.rum.longTask),
|
|
128
|
-
createCspViolationPlugin(config.rum.cspViolation),
|
|
129
|
-
createRouteTimingPlugin(config.rum.routeTiming),
|
|
130
|
-
];
|
|
131
|
-
|
|
132
|
-
type SdkLifecyclePhase = 'idle' | 'started' | 'stopped';
|
|
133
|
-
|
|
134
|
-
export const createBkOT = (inputConfig: BkOTConfig): BkOTInstance => {
|
|
135
|
-
const config = normalizeConfig(inputConfig);
|
|
136
|
-
const sampled = config.enabled && isBkOTSampled(config);
|
|
137
|
-
const resource = resourceFromAttributes(config.resourceAttributes);
|
|
138
|
-
const spanExporter = wrapWithDebug(
|
|
139
|
-
new OTLPTraceExporter(createExporterOptions(config.traces)),
|
|
140
|
-
'traces',
|
|
141
|
-
config.debug,
|
|
142
|
-
);
|
|
143
|
-
const metricExporter = wrapWithDebug(
|
|
144
|
-
new OTLPMetricExporter(createExporterOptions(config.metrics)),
|
|
145
|
-
'metrics',
|
|
146
|
-
config.debug,
|
|
147
|
-
);
|
|
148
|
-
const logExporter = wrapWithDebug(new OTLPLogExporter(createExporterOptions(config.logs)), 'logs', config.debug);
|
|
149
|
-
const batchSpanProcessor = new BatchSpanProcessor(spanExporter, config.spanBatch);
|
|
150
|
-
const spanProcessor = new FilteringSpanProcessor(batchSpanProcessor, config);
|
|
151
|
-
const metricReader = new PeriodicExportingMetricReader({
|
|
152
|
-
exporter: metricExporter,
|
|
153
|
-
exportIntervalMillis: config.metricIntervalMillis,
|
|
154
|
-
});
|
|
155
|
-
const logProcessor = new BatchLogRecordProcessor(logExporter);
|
|
156
|
-
const tracerProvider = new WebTracerProvider({
|
|
157
|
-
resource,
|
|
158
|
-
sampler: new ParentBasedSampler({
|
|
159
|
-
root: new TraceIdRatioBasedSampler(sampled ? 1 : 0),
|
|
160
|
-
}),
|
|
161
|
-
spanProcessors: [spanProcessor],
|
|
162
|
-
});
|
|
163
|
-
const meterProvider = new MeterProvider({
|
|
164
|
-
resource,
|
|
165
|
-
readers: [metricReader],
|
|
166
|
-
});
|
|
167
|
-
const loggerProvider = new LoggerProvider({
|
|
168
|
-
resource,
|
|
169
|
-
processors: [logProcessor],
|
|
170
|
-
});
|
|
171
|
-
const tracer = tracerProvider.getTracer(BK_OT_INSTRUMENTATION_NAME);
|
|
172
|
-
const meter = meterProvider.getMeter(BK_OT_INSTRUMENTATION_NAME);
|
|
173
|
-
const logger = loggerProvider.getLogger(BK_OT_INSTRUMENTATION_NAME);
|
|
174
|
-
const runtimeAttributes: Attributes = {
|
|
175
|
-
'bk.rum.sampled': sampled,
|
|
176
|
-
};
|
|
177
|
-
// 核心插件在 SDK 启用时装载;RUM 数据上报类插件仅在采样命中时装载
|
|
178
|
-
const pluginManager = new PluginManager([
|
|
179
|
-
...(config.enabled ? getCorePlugins(config) : []),
|
|
180
|
-
...(sampled ? [...getRumPlugins(config), ...config.plugins] : []),
|
|
181
|
-
]);
|
|
182
|
-
const teardownCallbacks: Array<() => void> = [];
|
|
183
|
-
let phase: SdkLifecyclePhase = 'idle';
|
|
184
|
-
|
|
185
|
-
const getRuntimeAttributes = () => ({ ...runtimeAttributes });
|
|
186
|
-
const setRuntimeAttributes = (attributes: Attributes) => {
|
|
187
|
-
Object.assign(runtimeAttributes, attributes);
|
|
188
|
-
};
|
|
189
|
-
const applyRedact = (attributes: Attributes) => config.redactAttributes(attributes);
|
|
190
|
-
const startSpan = (name: string, attributes: Attributes = {}): Span =>
|
|
191
|
-
tracer.startSpan(name, {
|
|
192
|
-
kind: SpanKind.INTERNAL,
|
|
193
|
-
attributes: applyRedact({
|
|
194
|
-
...getRuntimeAttributes(),
|
|
195
|
-
...attributes,
|
|
196
|
-
}),
|
|
197
|
-
});
|
|
198
|
-
const emitLog = (record: LogRecord) => {
|
|
199
|
-
const merged: LogRecord = {
|
|
200
|
-
...record,
|
|
201
|
-
attributes: applyRedact({
|
|
202
|
-
...getRuntimeAttributes(),
|
|
203
|
-
...((record.attributes ?? {}) as Attributes),
|
|
204
|
-
}),
|
|
205
|
-
};
|
|
206
|
-
logger.emit(merged);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const instance: BkOTInstance = {
|
|
210
|
-
config,
|
|
211
|
-
tracer,
|
|
212
|
-
meter,
|
|
213
|
-
logger,
|
|
214
|
-
applyRedact,
|
|
215
|
-
emitLog,
|
|
216
|
-
getRuntimeAttributes,
|
|
217
|
-
setRuntimeAttributes,
|
|
218
|
-
startSpan,
|
|
219
|
-
reportCustomEvent({ attributes = {}, error, name }) {
|
|
220
|
-
if (!sampled) {
|
|
221
|
-
// 未采样时静默丢弃;如需调试请把 debug 选项打开
|
|
222
|
-
if (config.debug) {
|
|
223
|
-
globalThis.console?.warn('[bk-ot] custom event dropped (not sampled):', name);
|
|
224
|
-
}
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const span = startSpan(`custom.${name}`, {
|
|
229
|
-
...config.getPageAttributes(),
|
|
230
|
-
...config.getCustomAttributes(),
|
|
231
|
-
...attributes,
|
|
232
|
-
'rum.custom.name': name,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
if (error) {
|
|
236
|
-
span.recordException(error);
|
|
237
|
-
span.setStatus({
|
|
238
|
-
code: SpanStatusCode.ERROR,
|
|
239
|
-
message: error.message,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
span.end();
|
|
244
|
-
},
|
|
245
|
-
async start() {
|
|
246
|
-
if (phase === 'started') {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (phase === 'stopped') {
|
|
250
|
-
// shutdown 已经销毁了 provider/processor,此处禁止再次启动
|
|
251
|
-
throw new Error('[bk-ot] instance has been shut down and cannot be restarted; create a new instance instead.');
|
|
252
|
-
}
|
|
253
|
-
if (!config.enabled) {
|
|
254
|
-
phase = 'started';
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
propagation.setGlobalPropagator(new W3CTraceContextPropagator());
|
|
258
|
-
tracerProvider.register({
|
|
259
|
-
contextManager: new ZoneContextManager(),
|
|
260
|
-
});
|
|
261
|
-
metrics.setGlobalMeterProvider(meterProvider);
|
|
262
|
-
logs.setGlobalLoggerProvider(loggerProvider);
|
|
263
|
-
try {
|
|
264
|
-
await pluginManager.start(instance);
|
|
265
|
-
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
266
|
-
const flushOnPageHide = () => {
|
|
267
|
-
void instance.flush();
|
|
268
|
-
};
|
|
269
|
-
const flushOnHidden = () => {
|
|
270
|
-
if (document.visibilityState === 'hidden') {
|
|
271
|
-
void instance.flush();
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
window.addEventListener('pagehide', flushOnPageHide);
|
|
275
|
-
document.addEventListener('visibilitychange', flushOnHidden);
|
|
276
|
-
teardownCallbacks.push(
|
|
277
|
-
() => window.removeEventListener('pagehide', flushOnPageHide),
|
|
278
|
-
() => document.removeEventListener('visibilitychange', flushOnHidden),
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
while (teardownCallbacks.length) {
|
|
283
|
-
teardownCallbacks.pop()?.();
|
|
284
|
-
}
|
|
285
|
-
await pluginManager.shutdown();
|
|
286
|
-
trace.disable();
|
|
287
|
-
metrics.disable();
|
|
288
|
-
context.disable();
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
phase = 'started';
|
|
292
|
-
},
|
|
293
|
-
async flush() {
|
|
294
|
-
await pluginManager.flush();
|
|
295
|
-
await tracerProvider.forceFlush();
|
|
296
|
-
await meterProvider.forceFlush();
|
|
297
|
-
await loggerProvider.forceFlush();
|
|
298
|
-
},
|
|
299
|
-
async shutdown() {
|
|
300
|
-
if (phase === 'stopped') {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
await instance.flush();
|
|
304
|
-
while (teardownCallbacks.length) {
|
|
305
|
-
teardownCallbacks.pop()?.();
|
|
306
|
-
}
|
|
307
|
-
await pluginManager.shutdown();
|
|
308
|
-
await tracerProvider.shutdown();
|
|
309
|
-
await meterProvider.shutdown();
|
|
310
|
-
await loggerProvider.shutdown();
|
|
311
|
-
trace.disable();
|
|
312
|
-
metrics.disable();
|
|
313
|
-
context.disable();
|
|
314
|
-
phase = 'stopped';
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
if (config.autoStart) {
|
|
319
|
-
void instance.start().catch(error => {
|
|
320
|
-
globalThis.console?.warn('[bk-ot] auto start failed:', error);
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return instance;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
export const initBkOT = createBkOT;
|
package/src/core/url.ts
DELETED
|
@@ -1,66 +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 { NormalizedBkOTConfig } from './config';
|
|
28
|
-
|
|
29
|
-
const getBaseUrl = () => (typeof window === 'undefined' ? 'http://localhost' : window.location.origin);
|
|
30
|
-
|
|
31
|
-
const toAbsoluteUrl = (url: string) => {
|
|
32
|
-
try {
|
|
33
|
-
return new URL(url, getBaseUrl()).href;
|
|
34
|
-
} catch {
|
|
35
|
-
return url;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// 去掉 trailing slash,便于 path 边界对齐比较
|
|
40
|
-
const stripTrailingSlash = (value: string) => (value.length > 1 ? value.replace(/\/+$/, '') : value);
|
|
41
|
-
|
|
42
|
-
// 比较两个 URL 是否指向同一资源(忽略 query/fragment 与末尾斜杠)
|
|
43
|
-
const isSameResource = (left: string, right: string) => {
|
|
44
|
-
const stripped = (value: string) => stripTrailingSlash(value.replace(/[?#].*$/, ''));
|
|
45
|
-
return stripped(left) === stripped(right);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const isSameOrigin = (url: string) => {
|
|
49
|
-
try {
|
|
50
|
-
return new URL(url, getBaseUrl()).origin === getBaseUrl();
|
|
51
|
-
} catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export const isBkOTEndpoint = (config: NormalizedBkOTConfig, url: string) => {
|
|
57
|
-
const targetUrl = toAbsoluteUrl(url);
|
|
58
|
-
|
|
59
|
-
return [config.traces.endpoint, config.metrics.endpoint, config.logs.endpoint].some(endpoint =>
|
|
60
|
-
isSameResource(targetUrl, toAbsoluteUrl(endpoint))
|
|
61
|
-
);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const shouldIgnoreUrl = (config: NormalizedBkOTConfig, url: string) => isBkOTEndpoint(config, url);
|
|
65
|
-
|
|
66
|
-
export const shouldPropagateTraceHeader = (_config: NormalizedBkOTConfig, url: string) => isSameOrigin(url);
|