@blueking/open-telemetry 0.0.9 → 0.0.11
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/dist/bk-rum.global.js +2 -2
- package/dist/bk-rum.global.js.map +1 -1
- package/dist/core/config.d.ts +17 -0
- package/dist/core/telemetry-attributes.d.ts +3 -0
- package/dist/core/url.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +376 -339
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/schema/attributes.ts","../src/core/config.ts","../src/core/plugin.ts","../src/core/url.ts","../src/core/processor.ts","../src/core/runtime-context.ts","../src/schema/span.ts","../src/plugins/action.ts","../src/plugins/blank-screen.ts","../src/plugins/common.ts","../src/core/throttle.ts","../src/plugins/csp-violation.ts","../src/core/id.ts","../src/plugins/device.ts","../src/plugins/error.ts","../src/plugins/http-body.ts","../src/plugins/long-task.ts","../src/core/route-observer.ts","../src/plugins/page-view.ts","../src/plugins/resource.ts","../src/plugins/route-timing.ts","../src/plugins/session.ts","../src/plugins/web-vitals.ts","../src/plugins/websocket.ts","../src/core/sampling.ts","../src/core/sdk.ts"],"sourcesContent":["/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n/**\n * RUM 上报字段 key 集中定义。\n *\n * 插件里不要散落硬编码字符串,后续字段命名调整只改这里,避免采集端与字段文档逐步漂移。\n */\nexport const RUM_ATTRIBUTES = {\n actionType: 'action.type',\n deploymentEnvironmentName: 'deployment.environment.name',\n browserName: 'browser_name',\n browserVersion: 'browser_version',\n cacheHit: 'cache_hit',\n deviceType: 'device_type',\n durationBucket: 'duration_bucket',\n errorType: 'error_type',\n eventLabel: 'event_label',\n exceptionFingerprint: 'exception.fingerprint',\n exceptionMessageShort: 'exception.message_short',\n exceptionStackTopFrame: 'exception.stack_top_frame',\n longtaskAttributionScript: 'longtask.attribution_script',\n longtaskBlockingDuration: 'longtask.blocking_duration',\n networkEffectiveType: 'network.effective_type',\n nextHopProtocol: 'next_hop_protocol',\n result: 'result',\n serviceName: 'service.name',\n serviceVersion: 'service.version',\n osName: 'os_name',\n sessionHasReplay: 'session.has_replay',\n sessionId: 'session.id',\n sessionStartTime: 'session.start_time',\n spanSubtype: 'span_subtype',\n spanType: 'span_type',\n statusClass: 'status_class',\n targetDomain: 'target_domain',\n targetLabel: 'target_label',\n targetPathTemplate: 'target_path_template',\n targetTag: 'target.tag',\n targetTextShort: 'target.text_short',\n targetValue: 'target_value',\n traceScene: 'trace_scene',\n transferSize: 'transfer_size',\n userId: 'user.id',\n vitalMetric: 'vital.metric',\n vitalRating: 'vital.rating',\n vitalValue: 'vital.value',\n viewId: 'view.id',\n viewLoadingType: 'view.loading_type',\n viewUrl: 'view.url',\n viewUrlPathGroup: 'view.url_path_group',\n} as const;\n\nexport type RumAttributeKey = (typeof RUM_ATTRIBUTES)[keyof typeof RUM_ATTRIBUTES];\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { BkOTActionPluginOptions } from '../plugins/action';\nimport type { BkOTBlankScreenPluginOptions } from '../plugins/blank-screen';\nimport type { BkOTDevicePluginOptions } from '../plugins/device';\nimport type { BkOTErrorPluginOptions } from '../plugins/error';\nimport type { BkOTLongTaskPluginOptions } from '../plugins/long-task';\nimport type { BkOTSessionPluginOptions } from '../plugins/session';\nimport type { BkOTPlugin } from './plugin';\nimport type { BkOTUserContext } from './runtime-context';\nimport type { Attributes } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\n\nexport interface BkOTActionEventPayload {\n attributes?: Attributes;\n targetTag?: string;\n targetText?: string;\n type: 'click' | 'custom' | 'input' | 'keydown' | 'pointerdown' | 'scroll' | 'submit' | string;\n}\n\nexport interface BkOTAppConfig {\n environment?: string;\n name?: string;\n version?: string;\n}\n\nexport interface BkOTAttributesConfig {\n custom?: () => Attributes;\n error?: () => Attributes;\n metric?: () => Attributes;\n page?: () => Attributes;\n}\n\nexport type BkOTAttributeValue = boolean | number | string;\n\nexport interface BkOTBatchConfig {\n exportTimeoutMillis?: number;\n maxExportBatchSize?: number;\n maxQueueSize?: number;\n scheduledDelayMillis?: number;\n}\n\nexport interface BkOTCustomEventPayload {\n attributes?: Attributes;\n error?: Error;\n name: string;\n}\n\nexport interface BkOTHttpBodyConfig {\n maxBodySize?: number;\n redact?: (payload: BkOTHttpBodyRedactPayload) => string;\n}\n\nexport interface BkOTHttpBodyRedactPayload {\n body: string;\n contentType?: string;\n method: string;\n status?: number;\n truncated: boolean;\n type: 'request' | 'response';\n url: string;\n}\n\nexport interface BkOTInstrumentationsConfig {\n // 自定义/第三方 OTel instrumentation 实例;SDK 启用时直接注册,不受采样影响\n custom?: Instrumentation[];\n documentLoad?: boolean;\n fetch?: boolean;\n userInteraction?: boolean | { eventNames?: string[] };\n xhr?: boolean;\n}\n\nexport interface BkOTOptions {\n app?: BkOTAppConfig;\n attributes?: BkOTAttributesConfig;\n autoStart?: boolean;\n debug?: boolean;\n enabled?: boolean;\n instrumentations?: BkOTInstrumentationsConfig;\n plugins?: BkOTPluginsConfig;\n privacy?: BkOTPrivacyConfig;\n route?: BkOTRouteConfig;\n sampling?: BkOTSamplingConfig;\n spanBatch?: BkOTBatchConfig;\n spanProcessor?: BkOTSpanProcessorMode;\n transport?: BkOTTransportConfig;\n user?: BkOTUserContext;\n}\n\nexport interface BkOTPluginsConfig {\n action?: BkOTActionPluginOptions | boolean;\n blankScreen?: BkOTBlankScreenPluginOptions | boolean;\n cspViolation?: boolean;\n custom?: BkOTPlugin[];\n device?: BkOTDevicePluginOptions | boolean;\n error?: BkOTErrorPluginOptions | boolean;\n httpBody?: BkOTHttpBodyConfig | boolean;\n longTask?: BkOTLongTaskPluginOptions | boolean;\n pageView?: boolean;\n resource?: boolean;\n routeTiming?: boolean;\n session?: BkOTSessionPluginOptions | boolean;\n websocket?: boolean;\n webVitals?: boolean;\n}\n\nexport interface BkOTPrivacyConfig {\n // 用于隐私脱敏:在所有 RUM 自定义插件 emit 之前过滤一次属性\n redactAttributes?: (attributes: Attributes) => Attributes;\n // 用于隐私脱敏:所有上报到 attribute 的 URL 在写入前会经过此函数\n redactUrl?: (url: string) => string;\n}\n\nexport interface BkOTRouteConfig {\n /**\n * 返回低基数路由分组,例如 `/trace/:id`。\n * SDK 默认退化为 URL pathname;复杂业务路由建议接入方显式提供模板。\n */\n getPathGroup?: (url: string) => string | undefined;\n}\n\nexport interface BkOTSamplingConfig {\n rate?: number;\n}\n\nexport type BkOTSpanProcessorMode = 'batch' | 'simple';\n\nexport interface BkOTTransportConfig {\n endpoint?: string;\n headers?: Record<string, string>;\n signals?: SignalExportersInputConfig;\n token?: string;\n}\n\nexport interface ResolvedAppConfig {\n environment: string;\n name: string;\n version?: string;\n}\n\nexport interface ResolvedBkOTConfig {\n app: ResolvedAppConfig;\n autoStart: boolean;\n debug: boolean;\n enabled: boolean;\n hooks: ResolvedConfigHooks;\n instrumentations: BkOTInstrumentationsConfig;\n metricIntervalMillis: number;\n plugins: BkOTPluginsConfig;\n resourceAttributes: Attributes;\n sampling: ResolvedSamplingConfig;\n spanBatch?: BkOTBatchConfig;\n spanProcessor: BkOTSpanProcessorMode;\n transport: ResolvedTransportConfig;\n user?: BkOTUserContext;\n}\n\nexport interface ResolvedConfigHooks {\n getCustomAttributes: () => Attributes;\n getErrorAttributes: () => Attributes;\n getMetricAttributes: () => Attributes;\n getPageAttributes: () => Attributes;\n getViewPathGroup: (url: string) => string;\n redactAttributes: (attributes: Attributes) => Attributes;\n redactUrl: (url: string) => string;\n}\n\nexport interface ResolvedSamplingConfig {\n rate: number;\n}\n\nexport interface ResolvedTransportConfig {\n endpoint: string;\n logs: SignalExporterConfig;\n metrics: SignalExporterConfig;\n traces: SignalExporterConfig;\n}\n\nexport interface SignalEndpointsConfig {\n logs?: string;\n metrics?: string;\n traces?: string;\n}\n\nexport interface SignalExporterConfig {\n concurrencyLimit?: number;\n endpoint: string;\n headers?: Record<string, string>;\n timeoutMillis?: number;\n}\n\nexport type SignalExporterInputConfig = Partial<SignalExporterConfig>;\n\nexport interface SignalExportersInputConfig {\n endpoints?: SignalEndpointsConfig;\n logs?: SignalExporterInputConfig;\n metrics?: SignalExporterInputConfig;\n traces?: SignalExporterInputConfig;\n}\n\nconst DEFAULT_OTLP_ENDPOINT = 'http://localhost:4318'; // 默认上报到本机 OTLP collector\nconst DEFAULT_METRIC_INTERVAL_MILLIS = 60 * 1000; // 60s 上报一次\nconst DEFAULT_SERVICE_NAME = 'unknown_service';\n\nconst trimTrailingSlash = (value: string) => value.replace(/\\/+$/, '');\n\nconst resolveSignalEndpoint = (endpoint: string | undefined, signalPath: 'logs' | 'metrics' | 'traces') => {\n const normalized = trimTrailingSlash(endpoint || DEFAULT_OTLP_ENDPOINT);\n if (normalized.endsWith(`/v1/${signalPath}`)) {\n return normalized;\n }\n if (normalized.endsWith('/v1')) {\n return `${normalized}/${signalPath}`;\n }\n return `${normalized}/v1/${signalPath}`;\n};\n\nconst resolveSignalConfig = (\n transport: BkOTTransportConfig | undefined,\n signalPath: 'logs' | 'metrics' | 'traces',\n): SignalExporterConfig => {\n const signalConfig = transport?.signals?.[signalPath];\n const signalEndpoint = transport?.signals?.endpoints?.[signalPath];\n // token 只负责提供默认鉴权头;headers 保持显式配置优先,便于特殊场景覆盖。\n const mergedHeaders =\n transport?.token || transport?.headers || signalConfig?.headers\n ? {\n ...(transport?.token ? { Authorization: `Bearer ${transport.token}` } : {}),\n ...(transport?.headers ?? {}),\n ...(signalConfig?.headers ?? {}),\n }\n : undefined;\n\n return {\n concurrencyLimit: signalConfig?.concurrencyLimit,\n endpoint: signalEndpoint\n ? trimTrailingSlash(signalEndpoint)\n : resolveSignalEndpoint(signalConfig?.endpoint ?? transport?.endpoint, signalPath),\n headers: mergedHeaders,\n timeoutMillis: signalConfig?.timeoutMillis,\n };\n};\n\nconst clampSampleRate = (sampleRate?: number) => {\n if (typeof sampleRate !== 'number' || !Number.isFinite(sampleRate)) {\n return 1;\n }\n return Math.max(0, Math.min(1, sampleRate));\n};\n\nconst identityRedactAttributes = (attributes: Attributes) => attributes;\nconst identityRedactUrl = (url: string) => url;\n\nconst getDefaultPathGroup = (url: string) => {\n try {\n return new URL(url, typeof location === 'undefined' ? 'http://localhost' : location.href).pathname || '/';\n } catch {\n return url || '/';\n }\n};\n\nconst resolveAppConfig = (app: BkOTOptions['app']): ResolvedAppConfig => ({\n environment: app?.environment ?? 'production',\n name: app?.name ?? DEFAULT_SERVICE_NAME,\n version: app?.version,\n});\n\nconst resolveResourceAttributes = (app: ResolvedAppConfig): Attributes => ({\n [RUM_ATTRIBUTES.deploymentEnvironmentName]: app.environment,\n 'rum.provider': 'blueking',\n [RUM_ATTRIBUTES.serviceName]: app.name,\n ...(app.version ? { [RUM_ATTRIBUTES.serviceVersion]: app.version } : {}),\n 'telemetry.sdk.language': 'webjs',\n});\n\nconst resolveTransportConfig = (transport: BkOTOptions['transport']): ResolvedTransportConfig => ({\n endpoint: trimTrailingSlash(transport?.endpoint || DEFAULT_OTLP_ENDPOINT),\n traces: resolveSignalConfig(transport, 'traces'),\n metrics: resolveSignalConfig(transport, 'metrics'),\n logs: resolveSignalConfig(transport, 'logs'),\n});\n\nconst resolveSamplingConfig = (sampling: BkOTOptions['sampling']): ResolvedSamplingConfig => ({\n rate: clampSampleRate(sampling?.rate),\n});\n\nconst resolveHooks = (config: BkOTOptions): ResolvedConfigHooks => ({\n getPageAttributes:\n config.attributes?.page ??\n (() => ({\n 'rum.page.host': typeof window === 'undefined' ? '' : window.location.host,\n 'rum.page.path': typeof window === 'undefined' ? '' : window.location.pathname,\n })),\n getMetricAttributes: config.attributes?.metric ?? (() => ({})),\n getErrorAttributes: config.attributes?.error ?? (() => ({})),\n getCustomAttributes: config.attributes?.custom ?? (() => ({})),\n getViewPathGroup: url => config.route?.getPathGroup?.(url) ?? getDefaultPathGroup(url),\n redactAttributes: config.privacy?.redactAttributes ?? identityRedactAttributes,\n redactUrl: config.privacy?.redactUrl ?? identityRedactUrl,\n});\n\nexport const resolveConfig = (config: BkOTOptions): ResolvedBkOTConfig => {\n const app = resolveAppConfig(config.app);\n\n return {\n app,\n autoStart: config.autoStart ?? true,\n debug: config.debug ?? false,\n enabled: config.enabled ?? true,\n hooks: resolveHooks(config),\n instrumentations: config.instrumentations ?? {},\n metricIntervalMillis: DEFAULT_METRIC_INTERVAL_MILLIS,\n plugins: config.plugins ?? {},\n resourceAttributes: resolveResourceAttributes(app),\n sampling: resolveSamplingConfig(config.sampling),\n spanBatch: config.spanBatch,\n spanProcessor: config.spanProcessor ?? 'batch',\n transport: resolveTransportConfig(config.transport),\n user: config.user,\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport type { ResolvedBkOTConfig } from './config';\nimport type { BkOTUserContext, BkOTViewContext } from './runtime-context';\nimport type { Attributes, Meter, Span, TimeInput, Tracer } from '@opentelemetry/api';\nimport type { Logger, LogRecord } from '@opentelemetry/api-logs';\n\nexport interface BkOTPlugin {\n enabled?: ((config: ResolvedBkOTConfig) => boolean) | boolean;\n name: string;\n flush?: () => Promise<void> | void;\n init: (context: BkOTRuntimeContext) => Promise<void> | void;\n shutdown?: () => Promise<void> | void;\n}\n\nexport interface BkOTRuntimeContext {\n config: ResolvedBkOTConfig;\n logger: Logger;\n meter: Meter;\n tracer: Tracer;\n /** 对外提供的属性脱敏入口,所有插件在 emit 前都应过一次 */\n applyRedact: (attributes: Attributes) => Attributes;\n /** 包装 logger.emit,自动应用 redact 与 runtime attributes */\n emitLog: (record: LogRecord) => void;\n getRuntimeAttributes: () => Attributes;\n setRuntimeAttributes: (attributes: Attributes) => void;\n /** 更新用户上下文;user.id 由接入方传入,SDK 原样透传 */\n setUser: (user: BkOTUserContext) => void;\n /** 更新当前 View 上下文,后续 span/log 会自动带上 view.* 关联字段 */\n setView: (view: BkOTViewContext) => void;\n startSpan: (name: string, attributes?: Attributes, options?: BkOTStartSpanOptions) => Span;\n}\n\n/**\n * startSpan 的扩展选项。\n *\n * 用于\"事件实际发生在过去某个时刻,但 span 现在才创建\"的场景:\n * 例如 route-timing 在 raf+setTimeout 链路结束后才上报,但希望 span 的时间窗\n * 反映真实的\"路由切换瞬间到渲染完成\"区间,方便 trace 视图与并发请求对齐。\n *\n * startTime 接受 OT TimeInput;常用 epoch 毫秒(performance.timeOrigin + performance.now())。\n * 配合 span.end(endTime?) 可控制结束时间。\n */\nexport interface BkOTStartSpanOptions {\n startTime?: TimeInput;\n}\n\nexport const isPluginEnabled = (plugin: BkOTPlugin, config: ResolvedBkOTConfig) => {\n if (typeof plugin.enabled === 'function') {\n return plugin.enabled(config);\n }\n return plugin.enabled !== false;\n};\n\nexport const createPlugin = (plugin: BkOTPlugin) => plugin;\n\nexport class PluginManager {\n private readonly plugins: BkOTPlugin[];\n private startedPlugins: BkOTPlugin[] = [];\n\n public constructor(plugins: BkOTPlugin[]) {\n this.plugins = plugins;\n }\n\n public async flush() {\n // 并行 flush,避免单个慢插件拖累整体\n await Promise.all(this.startedPlugins.map(plugin => Promise.resolve().then(() => plugin.flush?.())));\n }\n\n public async shutdown() {\n const plugins = [...this.startedPlugins].reverse();\n for (const plugin of plugins) {\n await plugin.shutdown?.();\n }\n this.startedPlugins = [];\n }\n\n public async start(context: BkOTRuntimeContext) {\n // 每个插件独立 try/catch,单个插件 init 失败不应拖累其他插件:\n // - 失败的插件不会进入 startedPlugins(shutdown 时不会被反向 teardown)\n // - debug 模式打 warn,方便排查;非 debug 静默失败避免污染业务日志\n for (const plugin of this.plugins) {\n if (!isPluginEnabled(plugin, context.config)) {\n continue;\n }\n try {\n await plugin.init(context);\n this.startedPlugins.push(plugin);\n } catch (error) {\n if (context.config.debug) {\n globalThis.console?.warn(`[bk-ot] plugin \"${plugin.name}\" init failed:`, error);\n }\n }\n }\n }\n}\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport type { ResolvedBkOTConfig } from './config';\n\nconst getBaseUrl = () => (typeof window === 'undefined' ? 'http://localhost' : window.location.origin);\n\nconst toAbsoluteUrl = (url: string) => {\n try {\n return new URL(url, getBaseUrl()).href;\n } catch {\n return url;\n }\n};\n\n// 去掉 trailing slash,便于 path 边界对齐比较\nconst stripTrailingSlash = (value: string) => (value.length > 1 ? value.replace(/\\/+$/, '') : value);\n\n// 比较两个 URL 是否指向同一资源(忽略 query/fragment 与末尾斜杠)\nconst isSameResource = (left: string, right: string) => {\n const stripped = (value: string) => stripTrailingSlash(value.replace(/[?#].*$/, ''));\n return stripped(left) === stripped(right);\n};\n\nconst isSameOrigin = (url: string) => {\n try {\n return new URL(url, getBaseUrl()).origin === getBaseUrl();\n } catch {\n return false;\n }\n};\n\nexport const isBkOTEndpoint = (config: ResolvedBkOTConfig, url: string) => {\n const targetUrl = toAbsoluteUrl(url);\n\n return [config.transport.traces.endpoint, config.transport.metrics.endpoint, config.transport.logs.endpoint].some(endpoint =>\n isSameResource(targetUrl, toAbsoluteUrl(endpoint)),\n );\n};\n\nexport const shouldIgnoreUrl = (config: ResolvedBkOTConfig, url: string) => isBkOTEndpoint(config, url);\n\nexport const shouldPropagateTraceHeader = (_config: ResolvedBkOTConfig, url: string) => isSameOrigin(url);\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { isBkOTEndpoint } from './url';\n\nimport type { ResolvedBkOTConfig } from './config';\nimport type { Context } from '@opentelemetry/api';\nimport type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nconst SELF_TELEMETRY_URL_KEYS = ['http.url', 'url.full'];\n\nconst getSpanUrl = (span: ReadableSpan): string | undefined => {\n for (const key of SELF_TELEMETRY_URL_KEYS) {\n const value = span.attributes?.[key];\n if (typeof value === 'string') {\n return value;\n }\n }\n return undefined;\n};\n\n/**\n * 包装 SpanProcessor,在 onEnd 阶段过滤指向自身上报 endpoint 的 span,\n * 兜底防止 official fetch/xhr 拦截器对 OTel 自身请求产生回环 span。\n */\nexport class FilteringSpanProcessor implements SpanProcessor {\n public constructor(\n private readonly inner: SpanProcessor,\n private readonly config: ResolvedBkOTConfig,\n ) {}\n\n public forceFlush(): Promise<void> {\n return this.inner.forceFlush();\n }\n\n public onEnd(span: ReadableSpan): void {\n const url = getSpanUrl(span);\n if (url && isBkOTEndpoint(this.config, url)) {\n return;\n }\n this.inner.onEnd(span);\n }\n\n public onStart(span: Span, parentContext: Context): void {\n this.inner.onStart(span, parentContext);\n }\n\n public shutdown(): Promise<void> {\n return this.inner.shutdown();\n }\n}\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { Attributes } from '@opentelemetry/api';\n\nexport interface BkOTUserContext {\n /** 由接入方传入并由 SDK 原样透传;SDK 不 hash、不脱敏、不改写。 */\n id?: string;\n}\n\nexport interface BkOTViewContext {\n id?: string;\n loadingType?: 'initial_load' | 'route_change';\n url?: string;\n urlPathGroup?: string;\n}\n\n/**\n * 管理 SDK 运行期公共属性快照。\n *\n * 这里仅保存“后续事件都需要带上”的低成本上下文,Session/View/用户切换时更新快照,\n * 插件发 span/log 时读取当前快照即可,避免每个插件重复维护公共字段。\n */\nexport class RumRuntimeContext {\n private readonly attributes: Attributes = {};\n private readonly setOptionalAttribute = (key: string, value: string | undefined) => {\n if (value === undefined) {\n delete this.attributes[key];\n return;\n }\n this.attributes[key] = value;\n };\n private user: BkOTUserContext = {};\n\n private view: BkOTViewContext = {};\n\n public getAttributes = (): Attributes => ({ ...this.attributes });\n\n public getUser = (): BkOTUserContext => ({ ...this.user });\n\n public getView = (): BkOTViewContext => ({ ...this.view });\n\n public setAttributes = (attributes: Attributes) => {\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined) {\n delete this.attributes[key];\n continue;\n }\n this.attributes[key] = value;\n }\n };\n\n public setUser = (user: BkOTUserContext) => {\n this.user = {\n ...this.user,\n ...user,\n };\n\n if ('id' in user) {\n if (user.id === undefined) {\n delete this.attributes[RUM_ATTRIBUTES.userId];\n return;\n }\n this.attributes[RUM_ATTRIBUTES.userId] = user.id;\n }\n };\n\n public setView = (view: BkOTViewContext) => {\n this.view = {\n ...this.view,\n ...view,\n };\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewId, view.id);\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewUrl, view.url);\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewUrlPathGroup, view.urlPathGroup);\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewLoadingType, view.loadingType);\n };\n\n public constructor(initialAttributes: Attributes = {}, user?: BkOTUserContext) {\n this.setAttributes(initialAttributes);\n if (user) {\n this.setUser(user);\n }\n }\n}\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\nimport { RUM_ATTRIBUTES } from './attributes';\n\nimport type { Attributes } from '@opentelemetry/api';\n\nexport const RUM_SPAN_RESULT = {\n abort: 'abort',\n error: 'error',\n success: 'success',\n timeout: 'timeout',\n warning: 'warning',\n} as const;\n\nexport const RUM_SPAN_TYPE = {\n action: 'action',\n custom: 'custom',\n document: 'document',\n error: 'error',\n http: 'http',\n longtask: 'longtask',\n resource: 'resource',\n route: 'route',\n vital: 'vital',\n} as const;\n\nexport interface RumHttpSpanInput {\n duration?: number;\n method?: string;\n statusCode?: number;\n subtype: 'fetch' | 'xhr';\n url?: string;\n urlPathTemplate?: string;\n}\n\nexport interface RumResourceSpanInput {\n duration: number;\n initiatorType?: string;\n responseStatus?: number;\n transferSize?: number;\n url: string;\n urlPathTemplate?: string;\n}\n\nexport interface RumSpanClassification {\n duration?: number;\n errorType?: string;\n eventLabel?: string;\n result?: RumSpanResult;\n spanSubtype: string;\n spanType: RumSpanType;\n targetLabel?: string;\n targetValue?: boolean | number | string;\n traceScene?: string;\n}\n\nexport type RumSpanResult = (typeof RUM_SPAN_RESULT)[keyof typeof RUM_SPAN_RESULT];\n\nexport type RumSpanType = (typeof RUM_SPAN_TYPE)[keyof typeof RUM_SPAN_TYPE];\n\nexport interface RumUrlTarget {\n domain?: string;\n label?: string;\n pathTemplate?: string;\n}\n\nexport const getDurationBucket = (duration: number) => {\n if (duration < 100) return '<100ms';\n if (duration < 500) return '100~500ms';\n if (duration < 2000) return '500ms~2s';\n return '>2s';\n};\n\nexport const getStatusClass = (statusCode: number) => `${Math.trunc(statusCode / 100)}xx`;\n\nexport const getHttpResult = (statusCode: number | undefined): RumSpanResult => {\n if (statusCode === 408 || statusCode === 504) return RUM_SPAN_RESULT.timeout;\n if (typeof statusCode === 'number' && statusCode >= 400) return RUM_SPAN_RESULT.error;\n return RUM_SPAN_RESULT.success;\n};\n\nexport const getHttpErrorType = (statusCode: number | undefined) => {\n if (statusCode === 408 || statusCode === 504) return 'network_timeout';\n if (typeof statusCode !== 'number' || statusCode < 400) return 'none';\n return statusCode >= 500 ? 'http_5xx' : 'http_4xx';\n};\n\nexport const getVitalResult = (rating: string | undefined): RumSpanResult =>\n rating === 'good' ? RUM_SPAN_RESULT.success : RUM_SPAN_RESULT.warning;\n\nexport const getUrlTarget = (url: string | undefined, pathTemplate?: string): RumUrlTarget => {\n if (!url) {\n return {};\n }\n try {\n const parsed = new URL(url, typeof location === 'undefined' ? 'http://localhost' : location.href);\n const normalizedPath = pathTemplate ?? parsed.pathname;\n return {\n domain: parsed.host,\n label: `${parsed.host}${normalizedPath}`,\n pathTemplate: normalizedPath,\n };\n } catch {\n return {\n label: url,\n pathTemplate: pathTemplate ?? url,\n };\n }\n};\n\nexport const buildRumSpanAttributes = ({\n duration,\n errorType = 'none',\n eventLabel,\n result = RUM_SPAN_RESULT.success,\n spanSubtype,\n spanType,\n targetLabel,\n targetValue,\n traceScene,\n}: RumSpanClassification): Attributes => ({\n [RUM_ATTRIBUTES.spanType]: spanType,\n [RUM_ATTRIBUTES.spanSubtype]: spanSubtype,\n [RUM_ATTRIBUTES.result]: result,\n [RUM_ATTRIBUTES.errorType]: errorType,\n ...(duration !== undefined ? { [RUM_ATTRIBUTES.durationBucket]: getDurationBucket(duration) } : {}),\n ...(eventLabel !== undefined ? { [RUM_ATTRIBUTES.eventLabel]: eventLabel } : {}),\n ...(targetLabel !== undefined ? { [RUM_ATTRIBUTES.targetLabel]: targetLabel } : {}),\n ...(targetValue !== undefined ? { [RUM_ATTRIBUTES.targetValue]: targetValue } : {}),\n ...(traceScene !== undefined ? { [RUM_ATTRIBUTES.traceScene]: traceScene } : {}),\n});\n\nexport const buildRumHttpSpanAttributes = ({\n duration,\n method,\n statusCode,\n subtype,\n url,\n urlPathTemplate,\n}: RumHttpSpanInput): Attributes => {\n const target = getUrlTarget(url, urlPathTemplate);\n return {\n ...buildRumSpanAttributes({\n duration,\n errorType: getHttpErrorType(statusCode),\n eventLabel: 'API 调用',\n result: getHttpResult(statusCode),\n spanSubtype: subtype,\n spanType: RUM_SPAN_TYPE.http,\n targetLabel: target.label,\n targetValue: statusCode,\n }),\n ...(method ? { 'http.request.method': method.toUpperCase() } : {}),\n ...(target.domain ? { [RUM_ATTRIBUTES.targetDomain]: target.domain } : {}),\n ...(target.pathTemplate ? { [RUM_ATTRIBUTES.targetPathTemplate]: target.pathTemplate } : {}),\n ...(typeof statusCode === 'number'\n ? {\n 'http.response.status_code': statusCode,\n [RUM_ATTRIBUTES.statusClass]: getStatusClass(statusCode),\n }\n : {}),\n };\n};\n\nexport const buildRumResourceSpanAttributes = ({\n duration,\n initiatorType = 'other',\n responseStatus,\n transferSize,\n url,\n urlPathTemplate,\n}: RumResourceSpanInput): Attributes => {\n const target = getUrlTarget(url, urlPathTemplate);\n const result = typeof responseStatus === 'number' ? getHttpResult(responseStatus) : RUM_SPAN_RESULT.success;\n return {\n ...buildRumSpanAttributes({\n duration,\n errorType: typeof responseStatus === 'number' ? getHttpErrorType(responseStatus) : 'none',\n eventLabel: '静态资源',\n result,\n spanSubtype: initiatorType,\n spanType: RUM_SPAN_TYPE.resource,\n targetLabel: target.label ?? url,\n targetValue: transferSize,\n }),\n ...(target.domain ? { [RUM_ATTRIBUTES.targetDomain]: target.domain } : {}),\n ...(target.pathTemplate ? { [RUM_ATTRIBUTES.targetPathTemplate]: target.pathTemplate } : {}),\n ...(typeof responseStatus === 'number'\n ? {\n 'http.response.status_code': responseStatus,\n [RUM_ATTRIBUTES.statusClass]: getStatusClass(responseStatus),\n }\n : {}),\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport type BkOTActionEventName = 'click' | 'input' | 'keydown' | 'pointerdown' | 'scroll' | 'submit';\n\nexport interface BkOTActionPluginOptions {\n eventNames?: BkOTActionEventName[];\n}\n\nconst DEFAULT_EVENT_NAMES: BkOTActionEventName[] = ['click', 'submit', 'keydown'];\n\nconst getElement = (target: EventTarget | null) => (target instanceof Element ? target : undefined);\n\nconst getTargetText = (element: Element | undefined) => {\n if (!element) {\n return undefined;\n }\n const text = element.textContent?.trim() || element.getAttribute('aria-label') || element.getAttribute('title') || '';\n return text ? text.slice(0, 32) : undefined;\n};\n\nexport const createActionPlugin = (options: BkOTActionPluginOptions = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const eventNames = options.eventNames ?? DEFAULT_EVENT_NAMES;\n\n return {\n name: 'action',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const listenerOptions: AddEventListenerOptions = { capture: true, passive: true };\n\n const emitAction = (event: Event) => {\n const element = getElement(event.target);\n const targetText = getTargetText(element);\n const targetTag = element?.tagName.toLowerCase();\n const span = context.startSpan(`action.${event.type}`, {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: '用户操作',\n spanSubtype: event.type,\n spanType: RUM_SPAN_TYPE.action,\n targetLabel: targetText ?? targetTag ?? event.type,\n }),\n [RUM_ATTRIBUTES.actionType]: event.type,\n ...(targetText ? { [RUM_ATTRIBUTES.targetTextShort]: targetText } : {}),\n ...(targetTag ? { [RUM_ATTRIBUTES.targetTag]: targetTag } : {}),\n });\n span.end();\n };\n\n for (const eventName of eventNames) {\n window.addEventListener(eventName, emitAction, listenerOptions);\n teardownCallbacks.push(() => window.removeEventListener(eventName, emitAction, listenerOptions));\n }\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 白屏检测插件,基于视口采样判定首屏是否疑似空白。\n *\n * 检测策略:\n * - 在 `DOMContentLoaded` 后延迟 checkDelay(默认 3s)触发一次检测,避免 SPA / 慢加载场景下检测时机过早。\n * - 在视口的 5 个采样点(中心、四象限)调用 `document.elementFromPoint`,命中\"根元素 / body / documentElement\"\n * 即视为该样本\"空白\"。\n * - 命中 `loadingSelectors`(默认含常见 loading mask / spinner,业务可通过 `ignoreSelectors` 追加)的样本\n * 既不计入分子也不计入分母,避免骨架屏被误判为白屏。\n * - 命中比例 `score = empty / (total - loading)` 超过 threshold(默认 0.8)即判白屏;全部样本都是 loading 时\n * 视为待定不上报。\n *\n * 上报形态(仅在判定为白屏时):\n * - metric: `browser.blank_screen.count`,仅带低基数维度 `blank_screen.root`,控制 cardinality。\n * - error span: `browser.blank_screen`,携带 score、threshold、命中元素、DOM 节点数等信息,用于错误分析。\n *\n * SPA 注意事项:\n * - 默认 `rootSelector` 为 `body`。SPA 项目(React/Vue 等)应显式配置为应用挂载点(如 `#app` / `#root`),\n * 否则一旦应用成功 mount,采样点会落在挂载根的子节点上而非 body,永远无法判定为白屏。\n *\n * 关键 attribute:\n * - `blank_screen.score` / `threshold` / `detected`:判定结果与依据。\n * - `blank_screen.sample_total` / `sample_valid` / `sample_loading`:采样统计明细,便于复盘 loading 排除情况。\n * - `blank_screen.center_element`:视口中心点元素的简化选择器,辅助定位问题。\n * - `blank_screen.dom_node_count`:body 下 DOM 节点总数,用于判断\"是否真的没渲染\"。\n */\n\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport interface BkOTBlankScreenPluginOptions {\n checkDelay?: number;\n // 自定义\"页面已渲染\"忽略选择器,命中即认为是 loading 占位、非空白\n ignoreSelectors?: string[];\n rootSelector?: string;\n threshold?: number;\n}\n\nconst DEFAULT_SELECTOR = 'body';\nconst DEFAULT_CHECK_DELAY = 3000;\nconst DEFAULT_THRESHOLD = 0.8;\n// 常见 loading mask / spinner 选择器,命中说明页面正在加载而非空白\nconst DEFAULT_LOADING_SELECTORS = [\n '[data-loading=\"true\"]',\n '[aria-busy=\"true\"]',\n '.loading',\n '.is-loading',\n '.spinner',\n '.skeleton',\n '.bk-loading',\n];\n\nconst SAMPLE_POINTS: Array<[number, number]> = [\n [0.5, 0.5],\n [0.25, 0.25],\n [0.75, 0.25],\n [0.25, 0.75],\n [0.75, 0.75],\n];\n\nconst nonNegativeNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;\n\nconst boundedRatio = (value: number | undefined, fallback: number) => {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return fallback;\n }\n return Math.max(0, Math.min(1, value));\n};\n\nconst getElementSelector = (element: Element | null) => {\n if (!element) {\n return '';\n }\n const tag = element.tagName.toLowerCase();\n if (element.id) {\n return `${tag}#${element.id}`;\n }\n return tag;\n};\n\nconst matchesAny = (element: Element | null, selectors: string[]) => {\n if (!element) return false;\n return selectors.some(selector => {\n try {\n return element.matches(selector) || !!element.closest(selector);\n } catch {\n return false;\n }\n });\n};\n\n// 页面就绪后再开始计时,避免 SPA / 慢加载场景下检测时机过早\n// 返回 dispose 用于在 shutdown 时取消尚未触发的监听,避免事件逃逸\nconst whenDocumentReady = (callback: () => void): (() => void) => {\n if (typeof document === 'undefined') return () => {};\n if (document.readyState === 'complete' || document.readyState === 'interactive') {\n callback();\n return () => {};\n }\n const handler = () => {\n document.removeEventListener('DOMContentLoaded', handler);\n callback();\n };\n document.addEventListener('DOMContentLoaded', handler);\n return () => document.removeEventListener('DOMContentLoaded', handler);\n};\n\nexport const createBlankScreenPlugin = (options: BkOTBlankScreenPluginOptions = {}): BkOTPlugin => {\n let timer: number | undefined;\n let disposeReadyListener: (() => void) | undefined;\n let disposed = false;\n const config = {\n checkDelay: nonNegativeNumber(options.checkDelay, DEFAULT_CHECK_DELAY),\n ignoreSelectors: options.ignoreSelectors ?? [],\n rootSelector: options.rootSelector ?? DEFAULT_SELECTOR,\n threshold: boundedRatio(options.threshold, DEFAULT_THRESHOLD),\n };\n\n return {\n name: 'blank-screen',\n init(context) {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n const { checkDelay, rootSelector, threshold } = config;\n const loadingSelectors = [...DEFAULT_LOADING_SELECTORS, ...config.ignoreSelectors];\n\n // 仅创建一次 counter,避免每次回调都触发一次 meter lookup\n const counter = context.meter.createCounter('browser.blank_screen.count', {\n description: 'Number of suspected blank screen detections',\n });\n\n const runDetection = () => {\n try {\n const root = document.querySelector(rootSelector);\n let emptyCount = 0;\n let loadingCount = 0;\n\n for (const [x, y] of SAMPLE_POINTS) {\n const element = document.elementFromPoint(window.innerWidth * x, window.innerHeight * y);\n if (matchesAny(element, loadingSelectors)) {\n loadingCount += 1;\n continue;\n }\n if (element === root || element === document.body || element === document.documentElement) {\n emptyCount += 1;\n }\n }\n\n // loading 样本既不计入分子也不计入分母,避免稀释空白率;全部为 loading 时视为待定不上报\n const validCount = SAMPLE_POINTS.length - loadingCount;\n if (validCount === 0) {\n return;\n }\n const score = emptyCount / validCount;\n const isBlank = score >= threshold;\n const centerElement = getElementSelector(\n document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2),\n );\n const attributes = {\n ...buildRumSpanAttributes({\n errorType: isBlank ? 'blank_screen' : 'none',\n eventLabel: '白屏',\n result: isBlank ? RUM_SPAN_RESULT.error : RUM_SPAN_RESULT.success,\n spanSubtype: 'blank_screen',\n spanType: RUM_SPAN_TYPE.error,\n targetLabel: rootSelector,\n targetValue: score,\n }),\n 'blank_screen.score': score,\n 'blank_screen.threshold': threshold,\n 'blank_screen.root': rootSelector,\n 'blank_screen.detected': isBlank,\n 'blank_screen.sample_total': SAMPLE_POINTS.length,\n 'blank_screen.sample_valid': validCount,\n 'blank_screen.sample_loading': loadingCount,\n 'blank_screen.center_element': centerElement,\n 'blank_screen.dom_node_count': document.body?.getElementsByTagName('*').length ?? 0,\n };\n\n if (isBlank) {\n counter.add(\n 1,\n context.applyRedact({\n // metric 仅放低基数维度,center_element 等高基数字段只进入 error span\n 'blank_screen.root': rootSelector,\n }),\n );\n context.startSpan('browser.blank_screen', attributes).end();\n }\n } catch (error) {\n // 单次检测异常不应影响 SDK 其它链路;debug 模式下打印便于排查\n if (context.config.debug) {\n globalThis.console?.warn('[bk-ot][blank-screen] detection failed:', error);\n }\n }\n };\n\n disposeReadyListener = whenDocumentReady(() => {\n // 在 shutdown 之后才触发的 ready 事件不应再注册 timer\n if (disposed) return;\n timer = window.setTimeout(runDetection, checkDelay);\n });\n },\n shutdown() {\n disposed = true;\n disposeReadyListener?.();\n disposeReadyListener = undefined;\n if (timer !== undefined && typeof window !== 'undefined') {\n window.clearTimeout(timer);\n timer = undefined;\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 官方 instrumentation 适配插件,把 OpenTelemetry 浏览器侧的 4 个 instrumentation\n * (document-load / fetch / xhr / user-interaction)按需装载并接入 BkOT 的 page attributes、URL 脱敏、\n * endpoint 自循环过滤、同源 traceparent 传播等通用能力。\n *\n * 加载策略:\n * - 4 个 instrumentation 通过动态 import 按需引入,未启用的不会进入 bundle。\n * - 加载使用 Promise.all 并行执行,避免 4 个 chunk 串行 RTT 累积。\n *\n * URL 行为:\n * - `ignoreUrls`:屏蔽对自身上报 endpoint(trace/metric/log)的采集,避免 trace 自循环放大上报量。\n * - `propagateTraceHeaderCorsUrls`:仅向同源请求注入 W3C `traceparent`,避免被跨域预检拒绝。\n * - 上报前对 span 上的 `http.url` / `url.full` 调用 `redactUrl` 做脱敏。\n *\n * span 装饰:\n * - 每个由 fetch/xhr instrumentation 产生的 span,统一补一份 `getPageAttributes()` + `applyRedact`\n * 的属性,与自定义插件保持一致。\n *\n * 隐式依赖:\n * - `registerInstrumentations` 没有显式传 tracerProvider / meterProvider,依赖\n * `BkOpenTelemetry.start()` 此前已经把对应 provider 设为 global。\n * `PluginManager.start` 是在那一步之后才调用,因此时序是稳定的。\n */\n\nimport { type Instrumentation, registerInstrumentations } from '@opentelemetry/instrumentation';\n\nimport { shouldIgnoreUrl, shouldPropagateTraceHeader } from '../core/url';\nimport {\n buildRumHttpSpanAttributes,\n buildRumResourceSpanAttributes,\n buildRumSpanAttributes,\n RUM_SPAN_TYPE,\n} from '../schema/span';\n\nimport type { BkOTInstrumentationsConfig } from '../core/config';\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\nimport type { Span } from '@opentelemetry/api';\n\nconst URL_KEYS = ['http.url', 'url.full'];\nconst STATUS_KEYS = ['http.status_code', 'http.response.status_code'];\nconst METHOD_KEYS = ['http.method', 'http.request.method'];\n\n// OTel SDK 实现的 Span 在 export 前会暴露 `attributes` 字段供消费者读取;\n// 这是 implementation detail 但 OTel SDK 长期保留,比 `as any` 更收敛。\ntype DecoratableSpan = Span & { attributes?: Record<string, unknown> };\n\n// OTel 官方 instrumentation 内部对每个 URL 调用 matcher.test(url),\n// 这里把\"用户函数 + 自身 endpoint 过滤\"包装成具备 .test 的对象,是与 OTel 一致的扩展用法\nconst toUrlPredicateMatcher = (predicate: (url: string) => boolean): RegExp =>\n ({ test: predicate }) as unknown as RegExp;\n\n// 对官方 instrumentation 创建的 span,统一附加 page attributes 与 URL 脱敏\nconst decorateSpan = (context: BkOTRuntimeContext, span: DecoratableSpan, subtype: 'fetch' | 'xhr') => {\n span.setAttributes(context.applyRedact(context.config.hooks.getPageAttributes()));\n let redactedUrl: string | undefined;\n for (const key of URL_KEYS) {\n const value = span.attributes?.[key];\n if (typeof value === 'string') {\n const redactedAttributes = context.applyRedact({ [key]: context.config.hooks.redactUrl(value) });\n const nextUrl = redactedAttributes[key];\n if (typeof nextUrl === 'string') {\n redactedUrl = nextUrl;\n span.setAttribute(key, nextUrl);\n }\n }\n }\n const statusCode = STATUS_KEYS.map(key => span.attributes?.[key]).find(value => typeof value === 'number') as\n | number\n | undefined;\n const method = METHOD_KEYS.map(key => span.attributes?.[key]).find(value => typeof value === 'string') as\n | string\n | undefined;\n const sourceUrl =\n redactedUrl ??\n (URL_KEYS.map(key => span.attributes?.[key]).find(value => typeof value === 'string') as string | undefined);\n\n span.setAttributes(\n context.applyRedact(\n buildRumHttpSpanAttributes({\n method,\n statusCode,\n subtype,\n url: sourceUrl,\n urlPathTemplate: sourceUrl ? context.config.hooks.getViewPathGroup(sourceUrl) : undefined,\n }),\n ),\n );\n};\n\nconst decorateDocumentSpan = (context: BkOTRuntimeContext, span: Span, subtype: string) => {\n span.setAttributes(\n context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: '文档加载',\n spanSubtype: subtype,\n spanType: RUM_SPAN_TYPE.document,\n targetLabel: context.config.hooks.getViewPathGroup(typeof location === 'undefined' ? '' : location.href),\n traceScene: 'page_load',\n }),\n }),\n );\n};\n\nconst decorateDocumentResourceSpan = (context: BkOTRuntimeContext, span: Span, resource: PerformanceResourceTiming) => {\n const redactedUrl = context.config.hooks.redactUrl(resource.name);\n span.setAttributes(\n context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...buildRumResourceSpanAttributes({\n duration: resource.duration,\n initiatorType: resource.initiatorType || 'other',\n responseStatus: (resource as PerformanceResourceTiming & { responseStatus?: number }).responseStatus,\n transferSize: resource.transferSize,\n url: redactedUrl,\n urlPathTemplate: context.config.hooks.getViewPathGroup(resource.name),\n }),\n 'url.full': redactedUrl,\n }),\n );\n};\n\nexport const createCommonInstrumentationsPlugin = (option: BkOTInstrumentationsConfig = {}): BkOTPlugin => {\n const instrumentations: Instrumentation[] = [];\n const instrumentationOptions = {\n documentLoad: option.documentLoad ?? true,\n fetch: option.fetch ?? true,\n xhr: option.xhr ?? true,\n userInteraction: option.userInteraction ?? true,\n };\n\n return {\n name: 'official-instrumentations',\n enabled: Boolean(\n instrumentationOptions.documentLoad ||\n instrumentationOptions.fetch ||\n instrumentationOptions.xhr ||\n instrumentationOptions.userInteraction,\n ),\n async init(context) {\n const ignoreUrls = [toUrlPredicateMatcher(url => shouldIgnoreUrl(context.config, url))];\n const propagateTraceHeaderCorsUrls = [\n toUrlPredicateMatcher(url => shouldPropagateTraceHeader(context.config, url)),\n ];\n\n // 并行加载所有按需启用的 chunk,避免串行 RTT 累积\n const [docLoadMod, fetchMod, xhrMod, uiMod] = await Promise.all([\n instrumentationOptions.documentLoad\n ? import('@opentelemetry/instrumentation-document-load')\n : Promise.resolve(null),\n instrumentationOptions.fetch ? import('@opentelemetry/instrumentation-fetch') : Promise.resolve(null),\n instrumentationOptions.xhr\n ? import('@opentelemetry/instrumentation-xml-http-request')\n : Promise.resolve(null),\n instrumentationOptions.userInteraction\n ? import('@opentelemetry/instrumentation-user-interaction')\n : Promise.resolve(null),\n ]);\n\n // 每加载一个就立刻 push 到外层 instrumentations,确保 init 中途失败时 shutdown 仍能清理已构造的实例\n if (docLoadMod) {\n instrumentations.push(\n new docLoadMod.DocumentLoadInstrumentation({\n semconvStabilityOptIn: 'http',\n applyCustomAttributesOnSpan: {\n documentLoad: span => decorateDocumentSpan(context, span, 'navigate'),\n documentFetch: span => decorateDocumentSpan(context, span, 'document_fetch'),\n resourceFetch: (span, resource) => decorateDocumentResourceSpan(context, span, resource),\n },\n }),\n );\n }\n\n if (fetchMod) {\n instrumentations.push(\n new fetchMod.FetchInstrumentation({\n applyCustomAttributesOnSpan: span => decorateSpan(context, span as DecoratableSpan, 'fetch'),\n ignoreUrls,\n propagateTraceHeaderCorsUrls,\n semconvStabilityOptIn: 'http',\n ignoreNetworkEvents: false,\n }),\n );\n }\n\n if (xhrMod) {\n instrumentations.push(\n new xhrMod.XMLHttpRequestInstrumentation({\n applyCustomAttributesOnSpan: span => decorateSpan(context, span as DecoratableSpan, 'xhr'),\n ignoreUrls,\n propagateTraceHeaderCorsUrls,\n semconvStabilityOptIn: 'http',\n ignoreNetworkEvents: false,\n }),\n );\n }\n\n if (uiMod) {\n // typeof null === 'object' 是 JS 历史遗留坑;这里显式排除,防止外部传入 null 时取 .eventNames 抛错\n const userInteractionConfig =\n instrumentationOptions.userInteraction !== null && typeof instrumentationOptions.userInteraction === 'object'\n ? { eventNames: instrumentationOptions.userInteraction.eventNames as Array<keyof HTMLElementEventMap> }\n : undefined;\n instrumentations.push(new uiMod.UserInteractionInstrumentation(userInteractionConfig));\n }\n\n // 这里没有显式传 tracerProvider / meterProvider,依赖 BkOpenTelemetry.start() 此前已通过\n // tracerProvider.register() 与 setGlobalMeterProvider() 把 BkOT 的 provider 注册为 global,\n // 否则官方 instrumentation 会拿到 NoopProvider 导致数据丢失。\n // PluginManager.start 在 sdk.ts 中是在上述两步之后才调用,因此该顺序是稳定的。\n registerInstrumentations({ instrumentations });\n },\n shutdown() {\n while (instrumentations.length) {\n instrumentations.pop()?.disable?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 简易 djb2 字符串 hash。结果稳定、短(base36),足以区分常见 fingerprint 字符串组合。\n * 不是密码学安全 hash,仅用于事件聚合分组场景。\n */\nexport const hashString = (input: string): string => {\n let hash = 5381;\n for (let i = 0; i < input.length; i++) {\n hash = (hash * 33) ^ input.charCodeAt(i);\n }\n return (hash >>> 0).toString(36);\n};\n\nexport interface ThrottleResult {\n /** 是否允许本次上报 */\n allowed: boolean;\n /** 当前窗口内同 fingerprint 的总触发次数(含本次,含被丢弃次数),便于下游感知是否被节流 */\n count: number;\n}\n\ninterface ThrottleEntry {\n count: number;\n windowStart: number;\n}\n\n/**\n * 时间窗节流:同一 key 在 windowMs 窗口内最多允许 maxPerWindow 次通过;\n * 超出则被拦截,但 `count` 仍累加,便于下游感知风暴规模。\n *\n * 长会话场景下避免 records 无限累积——按窗口长度节奏轻量清理一次过期 entry。\n */\nexport const createThrottle = (windowMs: number, maxPerWindow: number) => {\n const records = new Map<string, ThrottleEntry>();\n let lastSweep = 0;\n const sweep = (now: number) => {\n if (now - lastSweep < windowMs) return;\n lastSweep = now;\n for (const [key, entry] of records) {\n if (now - entry.windowStart > windowMs) records.delete(key);\n }\n };\n return {\n allow(key: string): ThrottleResult {\n const now = Date.now();\n sweep(now);\n const entry = records.get(key);\n if (!entry || now - entry.windowStart > windowMs) {\n records.set(key, { count: 1, windowStart: now });\n return { allowed: true, count: 1 };\n }\n entry.count += 1;\n return { allowed: entry.count <= maxPerWindow, count: entry.count };\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { createThrottle, hashString } from '../core/throttle';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nconst CSP_THROTTLE_WINDOW_MS = 60_000;\nconst CSP_THROTTLE_MAX_PER_WINDOW = 5;\n\n// blockedURI 除了 URL 还可能是 'inline' / 'eval' / 'wasm-eval' / 'self' 等关键字(CSP spec),\n// 这些不是 URL,不应该走 redactUrl\nconst URL_LIKE_BLOCKED_URI = /^https?:|^\\/\\//i;\n\nconst redactBlockedUri = (redactUrl: (url: string) => string, blockedURI: string | undefined) => {\n if (!blockedURI) return undefined;\n return URL_LIKE_BLOCKED_URI.test(blockedURI) ? redactUrl(blockedURI) : blockedURI;\n};\n\n/**\n * CSP 违规事件采集插件,通过监听 `securitypolicyviolation` 事件捕获被 CSP 策略拦截的脚本/资源,\n * 以 error span 上报,进入统一错误分析口径。\n *\n * 节流策略:\n * - 同 fingerprint(`violatedDirective + blockedURI + sourceFile + lineNumber`)\n * 在 60s 窗口内最多上报 5 条,避免风暴打爆 collector(如某段被拦截的 inline 脚本被反复渲染)。\n * - 每条 attribute 带 `csp.window_count`,反映当前窗口内同 fingerprint 的总触发次数(含被丢弃的)。\n *\n * 体积优化:\n * - `csp.original_policy`(完整 CSP 策略,可能数 KB)仅在窗口首条上报,后续同 fingerprint 不再重复携带。\n *\n * URL 处理:\n * - `blockedURI` / `sourceFile` 中的 URL 类值会经 `redactUrl` 脱敏。\n * - `blockedURI` 是 'inline' / 'eval' 等 CSP 关键字时直接保留,不调用 redactUrl。\n */\nexport const createCspViolationPlugin = (): BkOTPlugin => {\n let teardown: (() => void) | undefined;\n\n return {\n name: 'csp-violation',\n init(context) {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n const throttle = createThrottle(CSP_THROTTLE_WINDOW_MS, CSP_THROTTLE_MAX_PER_WINDOW);\n\n const handler = (event: SecurityPolicyViolationEvent) => {\n const fingerprint = hashString(\n `${event.violatedDirective}|${event.blockedURI}|${event.sourceFile}|${event.lineNumber}`,\n );\n const result = throttle.allow(fingerprint);\n if (!result.allowed) return;\n\n const blockedUri = redactBlockedUri(context.config.hooks.redactUrl, event.blockedURI);\n const sourceFile = event.sourceFile ? context.config.hooks.redactUrl(event.sourceFile) : undefined;\n // 窗口首条带完整 originalPolicy 用于排查策略本身;后续同 fingerprint 省略,避免数 KB 字段反复传输\n const includeOriginalPolicy = result.count === 1;\n\n context\n .startSpan('csp.violation', {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'csp',\n eventLabel: 'CSP 错误',\n result: RUM_SPAN_RESULT.error,\n spanSubtype: 'csp',\n spanType: RUM_SPAN_TYPE.error,\n targetLabel: blockedUri ?? event.effectiveDirective,\n targetValue: event.statusCode,\n }),\n 'csp.blocked_uri': blockedUri,\n 'csp.violated_directive': event.violatedDirective,\n 'csp.effective_directive': event.effectiveDirective,\n 'csp.disposition': event.disposition,\n 'csp.source_file': sourceFile,\n 'csp.line_number': event.lineNumber,\n 'csp.column_number': event.columnNumber,\n 'csp.status_code': event.statusCode,\n 'csp.fingerprint': fingerprint,\n 'csp.window_count': result.count,\n [RUM_ATTRIBUTES.exceptionFingerprint]: fingerprint,\n 'error.source': 'csp',\n ...(includeOriginalPolicy ? { 'csp.original_policy': event.originalPolicy } : {}),\n })\n .end();\n };\n\n document.addEventListener('securitypolicyviolation', handler);\n teardown = () => document.removeEventListener('securitypolicyviolation', handler);\n },\n shutdown() {\n teardown?.();\n teardown = undefined;\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 生成随机 ID。优先用 `crypto.randomUUID`(standards-compliant UUID v4),不可用时回退到\n * `${prefix}-${timestamp}-${rand}` 形式。\n *\n * 注意:fallback 实现使用 Math.random,**不是密码学安全**,仅适用于设备/会话/事件标识等场景,\n * 不要复用此函数生成 token、CSRF 防护等安全相关的值。\n */\nexport const createRandomId = (prefix: string) => {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { createRandomId } from '../core/id';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { BkOTPlugin } from '../core/plugin';\nimport type { Attributes } from '@opentelemetry/api';\n\nexport interface BkOTDevicePluginOptions {\n storageKey?: string;\n}\n\nconst DEFAULT_DEVICE_STORAGE_KEY = 'BK_RUM_DEVICE_ID';\n// 用户拖窗口时 resize 高频触发,debounce 静默期内只在最后一次更新,避免无谓的 setRuntimeAttributes 风暴\nconst VIEWPORT_DEBOUNCE_MS = 250;\n\ninterface NetworkInformationLike {\n downlink?: number;\n effectiveType?: string;\n rtt?: number;\n saveData?: boolean;\n type?: string;\n addEventListener?: (type: 'change', listener: () => void) => void;\n removeEventListener?: (type: 'change', listener: () => void) => void;\n}\n\ninterface UserAgentDataLike {\n mobile?: boolean;\n platform?: string;\n}\n\nconst getConnection = (): NetworkInformationLike | undefined =>\n (\n navigator as Navigator & {\n connection?: NetworkInformationLike;\n }\n ).connection;\n\nconst getUserAgentData = (): undefined | UserAgentDataLike =>\n (\n navigator as Navigator & {\n userAgentData?: UserAgentDataLike;\n }\n ).userAgentData;\n\nconst getBrowserInfo = (ua: string) => {\n const matchers: Array<[string, RegExp]> = [\n ['Edge', /Edg\\/([\\d.]+)/],\n ['Chrome', /Chrome\\/([\\d.]+)/],\n ['Firefox', /Firefox\\/([\\d.]+)/],\n ['Safari', /Version\\/([\\d.]+).*Safari/],\n ];\n for (const [name, pattern] of matchers) {\n const matched = ua.match(pattern);\n if (matched?.[1]) {\n return { name, version: matched[1].split('.')[0] };\n }\n }\n return { name: 'Other', version: undefined };\n};\n\nconst getOsName = (ua: string, platform?: string) => {\n const value = `${platform ?? ''} ${ua}`;\n if (/iphone|ipad|ipod/i.test(value)) return 'iOS';\n if (/android/i.test(value)) return 'Android';\n if (/mac/i.test(value)) return 'macOS';\n if (/win/i.test(value)) return 'Windows';\n if (/linux/i.test(value)) return 'Linux';\n return 'Other';\n};\n\nconst readDeviceId = (storageKey: string) => {\n let existed: null | string = null;\n try {\n existed = window.localStorage.getItem(storageKey);\n } catch {\n /* localStorage 不可用(隐私模式 / sandbox iframe),下面会创建一个 in-memory id */\n }\n if (existed) {\n return existed;\n }\n const deviceId = createRandomId('device');\n try {\n window.localStorage.setItem(storageKey, deviceId);\n } catch {\n /* 写不进去也行,至少本次 SDK 生命周期内 deviceId 仍然稳定 */\n }\n return deviceId;\n};\n\n// 视口尺寸:随窗口大小 / 屏幕旋转变化,需要在 resize / orientationchange 时刷新\nconst getViewportAttributes = (): Attributes => {\n if (typeof window === 'undefined') {\n return {};\n }\n return {\n 'browser.viewport.width': window.innerWidth,\n 'browser.viewport.height': window.innerHeight,\n 'browser.screen.width': window.screen?.width,\n 'browser.screen.height': window.screen?.height,\n };\n};\n\n// 网络信息:随 connection.change 变化\nconst getNetworkAttributes = (): Attributes => {\n const connection = getConnection();\n if (!connection) {\n return {};\n }\n return {\n 'network.effective_type': connection.effectiveType,\n 'network.connection_type': connection.type,\n 'network.downlink': connection.downlink,\n 'network.rtt': connection.rtt,\n 'network.save_data': connection.saveData,\n };\n};\n\n// 设备硬件能力:进入页面时一次性读取,运行期不会变化\nconst getHardwareAttributes = (): Attributes => {\n if (typeof navigator === 'undefined') {\n return {};\n }\n const uaData = getUserAgentData();\n const ua = navigator.userAgent;\n const browser = getBrowserInfo(ua);\n const osName = getOsName(ua, uaData?.platform);\n const deviceType = uaData?.mobile || /mobi|android|iphone|ipad|ipod/i.test(ua) ? 'mobile' : 'desktop';\n return {\n 'device.memory': (navigator as Navigator & { deviceMemory?: number }).deviceMemory,\n 'device.cpu_cores': navigator.hardwareConcurrency,\n 'device.platform': uaData?.platform,\n 'device.mobile': uaData?.mobile,\n [RUM_ATTRIBUTES.browserName]: browser.name,\n [RUM_ATTRIBUTES.browserVersion]: browser.version,\n [RUM_ATTRIBUTES.deviceType]: deviceType,\n [RUM_ATTRIBUTES.osName]: osName,\n 'user_agent.name': browser.name,\n 'user_agent.version': browser.version,\n 'user_agent.os.name': osName,\n };\n};\n\n// trailing debounce:windowMs 静默期内的多次触发只执行最后一次。\n// 适合 resize 这类\"用户停下来后才需要拿到最终值\"的场景。\nconst createDebounce = (fn: () => void, windowMs: number) => {\n let timer: number | undefined;\n return () => {\n if (timer !== undefined) {\n window.clearTimeout(timer);\n }\n timer = window.setTimeout(() => {\n timer = undefined;\n fn();\n }, windowMs);\n };\n};\n\n/**\n * 设备元数据采集插件,把\"设备级\"信息写入 SDK 的 runtimeAttributes,\n * 让所有 log/span 都自动带上这些维度。\n *\n * 上报字段:\n * - `device.id`:设备级永久标识,存于 localStorage(默认 key 复用旧 session 插件以保留现网用户)。\n * - `device.memory` / `device.cpu_cores` / `device.platform` / `device.mobile`:硬件能力快照(运行期不变)。\n * - `browser.viewport.*` / `browser.screen.*`:视口与屏幕尺寸。\n * - `network.effective_type` / `network.connection_type` / `network.downlink` / `network.rtt` / `network.save_data`:\n * 网络信息。\n *\n * 实时更新:\n * - viewport 监听 `resize` / `orientationchange`,并经 250ms debounce 合并,避免拖窗口时高频触发;\n * 静默期结束后用最终值刷新 runtimeAttributes。\n * - network 监听 `connection.change`(本身低频,不需要 throttle)。\n * - 二者只更新 in-memory 的 runtimeAttributes,不会主动产生新的 span/log,cardinality 安全;\n * 但更新后续生成的 log/span 会带最新值。\n *\n * 与 session 插件的关系:device 跨会话持久(终身),session 有过期与续期(连续访问语义)。\n */\nexport const createDevicePlugin = (options: BkOTDevicePluginOptions = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const storageKey = options.storageKey ?? DEFAULT_DEVICE_STORAGE_KEY;\n\n return {\n name: 'device',\n init(context) {\n const deviceId = typeof window === 'undefined' ? createRandomId('device') : readDeviceId(storageKey);\n\n context.setRuntimeAttributes({\n 'device.id': deviceId,\n ...getHardwareAttributes(),\n ...getViewportAttributes(),\n ...getNetworkAttributes(),\n });\n\n if (typeof window === 'undefined') {\n return;\n }\n\n const refreshViewport = createDebounce(() => {\n context.setRuntimeAttributes(getViewportAttributes());\n }, VIEWPORT_DEBOUNCE_MS);\n window.addEventListener('resize', refreshViewport);\n window.addEventListener('orientationchange', refreshViewport);\n teardownCallbacks.push(\n () => window.removeEventListener('resize', refreshViewport),\n () => window.removeEventListener('orientationchange', refreshViewport),\n );\n\n const connection = getConnection();\n // NetworkInformation 实现了 EventTarget;旧浏览器或某些 polyfill 可能没有 addEventListener,做能力检测\n if (connection && typeof connection.addEventListener === 'function') {\n const refreshNetwork = () => {\n context.setRuntimeAttributes(getNetworkAttributes());\n };\n connection.addEventListener('change', refreshNetwork);\n teardownCallbacks.push(() => {\n connection.removeEventListener?.('change', refreshNetwork);\n });\n }\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 浏览器错误采集插件,负责捕获 JS 运行时错误、未处理的 Promise rejection、资源加载失败三类错误。\n *\n * 上报策略:\n * - 错误分析主数据统一使用 `span_type=error` 的 error span。\n * - 不再为同一个错误额外 emit error log,避免后端错误数按 span/log 混算时重复。\n *\n * 节流策略:\n * - 以 source + message + stack 前 256 字符做 djb2 hash 作为 fingerprint。\n * - 同 fingerprint 在 windowMs(默认 60s)内最多上报 maxPerWindow(默认 5)条;超过的会被丢弃。\n * - 每条上报的 attribute 都带 `error.window_count`,反映当前窗口内同 fingerprint 的总触发次数(含被丢弃次数),\n * 用于下游识别错误风暴。\n *\n * 关键 attribute:\n * - `error.source`:错误来源,取值 `window.error` / `unhandledrejection` / `resource`。\n * - `exception.fingerprint`:节流分组用的指纹 hash,可用于聚合同类错误。\n * - `error.window_count`:当前节流窗口内同指纹的总触发次数。\n * - `error.cross_origin`:仅在跨域脚本错误(\"Script error.\")时为 true,便于过滤这类无 stack 噪声。\n * - 资源错误额外带 `url.full`(经 redactUrl 脱敏)与 `html.tag`。\n * - JS 错误额外带 `code.filepath` / `code.lineno` / `code.column`。\n */\n\nimport { SpanStatusCode } from '@opentelemetry/api';\n\nimport { createThrottle, hashString } from '../core/throttle';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\n\nexport interface BkOTErrorPluginOptions {\n // 同 hash 错误窗口内最多上报多少条,默认 5\n maxPerWindow?: number;\n // 节流窗口长度(ms),默认 60_000\n windowMs?: number;\n}\n\nconst DEFAULT_WINDOW_MS = 60_000;\nconst DEFAULT_MAX_PER_WINDOW = 5;\n\nconst positiveNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n\nconst getErrorMessage = (value: unknown) => {\n if (value instanceof Error) {\n return value.message;\n }\n if (typeof value === 'string') {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n};\n\nconst getErrorStack = (value: unknown) => (value instanceof Error ? (value.stack ?? '') : '');\n\nconst getMessageShort = (message: string) => message.slice(0, 80);\n\nconst getStackTopFrame = (stack: string) =>\n stack\n .split('\\n')\n .find(line => line.trim().startsWith('at '))\n ?.trim() ?? '';\n\nconst getErrorSubtype = (source: string) => {\n if (source === 'unhandledrejection') return 'promise';\n if (source === 'resource') return 'resource_load';\n return 'js';\n};\n\ninterface EmitErrorOptions {\n context: BkOTRuntimeContext;\n error: unknown;\n exceptionType?: string;\n extra?: Record<string, unknown>;\n source: string;\n spanName: string;\n throttle: ReturnType<typeof createThrottle>;\n}\n\nconst emitError = ({ context, error, exceptionType, extra = {}, source, spanName, throttle }: EmitErrorOptions) => {\n const message = getErrorMessage(error);\n const stack = getErrorStack(error);\n const throttleKey = hashString(`${source}|${message}|${stack.slice(0, 256)}`);\n\n const throttleResult = throttle.allow(throttleKey);\n if (!throttleResult.allowed) {\n return;\n }\n\n const exceptionLike = error instanceof Error ? error : { message, name: exceptionType ?? source };\n const attributes = {\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getErrorAttributes(),\n ...buildRumSpanAttributes({\n errorType: getErrorSubtype(source),\n eventLabel: '错误',\n result: RUM_SPAN_RESULT.error,\n spanSubtype: getErrorSubtype(source),\n spanType: RUM_SPAN_TYPE.error,\n targetLabel: error instanceof Error ? error.name : (exceptionType ?? source),\n }),\n 'exception.message': message,\n [RUM_ATTRIBUTES.exceptionMessageShort]: getMessageShort(message),\n 'exception.stacktrace': stack,\n [RUM_ATTRIBUTES.exceptionStackTopFrame]: getStackTopFrame(stack),\n 'exception.type': error instanceof Error ? error.name : (exceptionType ?? source),\n [RUM_ATTRIBUTES.exceptionFingerprint]: throttleKey,\n 'error.handled': false,\n 'error.source': source,\n // 当前窗口内同 fingerprint 的总触发次数(含被节流丢弃的次数),用于下游识别错误风暴\n 'error.window_count': throttleResult.count,\n ...extra,\n };\n // 错误分析统一沉淀为 error span;若当前存在 active context,OTel 会自动建立父子关系。\n const errorSpan = context.startSpan(spanName, attributes);\n errorSpan.recordException(exceptionLike);\n errorSpan.setStatus({ code: SpanStatusCode.ERROR, message });\n errorSpan.end();\n};\n\nexport const createErrorPlugin = (options: BkOTErrorPluginOptions = {}): BkOTPlugin => {\n const listeners: Array<() => void> = [];\n const windowMs = positiveNumber(options.windowMs, DEFAULT_WINDOW_MS);\n const maxPerWindow = positiveNumber(options.maxPerWindow, DEFAULT_MAX_PER_WINDOW);\n\n return {\n name: 'error',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const throttle = createThrottle(windowMs, maxPerWindow);\n\n const onError = (event: ErrorEvent) => {\n // 跨域脚本错误浏览器会屏蔽 stack/filename,message 通常是 \"Script error.\"\n const isCrossOrigin = !event.error && !event.filename && event.message === 'Script error.';\n emitError({\n context,\n throttle,\n spanName: 'browser.error',\n source: 'window.error',\n error: event.error ?? event.message,\n extra: {\n 'code.filepath': event.filename,\n 'code.lineno': event.lineno,\n 'code.column': event.colno,\n ...(isCrossOrigin ? { 'error.cross_origin': true } : {}),\n },\n });\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n emitError({\n context,\n throttle,\n spanName: 'browser.unhandledrejection',\n source: 'unhandledrejection',\n error: event.reason,\n exceptionType: event.reason instanceof Error ? event.reason.name : 'UnhandledRejection',\n });\n };\n const onResourceError = (event: Event) => {\n // JS 运行时错误是 ErrorEvent,已由 bubble 阶段的 onError 处理;这里只关心资源加载失败\n if (event instanceof ErrorEvent) return;\n const target = event.target;\n if (!(target instanceof Element)) return;\n const elementWithUrl = target as Element & { href?: string; src?: string };\n const resourceUrl = elementWithUrl.src || elementWithUrl.href || (target as HTMLMediaElement).currentSrc || '';\n emitError({\n context,\n throttle,\n spanName: 'browser.resource_error',\n source: 'resource',\n error: new Error(`Resource load failed: ${target.tagName.toLowerCase()} ${resourceUrl}`),\n exceptionType: 'ResourceError',\n extra: {\n 'url.full': context.config.hooks.redactUrl(resourceUrl),\n 'html.tag': target.tagName,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n window.addEventListener('error', onResourceError, true);\n listeners.push(\n () => window.removeEventListener('error', onError),\n () => window.removeEventListener('unhandledrejection', onUnhandledRejection),\n () => window.removeEventListener('error', onResourceError, true),\n );\n },\n shutdown() {\n while (listeners.length) {\n listeners.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * HTTP body 采集插件,通过 monkey-patch `window.fetch` 与 `XMLHttpRequest.prototype` 三个方法\n * 在请求出错(抛错或 4xx+)时上报请求/响应 body,便于线上问题诊断。\n *\n * 上报策略:\n * - 仅在 `isError` 时(fetch reject 或 status >= 400)才把 body 写入 attribute;\n * 成功请求只上报 method/url/duration/status 等元数据。\n * - 所有 body 上报前先通过 `redact(payload)` 自定义函数脱敏,避免泄漏敏感字段。\n * - body 大小超过 `maxBodySize`(默认 10KB)会截断,并通过 `http_body.*.truncated` 标记。\n *\n * 内存保护:\n * - response 在读取前通过 `content-length` 预判,超过 `maxBodySize * 10` 的响应直接放弃 body 内容\n * (仍上报 truncated=true 与元数据),避免把超大响应整体吃进内存再截断。\n *\n * XHR meta 存储:\n * - 使用模块级 WeakMap 关联 XHR 实例与 meta(method / url / startTime / requestHeaders / requestPromise / aborted),\n * 避免在 XMLHttpRequest 接口上挂全局类型扩展,且实例 GC 时自动释放。\n *\n * 多重 patch 兼容:\n * - shutdown 时仅在 `window.fetch` / XHR prototype 仍是自己 patch 的版本时才回退,\n * 避免抹掉后来者(如 axios、其它 RUM SDK)的 patch。\n *\n * 已知限制:\n * - fetch/XHR 的 request body 仅在错误路径懒序列化;高频错误场景仍需注意 body 处理成本。\n * - 与 OTel 官方 fetch instrumentation 共存时会有两层 patch,行为可叠加但 fetch 调用栈会更深。\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\n\nimport { shouldIgnoreUrl } from '../core/url';\nimport { buildRumHttpSpanAttributes } from '../schema/span';\n\nimport type { BkOTHttpBodyConfig, BkOTHttpBodyRedactPayload } from '../core/config';\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\nimport type { Attributes } from '@opentelemetry/api';\n\ninterface BodySnapshot {\n body: string;\n contentType?: string;\n truncated: boolean;\n}\n\ninterface ReportHttpBodyOptions {\n config: Required<BkOTHttpBodyConfig>;\n context: BkOTRuntimeContext;\n duration: number;\n error?: unknown;\n method: string;\n request?: BodySnapshot;\n response?: BodySnapshot;\n status?: number;\n subtype: 'fetch' | 'xhr';\n url: string;\n}\n\ninterface XhrRequestMeta {\n aborted?: boolean;\n method: string;\n networkError?: boolean;\n requestBody?: Document | null | XMLHttpRequestBodyInit;\n requestContentType?: string;\n requestHeaders?: Record<string, string>;\n startTime: number;\n url: string;\n}\n\nconst HTTP_BODY_MAX_SIZE = 10 * 1024;\n// 响应 content-length 超过 maxBodySize * 此倍数 时直接放弃读 body,避免把超大响应整体加载到内存\nconst RESPONSE_BODY_SIZE_GUARD_FACTOR = 10;\nconst identityRedactHttpBody = (payload: BkOTHttpBodyRedactPayload) => payload.body;\n\nconst positiveNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n\n// WeakMap 替代 XMLHttpRequest 上的实例字段:避免污染全局类型,且实例 GC 时自动释放 meta\nconst xhrMetaMap = new WeakMap<XMLHttpRequest, XhrRequestMeta>();\n\ntype HttpBodyInput = BodyInit | Document | null | undefined;\n\nconst truncateBody = (body: string, maxBodySize: number): Pick<BodySnapshot, 'body' | 'truncated'> => {\n if (body.length <= maxBodySize) {\n return {\n body,\n truncated: false,\n };\n }\n return {\n body: body.slice(0, maxBodySize),\n truncated: true,\n };\n};\n\nconst getHeaderValue = (headers: HeadersInit | undefined, key: string) => {\n if (!headers) {\n return undefined;\n }\n if (headers instanceof Headers) {\n return headers.get(key) || undefined;\n }\n const normalizedKey = key.toLowerCase();\n if (Array.isArray(headers)) {\n return headers.find(([itemKey]) => itemKey.toLowerCase() === normalizedKey)?.[1];\n }\n const matchedKey = Object.keys(headers).find(item => item.toLowerCase() === normalizedKey);\n return matchedKey ? headers[matchedKey] : undefined;\n};\n\nconst stringifyFormData = (body: FormData) => {\n const entries: Record<string, string> = {};\n body.forEach((value, key) => {\n entries[key] =\n value instanceof File ? `[File name=${value.name} type=${value.type || 'unknown'} size=${value.size}]` : value;\n });\n return JSON.stringify(entries);\n};\n\nconst stringifyBody = async (body: HttpBodyInput): Promise<string> => {\n if (body == null) {\n return '';\n }\n if (typeof body === 'string') {\n return body;\n }\n if (body instanceof URLSearchParams) {\n return body.toString();\n }\n if (body instanceof FormData) {\n return stringifyFormData(body);\n }\n if (body instanceof Blob) {\n return body.text();\n }\n if (body instanceof ArrayBuffer) {\n return new TextDecoder().decode(body);\n }\n if (ArrayBuffer.isView(body)) {\n return new TextDecoder().decode(body);\n }\n if (body instanceof Document) {\n return new XMLSerializer().serializeToString(body);\n }\n return String(body);\n};\n\nconst createBodySnapshot = async (\n body: HttpBodyInput,\n maxBodySize: number,\n contentType?: string,\n): Promise<BodySnapshot | undefined> => {\n const rawBody = await stringifyBody(body);\n if (!rawBody) {\n return undefined;\n }\n return {\n ...truncateBody(rawBody, maxBodySize),\n contentType,\n };\n};\n\nconst safeCreateBodySnapshot = async (body: HttpBodyInput, maxBodySize: number, contentType?: string) => {\n try {\n return await createBodySnapshot(body, maxBodySize, contentType);\n } catch {\n return undefined;\n }\n};\n\nconst getFetchInputUrl = (input: RequestInfo | URL) => {\n if (input instanceof Request) {\n return input.url;\n }\n return String(input);\n};\n\nconst getFetchMethod = (input: RequestInfo | URL, init?: RequestInit) => {\n if (init?.method) {\n return init.method;\n }\n if (input instanceof Request) {\n return input.method;\n }\n return 'GET';\n};\n\ntype BodySnapshotReader = () => Promise<BodySnapshot | undefined>;\n\nconst getFetchRequestBodyReader = (\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n maxBodySize: number,\n): BodySnapshotReader => {\n if (init?.body) {\n return () =>\n safeCreateBodySnapshot(\n init.body,\n maxBodySize,\n getHeaderValue(init.headers as Headers | undefined, 'content-type'),\n );\n }\n if (input instanceof Request) {\n // 为了极致性能,不为所有 Request 预 clone body;接入方如需 body 诊断建议通过 init.body 发起请求。\n return () => Promise.resolve(undefined);\n }\n return () => Promise.resolve(undefined);\n};\n\nconst getResponseBody = async (response: Response, maxBodySize: number) => {\n try {\n const contentType = response.headers.get('content-type') || undefined;\n // 用 content-length 预判,超出阈值直接降级为 truncated 占位,避免吃下整个超大响应\n const contentLengthHeader = response.headers.get('content-length');\n if (contentLengthHeader) {\n const contentLength = Number(contentLengthHeader);\n if (Number.isFinite(contentLength) && contentLength > maxBodySize * RESPONSE_BODY_SIZE_GUARD_FACTOR) {\n return {\n body: '',\n contentType,\n truncated: true,\n };\n }\n }\n const clonedResponse = response.clone();\n return safeCreateBodySnapshot(clonedResponse.body ? await clonedResponse.text() : '', maxBodySize, contentType);\n } catch {\n return undefined;\n }\n};\n\nconst getXhrResponseBody = (xhr: XMLHttpRequest, maxBodySize: number): BodySnapshot | undefined => {\n if (xhr.responseType === '' || xhr.responseType === 'text') {\n return {\n ...truncateBody(xhr.responseText || '', maxBodySize),\n contentType: xhr.getResponseHeader('content-type') || undefined,\n };\n }\n if (xhr.responseType === 'json') {\n return {\n ...truncateBody(JSON.stringify(xhr.response), maxBodySize),\n contentType: xhr.getResponseHeader('content-type') || undefined,\n };\n }\n return undefined;\n};\n\nconst redactBody = (\n config: Required<BkOTHttpBodyConfig>,\n payload: Omit<BkOTHttpBodyRedactPayload, 'body' | 'truncated'>,\n snapshot?: BodySnapshot,\n) => {\n if (!snapshot) {\n return undefined;\n }\n return config.redact({\n ...payload,\n body: snapshot.body,\n truncated: snapshot.truncated,\n });\n};\n\nconst reportHttpBody = ({\n config,\n context,\n duration,\n error,\n method,\n request,\n response,\n status,\n subtype,\n url,\n}: ReportHttpBodyOptions) => {\n const isError = Boolean(error) || (typeof status === 'number' && status >= 400);\n const normalizedMethod = method.toUpperCase();\n const requestBody = isError\n ? redactBody(\n config,\n {\n contentType: request?.contentType,\n method: normalizedMethod,\n status,\n type: 'request',\n url,\n },\n request,\n )\n : undefined;\n const responseBody = isError\n ? redactBody(\n config,\n {\n contentType: response?.contentType,\n method: normalizedMethod,\n status,\n type: 'response',\n url,\n },\n response,\n )\n : undefined;\n const attributes: Attributes = {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumHttpSpanAttributes({\n duration,\n method: normalizedMethod,\n statusCode: status,\n subtype,\n url: context.config.hooks.redactUrl(url),\n urlPathTemplate: context.config.hooks.getViewPathGroup(url),\n }),\n 'http.duration': duration,\n 'http.request.method': normalizedMethod,\n 'url.full': context.config.hooks.redactUrl(url),\n };\n\n if (typeof status === 'number') {\n attributes['http.response.status_code'] = status;\n }\n if (requestBody != null) {\n attributes['http.request.body'] = requestBody;\n }\n if (responseBody != null) {\n attributes['http.response.body'] = responseBody;\n }\n if (isError) {\n attributes['http_body.request.truncated'] = request?.truncated ?? false;\n attributes['http_body.response.truncated'] = response?.truncated ?? false;\n }\n if (error instanceof Error) {\n attributes['exception.message'] = error.message;\n attributes['exception.type'] = error.name;\n attributes['exception.stacktrace'] = error.stack ?? '';\n }\n\n context.emitLog({\n severityNumber: isError ? SeverityNumber.ERROR : SeverityNumber.INFO,\n severityText: isError ? 'ERROR' : 'INFO',\n body: isError ? 'HTTP request completed with error' : 'HTTP request completed',\n attributes,\n });\n};\n\nexport const createHttpBodyPlugin = (options: BkOTHttpBodyConfig = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const config: Required<BkOTHttpBodyConfig> = {\n maxBodySize: positiveNumber(options.maxBodySize, HTTP_BODY_MAX_SIZE),\n redact: options.redact ?? identityRedactHttpBody,\n };\n\n return {\n name: 'http-body',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const { maxBodySize } = config;\n\n // ===== fetch patch =====\n const originalFetch = window.fetch;\n if (typeof originalFetch === 'function') {\n const patchedFetch: typeof window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\n const url = getFetchInputUrl(input);\n if (shouldIgnoreUrl(context.config, url)) {\n return originalFetch(input, init);\n }\n const method = getFetchMethod(input, init);\n const getRequestBody = getFetchRequestBodyReader(input, init, maxBodySize);\n const startTime = performance.now();\n try {\n const response = await originalFetch(input, init);\n const duration = performance.now() - startTime;\n const isError = response.status >= 400;\n const request = isError ? await getRequestBody() : undefined;\n const responseBody = isError ? await getResponseBody(response, maxBodySize) : undefined;\n reportHttpBody({\n config,\n context,\n duration,\n method,\n request,\n response: responseBody,\n status: response.status,\n subtype: 'fetch',\n url,\n });\n return response;\n } catch (error) {\n const request = await getRequestBody();\n reportHttpBody({\n config,\n context,\n duration: performance.now() - startTime,\n error,\n method,\n request,\n subtype: 'fetch',\n url,\n });\n throw error;\n }\n };\n window.fetch = patchedFetch;\n teardownCallbacks.push(() => {\n // 仅当 window.fetch 仍是我们 patch 的版本时才回退,避免抹掉后来者(如 axios、其它 RUM SDK)的 patch\n if (window.fetch === patchedFetch) {\n window.fetch = originalFetch;\n }\n });\n }\n\n // ===== XMLHttpRequest patch =====\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;\n\n const patchedOpen = function open(\n this: XMLHttpRequest,\n method: string,\n url: string | URL,\n ...args: [async?: boolean, username?: null | string, password?: null | string]\n ) {\n xhrMetaMap.set(this, {\n method,\n startTime: performance.now(),\n url: String(url),\n });\n return originalOpen.call(this, method, url, args[0] ?? true, args[1], args[2]);\n };\n\n const patchedSetRequestHeader = function setRequestHeader(this: XMLHttpRequest, name: string, value: string) {\n const meta = xhrMetaMap.get(this);\n if (meta) {\n // XHR 规范规定 header name 大小写不敏感、同名重复调用应追加(comma-separated)\n const lowerName = name.toLowerCase();\n const headers = meta.requestHeaders ?? {};\n const prev = headers[lowerName];\n headers[lowerName] = prev ? `${prev}, ${value}` : value;\n meta.requestHeaders = headers;\n }\n return originalSetRequestHeader.call(this, name, value);\n };\n\n const patchedSend = function send(this: XMLHttpRequest, body?: Document | null | XMLHttpRequestBodyInit) {\n const meta = xhrMetaMap.get(this);\n if (meta) {\n // 成功请求不解析 body;仅保留引用,错误路径再懒序列化。\n meta.requestBody = body;\n meta.requestContentType = meta.requestHeaders?.['content-type'];\n }\n // 区分用户主动 abort 与真实网络错误:前者不上报,后者按错误上报\n this.addEventListener(\n 'abort',\n () => {\n const m = xhrMetaMap.get(this);\n if (m) m.aborted = true;\n },\n { once: true },\n );\n this.addEventListener(\n 'error',\n () => {\n const m = xhrMetaMap.get(this);\n if (m) m.networkError = true;\n },\n { once: true },\n );\n this.addEventListener(\n 'loadend',\n () => {\n // addEventListener 期望 void 返回值,这里用 IIFE 包住异步逻辑,避免把 Promise 直接当作 listener 返回\n void (async () => {\n const m = xhrMetaMap.get(this);\n if (!m || shouldIgnoreUrl(context.config, m.url) || m.aborted) {\n return;\n }\n const isXhrError = m.networkError || this.status >= 400;\n const responseBody = isXhrError ? getXhrResponseBody(this, maxBodySize) : undefined;\n const request = isXhrError\n ? await safeCreateBodySnapshot(m.requestBody, maxBodySize, m.requestContentType)\n : undefined;\n reportHttpBody({\n config,\n context,\n duration: performance.now() - m.startTime,\n // status === 0 且非 abort 时构造一个占位 Error,让下游沿用统一的 isError 路径上报 body\n error: m.networkError ? new Error('XHR network error') : undefined,\n method: m.method,\n request,\n response: responseBody,\n status: this.status,\n subtype: 'xhr',\n url: m.url,\n });\n })();\n },\n { once: true },\n );\n return originalSend.call(this, body);\n };\n\n XMLHttpRequest.prototype.open = patchedOpen;\n XMLHttpRequest.prototype.setRequestHeader = patchedSetRequestHeader;\n XMLHttpRequest.prototype.send = patchedSend;\n teardownCallbacks.push(() => {\n // 同 fetch:仅当原型方法仍是自己 patch 的版本时才回退,避免破坏后续 patch 的库\n if (XMLHttpRequest.prototype.open === patchedOpen) {\n XMLHttpRequest.prototype.open = originalOpen;\n }\n if (XMLHttpRequest.prototype.setRequestHeader === patchedSetRequestHeader) {\n XMLHttpRequest.prototype.setRequestHeader = originalSetRequestHeader;\n }\n if (XMLHttpRequest.prototype.send === patchedSend) {\n XMLHttpRequest.prototype.send = originalSend;\n }\n });\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport interface BkOTLongTaskPluginOptions {\n // 任务时长阈值(ms),低于该值的 longtask 不上报,默认 50\n threshold?: number;\n}\n\nconst DEFAULT_THRESHOLD_MS = 50;\n\nconst nonNegativeNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;\n\ninterface LongTaskAttribution {\n containerId?: string;\n containerName?: string;\n containerSrc?: string;\n containerType?: string;\n name?: string;\n}\n\ninterface LongTaskEntry extends PerformanceEntry {\n attribution?: LongTaskAttribution[];\n}\n\n/**\n * Long Task 监控插件,通过 PerformanceObserver 捕获主线程上 >= threshold(默认 50ms)的长任务,\n * 上报为 metric(counter + duration histogram),用于评估页面卡顿趋势。\n *\n * 使用建议:\n * - 默认关闭,建议在性能敏感模块按需开启。\n * - PerformanceObserver 使用 `buffered: true`,能拿到 observer 创建之前已发生的 longtask,避免漏掉早期阶段。\n *\n * 兼容性:\n * - 仅在 PerformanceObserver 与 `supportedEntryTypes` 包含 `longtask` 的浏览器上启用,否则 init 直接 return;\n * Firefox 当前不支持 longtask,会自动跳过。\n */\nexport const createLongTaskPlugin = (options: BkOTLongTaskPluginOptions = {}): BkOTPlugin => {\n let observer: PerformanceObserver | undefined;\n const threshold = nonNegativeNumber(options.threshold, DEFAULT_THRESHOLD_MS);\n\n return {\n name: 'long-task',\n init(context) {\n if (typeof PerformanceObserver === 'undefined') {\n return;\n }\n if (!PerformanceObserver.supportedEntryTypes?.includes('longtask')) {\n return;\n }\n\n const counter = context.meter.createCounter('browser.long_task.count', {\n description: 'Number of long tasks observed (duration >= threshold)',\n });\n const durationHistogram = context.meter.createHistogram('browser.long_task.duration', {\n unit: 'ms',\n description: 'Duration of long tasks',\n });\n\n try {\n observer = new PerformanceObserver(list => {\n // 一次回调可能带多个 entry,把不随 entry 变化的公共属性提到循环外只算一次\n const baseDimensions = context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n });\n for (const entry of list.getEntries() as LongTaskEntry[]) {\n if (entry.duration < threshold) {\n continue;\n }\n const attribution = entry.attribution?.[0];\n const dimensions = {\n ...baseDimensions,\n [RUM_ATTRIBUTES.longtaskAttributionScript]: attribution?.containerSrc ?? attribution?.name ?? 'unknown',\n };\n const result = entry.duration > 100 ? RUM_SPAN_RESULT.warning : RUM_SPAN_RESULT.success;\n const span = context.startSpan(\n 'browser.long_task',\n {\n ...baseDimensions,\n ...buildRumSpanAttributes({\n duration: entry.duration,\n errorType: result === RUM_SPAN_RESULT.warning ? 'longtask_blocking' : 'none',\n eventLabel: '长任务',\n result,\n spanSubtype: attribution?.name ?? 'unknown',\n spanType: RUM_SPAN_TYPE.longtask,\n targetLabel: attribution?.containerSrc ?? attribution?.containerName ?? attribution?.name ?? 'unknown',\n targetValue: entry.duration,\n }),\n [RUM_ATTRIBUTES.longtaskBlockingDuration]: entry.duration,\n [RUM_ATTRIBUTES.longtaskAttributionScript]: attribution?.containerSrc ?? attribution?.name ?? 'unknown',\n },\n { startTime: performance.timeOrigin + entry.startTime },\n );\n span.end(performance.timeOrigin + entry.startTime + entry.duration);\n counter.add(1, dimensions);\n durationHistogram.record(entry.duration, dimensions);\n }\n });\n observer.observe({ type: 'longtask', buffered: true });\n } catch {\n observer = undefined;\n }\n },\n shutdown() {\n observer?.disconnect();\n observer = undefined;\n },\n };\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nexport interface RouteChangeEvent {\n fromUrl: string;\n source: RouteChangeSource;\n toUrl: string;\n}\n\nexport type RouteChangeSource = 'hashchange' | 'popstate' | 'pushState' | 'replaceState';\n\ntype RouteChangeHandler = (event: RouteChangeEvent) => void;\n\nconst subscribers = new Set<RouteChangeHandler>();\n\nlet lastUrl = '';\nlet originalPushState: typeof history.pushState | undefined;\nlet originalReplaceState: typeof history.replaceState | undefined;\nlet patched = false;\n\nconst getCurrentUrl = () => (typeof location === 'undefined' ? '' : location.href);\n\nconst emitRouteChange = (source: RouteChangeSource, fromUrl: string) => {\n const toUrl = getCurrentUrl();\n lastUrl = toUrl;\n const event: RouteChangeEvent = { fromUrl, source, toUrl };\n\n for (const subscriber of Array.from(subscribers)) {\n subscriber(event);\n }\n};\n\nconst onPopState = () => {\n emitRouteChange('popstate', lastUrl || getCurrentUrl());\n};\n\nconst onHashChange = () => {\n emitRouteChange('hashchange', lastUrl || getCurrentUrl());\n};\n\nconst patchHistory = () => {\n if (patched || typeof window === 'undefined' || typeof history === 'undefined') {\n return;\n }\n\n patched = true;\n lastUrl = getCurrentUrl();\n originalPushState = history.pushState;\n originalReplaceState = history.replaceState;\n\n history.pushState = function patchedPushState(this: History, data: any, title: string, url?: null | string | URL) {\n const fromUrl = lastUrl || getCurrentUrl();\n const result = originalPushState?.apply(this, [data, title, url]);\n emitRouteChange('pushState', fromUrl);\n return result;\n };\n history.replaceState = function patchedReplaceState(\n this: History,\n data: any,\n title: string,\n url?: null | string | URL,\n ) {\n const fromUrl = lastUrl || getCurrentUrl();\n const result = originalReplaceState?.apply(this, [data, title, url]);\n emitRouteChange('replaceState', fromUrl);\n return result;\n };\n window.addEventListener('popstate', onPopState);\n window.addEventListener('hashchange', onHashChange);\n};\n\nconst restoreHistory = () => {\n if (!patched || typeof window === 'undefined' || typeof history === 'undefined') {\n return;\n }\n\n if (originalPushState) {\n history.pushState = originalPushState;\n }\n if (originalReplaceState) {\n history.replaceState = originalReplaceState;\n }\n window.removeEventListener('popstate', onPopState);\n window.removeEventListener('hashchange', onHashChange);\n originalPushState = undefined;\n originalReplaceState = undefined;\n patched = false;\n lastUrl = '';\n};\n\nexport const subscribeRouteChange = (handler: RouteChangeHandler) => {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return () => {};\n }\n\n patchHistory();\n subscribers.add(handler);\n\n return () => {\n subscribers.delete(handler);\n if (subscribers.size === 0) {\n restoreHistory();\n }\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Page View 插件\n *\n * 监听浏览器 URL 变化并上报 page_view 事件,覆盖:\n * - 初始页面加载(source: 'load')\n * - SPA 路由切换:history.pushState / replaceState / popstate / hashchange\n *\n * 上报形态:\n * - 使用 span(browser.page_view)而非 log,立即 end()。\n * 这是有意的设计取舍:page_view 是一次瞬时事件并无 duration,但用 span 表达\n * 能让它成为 trace 视图的天然\"根节点\",与后续 fetch / xhr / user-interaction\n * span 在同一时间线上对齐,方便排查\"某次访问触发了哪些请求\"。\n * 代价是部分后端会把 0ms span 显示成奇怪的细线,可接受。\n *\n * 同 URL 去重:\n * 路由库(vue-router、react-router)常常会对相同 URL 重复 push(query/hash 微调或\n * 规范化),插件按完整 URL 字符串去重,避免重复上报。\n *\n * 关键属性:\n * - view.url:当前页面 URL(经 redactUrl 脱敏)\n * - url.previous:跳转前的 URL(来自 route-observer 的 fromUrl,初始 load 为空字符串)\n * - document.referrer:仅 'load' 事件携带\n * - event.source:触发源(load / pushState / replaceState / popstate / hashchange)\n */\n\nimport { createRandomId } from '../core/id';\nimport { subscribeRouteChange } from '../core/route-observer';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\nimport type { Span } from '@opentelemetry/api';\n\nconst getCurrentUrl = () => {\n if (typeof location === 'undefined') {\n return '';\n }\n return location.href;\n};\n\nexport const createPageViewPlugin = (): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n\n return {\n name: 'page-view',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n // 同 URL 去重:路由库常常因 query/hash 微调反复 push 同一 URL,去重避免重复上报\n let lastUrl = '';\n let activeViewSpan: Span | undefined;\n\n const emitPageView = (source: string, previousUrl = lastUrl) => {\n const currentUrl = getCurrentUrl();\n if (currentUrl === lastUrl) {\n return;\n }\n activeViewSpan?.setAttribute('view.end_reason', source);\n activeViewSpan?.end();\n\n lastUrl = currentUrl;\n const viewId = createRandomId('view');\n const pathGroup = context.config.hooks.getViewPathGroup(currentUrl);\n const loadingType = source === 'load' ? 'initial_load' : 'route_change';\n const redactedCurrentUrl = context.config.hooks.redactUrl(currentUrl);\n const redactedPreviousUrl = context.config.hooks.redactUrl(previousUrl);\n\n context.setView({\n id: viewId,\n loadingType,\n url: redactedCurrentUrl,\n urlPathGroup: pathGroup,\n });\n\n // referrer 仅在初始 load 时携带,SPA 内路由切换由 url.previous 表达\n const isInitialLoad = source === 'load';\n const viewAttributes = {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: isInitialLoad ? '文档加载' : '路由切换',\n spanSubtype: isInitialLoad ? 'navigate' : source,\n spanType: isInitialLoad ? RUM_SPAN_TYPE.document : RUM_SPAN_TYPE.route,\n targetLabel: pathGroup,\n traceScene: isInitialLoad ? 'page_load' : 'route_change',\n }),\n [RUM_ATTRIBUTES.viewId]: viewId,\n [RUM_ATTRIBUTES.viewUrl]: redactedCurrentUrl,\n [RUM_ATTRIBUTES.viewUrlPathGroup]: pathGroup,\n [RUM_ATTRIBUTES.viewLoadingType]: loadingType,\n 'url.previous': redactedPreviousUrl,\n 'event.source': source,\n ...(isInitialLoad ? { 'document.referrer': context.config.hooks.redactUrl(document.referrer || '') } : {}),\n };\n activeViewSpan = context.startSpan('browser.view', viewAttributes);\n const span = context.startSpan('browser.page_view', {\n ...viewAttributes,\n });\n span.end();\n };\n\n const unsubscribe = subscribeRouteChange(event => {\n emitPageView(event.source, event.fromUrl);\n });\n emitPageView('load');\n\n const endActiveView = () => {\n activeViewSpan?.setAttribute('view.end_reason', 'shutdown');\n activeViewSpan?.end();\n activeViewSpan = undefined;\n };\n window.addEventListener('pagehide', endActiveView);\n\n teardownCallbacks.push(unsubscribe, () => window.removeEventListener('pagehide', endActiveView), endActiveView);\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { shouldIgnoreUrl } from '../core/url';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumResourceSpanAttributes } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\ntype ResourceTimingWithStatus = PerformanceResourceTiming & {\n responseStatus?: number;\n};\n\nconst getCacheHit = (entry: PerformanceResourceTiming) =>\n entry.transferSize === 0 && entry.decodedBodySize > 0 ? true : undefined;\n\nexport const createResourcePlugin = (): BkOTPlugin => {\n let observer: PerformanceObserver | undefined;\n const seen = new Set<string>();\n\n return {\n name: 'resource',\n init(context) {\n if (typeof PerformanceObserver === 'undefined') {\n return;\n }\n if (!PerformanceObserver.supportedEntryTypes?.includes('resource')) {\n return;\n }\n\n const emitEntry = (entry: ResourceTimingWithStatus) => {\n if (!entry.name || shouldIgnoreUrl(context.config, entry.name)) {\n return;\n }\n const dedupeKey = `${entry.name}|${entry.startTime}`;\n if (seen.has(dedupeKey)) {\n return;\n }\n seen.add(dedupeKey);\n\n const redactedUrl = context.config.hooks.redactUrl(entry.name);\n const duration = entry.duration;\n const span = context.startSpan(\n 'browser.resource',\n {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumResourceSpanAttributes({\n duration,\n initiatorType: entry.initiatorType || 'other',\n responseStatus: entry.responseStatus,\n transferSize: entry.transferSize,\n url: redactedUrl,\n urlPathTemplate: context.config.hooks.getViewPathGroup(entry.name),\n }),\n 'url.full': redactedUrl,\n [RUM_ATTRIBUTES.transferSize]: entry.transferSize,\n [RUM_ATTRIBUTES.cacheHit]: getCacheHit(entry),\n [RUM_ATTRIBUTES.nextHopProtocol]: entry.nextHopProtocol,\n initiator_type: entry.initiatorType || 'other',\n 'resource.encoded_body_size': entry.encodedBodySize,\n 'resource.decoded_body_size': entry.decodedBodySize,\n },\n { startTime: performance.timeOrigin + entry.startTime },\n );\n span.end(performance.timeOrigin + entry.startTime + duration);\n };\n\n try {\n observer = new PerformanceObserver(list => {\n for (const entry of list.getEntries() as ResourceTimingWithStatus[]) {\n emitEntry(entry);\n }\n });\n observer.observe({ type: 'resource', buffered: true });\n } catch {\n observer = undefined;\n }\n },\n shutdown() {\n observer?.disconnect();\n observer = undefined;\n seen.clear();\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Route Timing 插件\n *\n * 估算 SPA 路由切换的\"路由变更 → 渲染完成\"耗时。不依赖具体框架,作为通用兜底方案;\n * 如需更精确的耗时(含异步数据加载),业务可走自定义 plugin 在框架钩子内 startSpan/end。\n *\n * 估算策略:\n * 订阅 route-observer 的路由变化事件 → 记录起始时刻 → 等待 raf + raf + setTimeout(0)\n * 后视为\"渲染完成\"。双 raf 让浏览器至少完成一帧 paint,setTimeout 再覆盖大多数同步\n * 组件挂载的尾巴。\n *\n * 双轨上报:\n * - metric (histogram `browser.route_change.duration`):维度只带 page + source(不带 URL),\n * 适合做 P50 / P99 / 平均耗时的聚合统计,避免 URL 级高基数。\n * - span (`browser.route_change`):携带 url.full / url.previous,方便个例 trace 排查。\n * span 通过 startTime 选项回填到路由变更瞬间,end 时为 finalize 时刻,使 trace 视图里\n * 能看到正确的时间窗,与并发的 fetch / xhr span 在同一时间线上对齐。\n *\n * 已知局限:\n * - tab 切到后台:raf 会被浏览器节流甚至暂停,duration 会被严重高估。\n * 策略:finalize 时若 document.visibilityState === 'hidden' 直接丢弃这条数据,\n * 宁可少报也不污染 metric P99。\n * - 频繁路由切换:100ms 内连续 push A→B→C 时,每次切换都会启动独立 measure,\n * 早期记录的 duration 会被后续渲染拖长(含被覆盖路由的渲染时间)。当前实现\"全报\",\n * 保留所有切换记录,下游分析时需注意这个语义。\n * - 初始 page load:route-observer 不为初始加载触发事件,本插件不覆盖;\n * 首屏耗时由 navigation timing / web-vitals 插件负责。\n *\n * 生命周期:\n * shutdown 后通过 disposed 标志位拦截 pending 的 raf+setTimeout 回调,\n * 避免在已销毁的 provider 上 record/startSpan 造成数据逃逸。\n * raf/setTimeout 句柄不主动取消(成本低,回调内 early return 即可)。\n */\n\nimport { subscribeRouteChange } from '../core/route-observer';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport const createRouteTimingPlugin = (): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n let disposed = false;\n\n return {\n name: 'route-timing',\n init(context) {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return;\n }\n\n const histogram = context.meter.createHistogram('browser.route_change.duration', {\n unit: 'ms',\n description: 'Estimated SPA route change duration (route start to next idle)',\n });\n\n const measure = (source: string, fromUrl: string, toUrl: string) => {\n if (disposed) return;\n // 用 performance.now() 计算 duration,再换算成 epoch 毫秒作为 span 的 startTime,\n // 让 span 时间窗对齐真实的路由变更瞬间\n const startedAt = performance.now();\n const startedAtEpochMs = performance.timeOrigin + startedAt;\n\n const finalize = () => {\n if (disposed) return;\n // tab 切到后台时 raf 会暂停,回到前台触发的 finalize 拿到的 duration 会包含切走时间,\n // 直接丢弃这条数据,避免污染 metric 的 P99 / max\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n return;\n }\n const duration = performance.now() - startedAt;\n const result = duration > 500 ? RUM_SPAN_RESULT.warning : RUM_SPAN_RESULT.success;\n const dimensions = context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n 'route.change.source': source,\n });\n histogram.record(duration, dimensions);\n const span = context.startSpan(\n 'browser.route_change',\n {\n ...dimensions,\n ...buildRumSpanAttributes({\n duration,\n errorType: result === RUM_SPAN_RESULT.warning ? 'slow' : 'none',\n eventLabel: '路由切换',\n result,\n spanSubtype: source,\n spanType: RUM_SPAN_TYPE.route,\n targetLabel: context.config.hooks.getViewPathGroup(toUrl),\n targetValue: duration,\n traceScene: 'route_change',\n }),\n 'url.full': context.config.hooks.redactUrl(toUrl),\n 'url.previous': context.config.hooks.redactUrl(fromUrl),\n },\n { startTime: startedAtEpochMs },\n );\n span.end();\n };\n // 双 raf 等到下一帧渲染后,再补一个宏任务,覆盖大多数同步组件挂载场景\n requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(finalize, 0)));\n };\n\n const unsubscribe = subscribeRouteChange(event => {\n if (event.fromUrl === event.toUrl) {\n return;\n }\n measure(event.source, event.fromUrl, event.toUrl);\n });\n\n teardownCallbacks.push(unsubscribe);\n },\n shutdown() {\n disposed = true;\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Session 插件\n *\n * 维护会话级标识 `session.id`,反映用户的\"连续访问\"语义。与 device 插件互补:\n * - device.id:设备终身标识(localStorage 持久),跨会话不变\n * - session.id:本次连续会话标识,过期后轮换\n *\n * 过期策略(满足任一条件触发轮换):\n * - inactivityMs:用户不活跃超过 N ms,默认 30 分钟\n * - maxLifetimeMs:会话总时长超过 N ms(即使一直活跃),默认 24 小时\n *\n * 续期触发:\n * - 用户活动事件(click / keydown / scroll / mousemove / touchstart)throttle 60s 一次\n * 解决\"用户持续在前台浏览,visibility 不变化导致 inactivityMs 永远不刷新\"的语义问题\n * - visibilitychange → visible 时刷新一次,处理\"切回前台\"场景\n *\n * 跨 tab 行为(共享语义):\n * - 读取优先 localStorage(跨 tab 最新视角),fallback sessionStorage\n * - 写入双写 localStorage + sessionStorage\n * - 监听 storage 事件,其他 tab 写入时实时同步本 tab 的 session.id(不重复 emit 事件)\n * - 已知边界:跨 tab 短窗口内并发判过期可能产生不同 sessionId,下游分析需注意\n *\n * 会话事件(log):\n * - session.start:首次新建会话\n * - session.rotate:因 inactivityMs / maxLifetimeMs 触发轮换,含 session.previous_id 与 reason\n * - 跨 tab storage 同步过来的 sessionId 变化不再重复 emit(源 tab 已发)\n *\n * 默认 storageKey:bk_ot_session__<endpoint host hash>,避免同域多个独立应用共用 session\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\n\nimport { createRandomId } from '../core/id';\nimport { hashString } from '../core/throttle';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\n\nexport interface BkOTSessionPluginOptions {\n // 不活跃多久后视为新会话,默认 30 分钟\n inactivityMs?: number;\n // 会话最大持续时间,超过则强制轮换(即使用户一直活跃),默认 24 小时\n maxLifetimeMs?: number;\n // 自定义 storage key;不传则按 endpoint host 自动生成隔离后缀\n storageKey?: string;\n}\n\nconst DEFAULT_INACTIVITY_MS = 30 * 60 * 1000;\nconst DEFAULT_MAX_LIFETIME_MS = 24 * 60 * 60 * 1000;\nconst ACTIVITY_THROTTLE_MS = 60_000;\n// 允许 ts 在未来 5 秒内(容忍系统时钟微小回拨 / 多 tab 时钟差),超出则视为非法记录\nconst FUTURE_CLOCK_TOLERANCE_MS = 5_000;\nconst ACTIVITY_EVENTS: Array<keyof WindowEventMap> = ['click', 'keydown', 'scroll', 'mousemove', 'touchstart'];\n\nconst positiveNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n\ninterface ResolveResult {\n id: string;\n prevId?: string;\n reason?: SessionRotateReason;\n // true 表示本次是新建/轮换;false 表示复用既有会话\n rotated: boolean;\n}\n\ninterface SessionRecord {\n id: string;\n // 会话开始时间,用于 maxLifetimeMs 判定\n startedAt: number;\n // 上次活跃时间戳\n ts: number;\n}\n\ntype SessionRotateReason = 'inactivity' | 'init' | 'maxLifetime';\n\nconst isValidTs = (value: unknown, now: number): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 && value <= now + FUTURE_CLOCK_TOLERANCE_MS;\n\nconst parseRecord = (raw: null | string, now: number): null | SessionRecord => {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as SessionRecord;\n if (typeof parsed?.id === 'string' && isValidTs(parsed.ts, now) && isValidTs(parsed.startedAt, now)) {\n return parsed;\n }\n } catch {\n /* JSON 损坏视为无效记录,下方会重新生成 */\n }\n return null;\n};\n\nconst readRecord = (storageKey: string, now: number): null | SessionRecord => {\n if (typeof window === 'undefined') return null;\n // localStorage 优先:反映跨 tab 最新视角;同 tab 刷新场景下两者通常一致\n let raw: null | string = null;\n try {\n raw = window.localStorage.getItem(storageKey);\n } catch {\n /* storage 不可用时视为无记录 */\n }\n let record = parseRecord(raw, now);\n if (record) return record;\n try {\n raw = window.sessionStorage.getItem(storageKey);\n } catch {\n /* storage 不可用时视为无记录 */\n }\n record = parseRecord(raw, now);\n return record;\n};\n\nconst writeRecord = (storageKey: string, record: SessionRecord) => {\n if (typeof window === 'undefined') return;\n const serialized = JSON.stringify(record);\n // sessionStorage 与 localStorage 分别 try/catch:一个失败不影响另一个\n try {\n window.sessionStorage.setItem(storageKey, serialized);\n } catch {\n /* ignore */\n }\n try {\n window.localStorage.setItem(storageKey, serialized);\n } catch {\n /* ignore */\n }\n};\n\nconst resolveSessionId = (storageKey: string, inactivityMs: number, maxLifetimeMs: number): ResolveResult => {\n const now = Date.now();\n const existed = readRecord(storageKey, now);\n if (existed) {\n const inactive = now - existed.ts >= inactivityMs;\n const tooOld = now - existed.startedAt >= maxLifetimeMs;\n if (!inactive && !tooOld) {\n const refreshed: SessionRecord = { ...existed, ts: now };\n writeRecord(storageKey, refreshed);\n return { id: refreshed.id, rotated: false };\n }\n // inactive 与 tooOld 都成立时优先记录 inactivity(业务上更直观)\n const reason: SessionRotateReason = inactive ? 'inactivity' : 'maxLifetime';\n const rotated: SessionRecord = { id: createRandomId('session'), startedAt: now, ts: now };\n writeRecord(storageKey, rotated);\n return { id: rotated.id, prevId: existed.id, reason, rotated: true };\n }\n const created: SessionRecord = { id: createRandomId('session'), startedAt: now, ts: now };\n writeRecord(storageKey, created);\n return { id: created.id, reason: 'init', rotated: true };\n};\n\n// 默认 storageKey 带 endpoint host hash,避免同域多个独立应用共用 session\nconst buildDefaultStorageKey = (endpoint: string) => {\n let suffix = '';\n try {\n suffix = new URL(endpoint).host;\n } catch {\n suffix = endpoint;\n }\n return `bk_ot_session__${hashString(suffix || 'default')}`;\n};\n\nconst emitSessionEvent = (context: BkOTRuntimeContext, result: ResolveResult) => {\n if (!result.rotated || !result.reason) return;\n const now = Date.now();\n context.emitLog({\n severityNumber: SeverityNumber.INFO,\n severityText: 'INFO',\n body: result.reason === 'init' ? 'session.start' : 'session.rotate',\n attributes: {\n ...context.config.hooks.getPageAttributes(),\n [RUM_ATTRIBUTES.sessionHasReplay]: false,\n [RUM_ATTRIBUTES.sessionId]: result.id,\n [RUM_ATTRIBUTES.sessionStartTime]: now,\n 'session.rotate.reason': result.reason,\n ...(result.prevId ? { 'session.previous_id': result.prevId } : {}),\n },\n });\n};\n\nexport const createSessionPlugin = (options: BkOTSessionPluginOptions = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const inactivityMs = positiveNumber(options.inactivityMs, DEFAULT_INACTIVITY_MS);\n const maxLifetimeMs = positiveNumber(options.maxLifetimeMs, DEFAULT_MAX_LIFETIME_MS);\n\n return {\n name: 'session',\n init(context) {\n const storageKey = options.storageKey ?? buildDefaultStorageKey(context.config.transport.endpoint);\n\n // 当前进程持有的 sessionId 快照,用于:\n // 1. 判定 storage 同步事件是否真的带来变化(避免无谓 emit)\n // 2. 做 user activity throttle 时无需访问 storage\n let currentSessionId = '';\n\n const refresh = () => {\n if (typeof window === 'undefined') {\n // SSR 兜底:直接生成一个一次性 id,不持久化\n if (!currentSessionId) {\n currentSessionId = createRandomId('session');\n context.setRuntimeAttributes({\n [RUM_ATTRIBUTES.sessionHasReplay]: false,\n [RUM_ATTRIBUTES.sessionId]: currentSessionId,\n });\n }\n return;\n }\n const result = resolveSessionId(storageKey, inactivityMs, maxLifetimeMs);\n context.setRuntimeAttributes({\n [RUM_ATTRIBUTES.sessionHasReplay]: false,\n [RUM_ATTRIBUTES.sessionId]: result.id,\n });\n if (result.id !== currentSessionId) {\n currentSessionId = result.id;\n // 仅当 resolve 内部判定为新建/轮换时才 emit;复用既有会话静默\n emitSessionEvent(context, result);\n }\n };\n\n refresh();\n\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n // user activity 节流续期:解决\"持续 visible 时 inactivityMs 永不刷新\"的语义问题\n let lastTouchAt = Date.now();\n const touch = () => {\n const now = Date.now();\n if (now - lastTouchAt < ACTIVITY_THROTTLE_MS) return;\n lastTouchAt = now;\n refresh();\n };\n // capture: true 让事件在捕获阶段就被感知,不受业务 stopPropagation 影响\n const activityListenerOptions: AddEventListenerOptions = { capture: true, passive: true };\n ACTIVITY_EVENTS.forEach(evt => {\n window.addEventListener(evt, touch, activityListenerOptions);\n });\n teardownCallbacks.push(() => {\n ACTIVITY_EVENTS.forEach(evt => {\n window.removeEventListener(evt, touch, activityListenerOptions);\n });\n });\n\n // 切回前台时刷新一次,处理\"后台 N 分钟回来\"场景\n const onVisibilityChange = () => {\n if (document.visibilityState === 'visible') {\n refresh();\n }\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n teardownCallbacks.push(() => document.removeEventListener('visibilitychange', onVisibilityChange));\n\n // 跨 tab storage 同步:其他 tab 写入新 sessionId 时本 tab 实时跟进\n // 不调用 refresh / emitSessionEvent,避免 N tab 同时 emit N 条事件\n const onStorage = (event: StorageEvent) => {\n if (event.storageArea !== window.localStorage) return;\n if (event.key !== storageKey) return;\n if (!event.newValue) return;\n const now = Date.now();\n const parsed = parseRecord(event.newValue, now);\n if (!parsed || parsed.id === currentSessionId) return;\n currentSessionId = parsed.id;\n // 同步到当前 tab 的 sessionStorage,保持两个 storage 一致\n try {\n window.sessionStorage.setItem(storageKey, event.newValue);\n } catch {\n /* ignore */\n }\n context.setRuntimeAttributes({ [RUM_ATTRIBUTES.sessionId]: parsed.id });\n };\n window.addEventListener('storage', onStorage);\n teardownCallbacks.push(() => window.removeEventListener('storage', onStorage));\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Web Vitals 插件\n *\n * 基于 google/web-vitals 库(attribution 版本)采集 Core Web Vitals:\n * CLS / FCP / INP / LCP / TTFB\n *\n * 维度分层(关键设计):\n * - metric (histogram):仅带低基数维度(navigation.type / vital.name / vital.rating),\n * 适合做 P50 / P75 / P99 聚合统计,避免维度爆炸\n * - span (browser.web_vital):带 vital.id(唯一)、vital.value 与 attribution 详情\n * (LCP 元素 url / CLS shift target / INP interaction target 等高基数字段),\n * 方便按个例排查\"哪个元素拖累了 LCP\"\n *\n * Histogram 设计:\n * - browser.web_vital.cls:单位 '1'(无量纲),CLS 是累积分数,不能与 ms 混用\n * - browser.web_vital.duration:单位 'ms',FCP/INP/LCP/TTFB 共享,靠 vital.metric 维度区分\n *\n * Navigation Type:\n * 优先使用 metric.navigationType(web-vitals 内部计算,含 'back-forward-cache' 等准确值),\n * fallback 到 PerformanceNavigationTiming.type(注意 web-vitals 用连字符 'back-forward',\n * PerformanceNavigationTiming 用下划线 'back_forward',统一成连字符)。\n *\n * Page Lifecycle 行为(来自 web-vitals 库的设计):\n * - LCP / INP / CLS 默认仅在 pagehide / visibilitychange→hidden 时触发\"最终值\"回调\n * - 如果 SDK 在 pagehide 之前 shutdown,该 page 的最终值会丢失(无法解决,库本身不暴露 unsubscribe)\n * - SPA 路由切换不会重置 web vitals(库的设计:按浏览器 page lifecycle 计算)\n *\n * 生命周期保护:\n * - disposed 标志位:shutdown 后所有回调入口 early return,避免向已销毁的 provider 上报\n * - dynamic import 完成后再次检查 disposed:处理\"start() 完成前 shutdown()\"的 race\n * - dynamic import 自身 try/catch:与 PluginManager 的 per-plugin 隔离形成纵深防御,\n * 即使加载失败也不影响整体启动\n */\n\nimport { type Attributes } from '@opentelemetry/api';\n\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, getVitalResult, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\nimport type * as WebVitalsAttribution from 'web-vitals/attribution';\nimport type { MetricWithAttribution } from 'web-vitals/attribution';\n\ntype NavigationTypeName =\n | 'back-forward'\n | 'back-forward-cache'\n | 'navigate'\n | 'prerender'\n | 'reload'\n | 'restore'\n | 'unknown';\n\ntype VitalLike = Pick<MetricWithAttribution, 'attribution' | 'id' | 'name' | 'navigationType' | 'rating' | 'value'>;\n\nconst FALLBACK_NAVIGATION_TYPE: NavigationTypeName = 'unknown';\n\n// PerformanceNavigationTiming.type 用下划线('back_forward'),web-vitals 用连字符('back-forward'),\n// 统一成 web-vitals 的连字符风格作为 fallback,下游聚合时维度值一致\nconst normalizeNavigationType = (raw: string | undefined): NavigationTypeName => {\n if (!raw) return FALLBACK_NAVIGATION_TYPE;\n return raw.replace(/_/g, '-') as NavigationTypeName;\n};\n\nconst computeFallbackNavigationType = (): NavigationTypeName => {\n if (typeof performance === 'undefined') return FALLBACK_NAVIGATION_TYPE;\n const entry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;\n return normalizeNavigationType(entry?.type);\n};\n\n// 仅挑选低基数、可枚举的字段进 metric attributes,避免指标维度爆炸\nconst buildMetricDimensions = (metric: VitalLike, fallbackNavType: NavigationTypeName): Attributes => ({\n // 优先使用 metric.navigationType(含 bfcache 等准确值),fallback 到首次 navigation entry\n 'rum.navigation.type': metric.navigationType ?? fallbackNavType,\n [RUM_ATTRIBUTES.vitalMetric]: metric.name.toLowerCase(),\n [RUM_ATTRIBUTES.vitalRating]: metric.rating,\n});\n\n// 把 attribution 中价值最高的字段抽出来(仅放进 span,不放进 metric)\nconst pickAttribution = (metric: VitalLike, redactUrl: (url: string) => string): Attributes => {\n const attr = metric.attribution ?? {};\n const result: Attributes = {};\n const getAttrValue = (key: string) => (attr as Record<string, unknown>)[key];\n\n const setIfDefined = (key: string, value: unknown, transform?: (value: string) => string) => {\n if (value === undefined || value === null) return;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n result[key] = typeof value === 'string' && transform ? transform(value) : value;\n }\n };\n\n switch (metric.name) {\n case 'LCP':\n setIfDefined('vital.lcp.url', getAttrValue('url'), redactUrl);\n setIfDefined('vital.lcp.target', getAttrValue('target'));\n setIfDefined('vital.lcp.element_render_delay', getAttrValue('elementRenderDelay'));\n setIfDefined('vital.lcp.resource_load_duration', getAttrValue('resourceLoadDuration'));\n setIfDefined('vital.lcp.time_to_first_byte', getAttrValue('timeToFirstByte'));\n break;\n case 'CLS':\n setIfDefined('vital.cls.largest_shift_target', getAttrValue('largestShiftTarget'));\n setIfDefined('vital.cls.largest_shift_value', getAttrValue('largestShiftValue'));\n setIfDefined('vital.cls.load_state', getAttrValue('loadState'));\n break;\n case 'INP':\n setIfDefined('vital.inp.interaction_target', getAttrValue('interactionTarget'));\n setIfDefined('vital.inp.interaction_type', getAttrValue('interactionType'));\n setIfDefined('vital.inp.input_delay', getAttrValue('inputDelay'));\n setIfDefined('vital.inp.processing_duration', getAttrValue('processingDuration'));\n setIfDefined('vital.inp.presentation_delay', getAttrValue('presentationDelay'));\n break;\n case 'FCP':\n setIfDefined('vital.fcp.time_to_first_byte', getAttrValue('timeToFirstByte'));\n setIfDefined('vital.fcp.load_state', getAttrValue('loadState'));\n break;\n case 'TTFB':\n setIfDefined('vital.ttfb.waiting_duration', getAttrValue('waitingDuration'));\n setIfDefined('vital.ttfb.dns_duration', getAttrValue('dnsDuration'));\n setIfDefined('vital.ttfb.connection_duration', getAttrValue('connectionDuration'));\n setIfDefined('vital.ttfb.request_duration', getAttrValue('requestDuration'));\n break;\n default:\n break;\n }\n return result;\n};\n\nexport const createWebVitalsPlugin = (): BkOTPlugin => {\n let disposed = false;\n\n return {\n name: 'web-vitals',\n async init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n // dynamic import 自包 try/catch:与 PluginManager 的 per-plugin 隔离形成纵深防御,\n // 离线 / chunk 加载失败时不影响其他插件,且 debug 模式留下排查痕迹\n let webVitalsModule: typeof WebVitalsAttribution;\n try {\n webVitalsModule = await import('web-vitals/attribution');\n } catch (error) {\n if (context.config.debug) {\n globalThis.console?.warn('[bk-ot] web-vitals load failed:', error);\n }\n return;\n }\n // import 期间用户可能已经 shutdown:此后再注册回调会向已销毁 provider 上报\n if (disposed) return;\n\n const { onCLS, onFCP, onINP, onLCP, onTTFB } = webVitalsModule;\n const fallbackNavType = computeFallbackNavigationType();\n const clsHistogram = context.meter.createHistogram('browser.web_vital.cls', {\n unit: '1',\n description: 'Cumulative Layout Shift',\n });\n const durationHistogram = context.meter.createHistogram('browser.web_vital.duration', {\n unit: 'ms',\n description: 'Web Vitals duration metrics, including FCP, INP, LCP and TTFB',\n });\n\n const recordSpan = (metric: VitalLike) => {\n const metricName = metric.name.toLowerCase();\n const span = context.startSpan('browser.web_vital', {\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...buildMetricDimensions(metric, fallbackNavType),\n ...buildRumSpanAttributes({\n duration: metric.name === 'CLS' ? undefined : metric.value,\n errorType: metric.rating === 'good' ? 'none' : 'slow',\n eventLabel: 'Web 指标',\n result: getVitalResult(metric.rating),\n spanSubtype: metricName,\n spanType: RUM_SPAN_TYPE.vital,\n targetLabel: metricName,\n targetValue: metric.value,\n }),\n [RUM_ATTRIBUTES.vitalMetric]: metricName,\n [RUM_ATTRIBUTES.vitalValue]: metric.value,\n [RUM_ATTRIBUTES.vitalRating]: metric.rating,\n // 单条度量唯一 ID 仅放进 span,避免污染 metric 维度\n 'vital.id': metric.id,\n ...pickAttribution(metric, context.config.hooks.redactUrl),\n });\n span.end();\n };\n\n const recordDurationMetric = (metric: VitalLike) => {\n // 防御非法值:极端浏览器实现 bug 可能给出 NaN/Infinity,OT 后端会拒收\n if (disposed || !Number.isFinite(metric.value)) return;\n durationHistogram.record(\n metric.value,\n context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...buildMetricDimensions(metric, fallbackNavType),\n }),\n );\n recordSpan(metric);\n };\n\n onCLS(metric => {\n if (disposed || !Number.isFinite(metric.value)) return;\n clsHistogram.record(\n metric.value,\n context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...buildMetricDimensions(metric, fallbackNavType),\n }),\n );\n recordSpan(metric);\n });\n onFCP(recordDurationMetric);\n onINP(recordDurationMetric);\n onLCP(recordDurationMetric);\n onTTFB(recordDurationMetric);\n },\n shutdown() {\n // web-vitals 的 onXXX 不返回 unsubscribe,无法真正取消订阅;\n // 用 disposed 标志位让回调入口 early return 即可\n disposed = true;\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * WebSocket 插件\n *\n * 通过 Proxy 包装 window.WebSocket,对每个 ws 实例采集:\n * - span (`websocket.connect`):仅覆盖建连阶段,避免长连接 span 永远不结束\n * span attributes: url.full / url.scheme / server.address / network.protocol.name\n * - histogram (`browser.websocket.connect.duration`):成功建连耗时(ms),便于聚合 P99\n * - counter (`browser.websocket.message.count` / `.bytes`):双向消息数与字节数,\n * 按 websocket.direction (in/out) 维度区分\n * - counter (`browser.websocket.error.count`):错误次数,按 websocket.error.phase\n * (connect / runtime) 区分建连失败与运行时中断\n * - log (`websocket.error` / `websocket.close`):错误与关闭事件详情;error 按 url\n * 做 throttle 防 reconnect 风暴\n *\n * Patch 策略(关键设计):\n * 使用 Proxy(NativeWebSocket, { construct }),相比传统 monkey-patch 优势:\n * - 自动透传静态常量(WebSocket.OPEN / CONNECTING 等),无需手动复制;\n * Object.assign 复制会失败,因为这些常量是 non-enumerable\n * - 完美支持 `class MyWS extends WebSocket` 子类化场景:\n * Reflect.construct 用 newTarget=MyWS 创建对象,原型链与 instanceof 都正确\n * - 透明的 Symbol.hasInstance 行为,业务侧 instanceof 检查正常\n *\n * 已知行为与限制:\n * - send 方法在每个实例上覆盖(不在 prototype),原因是 native ws 没有 send 回调,\n * 只能包装实例方法计数 outbound;副作用是若业务 `delete socket.send` 会失去统计\n * - shutdown 后已存在的 socket 实例仍带着我们的事件监听与 send 包装;\n * 用 disposed 标志位让回调入口 early return,新建的 ws 走原生路径\n * - 大消息(> 64KB string)字节计算降级为 string.length 估算(CJK 等多字节字符会偏小),\n * 避免 TextEncoder().encode() 的 O(n) 开销在 IM / 流式场景拖累主线程\n * - error throttle:默认 60s 窗口最多 5 条同 url 错误日志,counter 仍精确累计不受影响\n *\n * 多 patch 兼容:\n * shutdown 时检查 window.WebSocket 是否仍是我们的 Proxy,若被其他库覆盖则不强行恢复,\n * 避免抹掉后来者的 patch\n */\n\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\n\nimport { createThrottle } from '../core/throttle';\nimport { shouldIgnoreUrl } from '../core/url';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\nimport type { Span } from '@opentelemetry/api';\n\n// 64KB 以下精确计算 UTF-8 字节,超过则用 string.length 粗估,避免 IM 大文本拖慢主线程\nconst BYTES_PRECISE_THRESHOLD = 64 * 1024;\n// 错误日志节流:60s / 5 条同 url,防 reconnect 风暴\nconst ERROR_THROTTLE_WINDOW_MS = 60_000;\nconst ERROR_THROTTLE_MAX_PER_WINDOW = 5;\n\nconst getMessageByteLength = (data: unknown): number => {\n if (data == null) return 0;\n if (typeof data === 'string') {\n if (data.length > BYTES_PRECISE_THRESHOLD || typeof TextEncoder === 'undefined') {\n // 粗估:纯 ASCII 准确,CJK / emoji 会偏小;权衡性能与准确性\n return data.length;\n }\n return new TextEncoder().encode(data).byteLength;\n }\n if (data instanceof ArrayBuffer) return data.byteLength;\n if (ArrayBuffer.isView(data)) return data.byteLength;\n if (typeof Blob !== 'undefined' && data instanceof Blob) return data.size;\n return 0;\n};\n\ninterface WebSocketAttributeBundle {\n metricAttributes: Record<string, string>;\n spanAttributes: Record<string, string>;\n}\n\nconst buildWebSocketAttributes = (url: string, redactUrl: (url: string) => string): WebSocketAttributeBundle => {\n const spanAttributes: Record<string, string> = {\n 'network.protocol.name': 'websocket',\n 'url.full': redactUrl(url),\n };\n const metricAttributes: Record<string, string> = {\n 'network.protocol.name': 'websocket',\n };\n\n try {\n const parsed = new URL(url, typeof location === 'undefined' ? 'http://localhost' : location.href);\n spanAttributes['server.address'] = parsed.host;\n // url.scheme 区分 ws / wss,下游能按加密/非加密分组\n spanAttributes['url.scheme'] = parsed.protocol.replace(':', '');\n // server.address 进 metric 是中等基数(按域名维度通常 < 10),对 metric 系列影响可控\n metricAttributes['server.address'] = parsed.host;\n metricAttributes['url.scheme'] = spanAttributes['url.scheme'];\n } catch {\n /* malformed URL:保留 redacted 原 url,跳过 host / scheme 解析 */\n }\n\n return { metricAttributes, spanAttributes };\n};\n\ninterface InstrumentDeps {\n bytesCounter: ReturnType<BkOTRuntimeContext['meter']['createCounter']>;\n connectDurationHistogram: ReturnType<BkOTRuntimeContext['meter']['createHistogram']>;\n context: BkOTRuntimeContext;\n errorCounter: ReturnType<BkOTRuntimeContext['meter']['createCounter']>;\n errorThrottle: ReturnType<typeof createThrottle>;\n messageCounter: ReturnType<BkOTRuntimeContext['meter']['createCounter']>;\n url: string;\n isDisposed: () => boolean;\n}\n\nconst instrumentSocket = (socket: WebSocket, deps: InstrumentDeps) => {\n const {\n bytesCounter,\n connectDurationHistogram,\n context,\n errorCounter,\n errorThrottle,\n isDisposed,\n messageCounter,\n url,\n } = deps;\n const { metricAttributes, spanAttributes } = buildWebSocketAttributes(url, context.config.hooks.redactUrl);\n const targetLabel = spanAttributes['server.address'] ?? spanAttributes['url.full'];\n\n // connect span 仅覆盖建连阶段,长连接生命周期不绑定 span,避免\"幽灵 span\"\n const connectSpan: Span = context.startSpan('websocket.connect', {\n ...spanAttributes,\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: 'WebSocket',\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n }),\n ...(spanAttributes['server.address'] ? { [RUM_ATTRIBUTES.targetDomain]: spanAttributes['server.address'] } : {}),\n });\n const startTime = performance.now();\n let connectEnded = false;\n\n const endConnectSpan = (status: 'error' | 'opened') => {\n if (connectEnded) return;\n connectEnded = true;\n const duration = performance.now() - startTime;\n if (status === 'error') {\n connectSpan.setStatus({ code: SpanStatusCode.ERROR, message: 'websocket connect failed' });\n }\n connectSpan.setAttributes(\n buildRumSpanAttributes({\n duration,\n errorType: status === 'error' ? 'network' : 'none',\n eventLabel: 'WebSocket',\n result: status === 'error' ? RUM_SPAN_RESULT.error : RUM_SPAN_RESULT.success,\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n targetValue: duration,\n }),\n );\n connectSpan.end();\n // 仅成功建连记入 histogram;错误的 duration 多为 timeout 固定值,统计意义有限\n if (status === 'opened' && !isDisposed()) {\n connectDurationHistogram.record(duration, metricAttributes);\n }\n };\n\n socket.addEventListener('open', () => {\n if (isDisposed()) return;\n endConnectSpan('opened');\n });\n\n socket.addEventListener('message', event => {\n if (isDisposed()) return;\n const inAttrs = { ...metricAttributes, 'websocket.direction': 'in' };\n messageCounter.add(1, inAttrs);\n bytesCounter.add(getMessageByteLength(event.data), inAttrs);\n });\n\n socket.addEventListener('error', () => {\n if (isDisposed()) return;\n // 用 connectEnded 区分阶段:未 open 即出错为 connect 阶段,已 open 后为 runtime\n const phase = connectEnded ? 'runtime' : 'connect';\n errorCounter.add(1, { ...metricAttributes, 'websocket.error.phase': phase });\n endConnectSpan('error');\n // 同 url 错误节流:reconnect 失败循环不至于产生日志风暴;counter 不受影响(已在上方累计)\n const throttleResult = errorThrottle.allow(`ws:error:${url}`);\n if (!throttleResult.allowed) return;\n context.emitLog({\n severityNumber: SeverityNumber.ERROR,\n severityText: 'ERROR',\n body: 'websocket.error',\n attributes: {\n ...spanAttributes,\n ...buildRumSpanAttributes({\n errorType: 'network',\n eventLabel: 'WebSocket 错误',\n result: RUM_SPAN_RESULT.error,\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n }),\n 'websocket.error.phase': phase,\n 'websocket.error.window_count': throttleResult.count,\n },\n });\n });\n\n socket.addEventListener('close', event => {\n if (isDisposed()) return;\n // 从未 open 就 close → 建连失败(部分浏览器只触发 close 不触发 error 的边界)\n if (!connectEnded) {\n endConnectSpan('error');\n }\n context.emitLog({\n severityNumber: SeverityNumber.INFO,\n severityText: 'INFO',\n body: 'websocket.close',\n attributes: {\n ...spanAttributes,\n ...buildRumSpanAttributes({\n errorType: event.wasClean ? 'none' : 'network',\n eventLabel: 'WebSocket 关闭',\n result: event.wasClean ? RUM_SPAN_RESULT.success : RUM_SPAN_RESULT.warning,\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n targetValue: event.code,\n }),\n 'websocket.close.code': event.code,\n 'websocket.close.reason': event.reason,\n 'websocket.close.was_clean': event.wasClean,\n },\n });\n });\n\n // 包装 send 统计 outbound:原生 WebSocket 没有 send 回调或事件,只能逐实例覆盖方法\n const originalSend = socket.send.bind(socket);\n socket.send = (data: ArrayBufferLike | ArrayBufferView | Blob | string) => {\n if (!isDisposed()) {\n const outAttrs = { ...metricAttributes, 'websocket.direction': 'out' };\n messageCounter.add(1, outAttrs);\n bytesCounter.add(getMessageByteLength(data), outAttrs);\n }\n return originalSend(data);\n };\n};\n\nexport const createWebSocketPlugin = (): BkOTPlugin => {\n let originalWebSocket: typeof WebSocket | undefined;\n let patchedProxy: typeof WebSocket | undefined;\n let disposed = false;\n\n return {\n name: 'websocket',\n init(context) {\n if (typeof window === 'undefined' || typeof window.WebSocket === 'undefined') {\n return;\n }\n\n const nativeWebSocket = window.WebSocket;\n originalWebSocket = nativeWebSocket;\n\n const messageCounter = context.meter.createCounter('browser.websocket.message.count', {\n description: 'Total number of WebSocket messages observed',\n });\n const bytesCounter = context.meter.createCounter('browser.websocket.message.bytes', {\n unit: 'By',\n description: 'Total bytes transferred over WebSocket (best-effort)',\n });\n const errorCounter = context.meter.createCounter('browser.websocket.error.count', {\n description: 'Total number of WebSocket error events',\n });\n const connectDurationHistogram = context.meter.createHistogram('browser.websocket.connect.duration', {\n unit: 'ms',\n description: 'WebSocket connection establishment duration (only successful connects)',\n });\n const errorThrottle = createThrottle(ERROR_THROTTLE_WINDOW_MS, ERROR_THROTTLE_MAX_PER_WINDOW);\n const isDisposed = () => disposed;\n\n // Proxy 而非 function 替换:自动透传静态常量、原型与 Symbol.hasInstance;\n // 同时 Reflect.construct 用 newTarget 让 `class extends WebSocket` 子类化正确工作\n patchedProxy = new Proxy(nativeWebSocket, {\n construct(target, args, newTarget) {\n const socket = Reflect.construct(target, args, newTarget) as WebSocket;\n if (disposed) return socket;\n const url = args[0] != null ? String(args[0]) : '';\n // 用户主动 ignore 或上报 endpoint,避免回环监控\n if (!url || shouldIgnoreUrl(context.config, url)) {\n return socket;\n }\n instrumentSocket(socket, {\n bytesCounter,\n connectDurationHistogram,\n context,\n errorCounter,\n errorThrottle,\n isDisposed,\n messageCounter,\n url,\n });\n return socket;\n },\n });\n\n window.WebSocket = patchedProxy;\n },\n shutdown() {\n disposed = true;\n // 多 patch 兼容:仅当当前 window.WebSocket 还是我们的 Proxy 时才恢复,\n // 避免抹掉其他库后来者的 patch\n if (typeof window !== 'undefined' && originalWebSocket && window.WebSocket === patchedProxy) {\n window.WebSocket = originalWebSocket;\n }\n patchedProxy = undefined;\n originalWebSocket = undefined;\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport type { ResolvedBkOTConfig } from './config';\n\nconst SAMPLE_STORAGE_KEY = 'BK_RUM_SAMPLING';\n\n// 在 sessionStorage 不可用时降级到模块内单例缓存,避免同会话内多次结果不一致\nconst memoryCache = new Map<string, boolean>();\n\nconst readMemory = (key: string) => memoryCache.get(key);\nconst writeMemory = (key: string, value: boolean) => {\n memoryCache.set(key, value);\n return value;\n};\n\nexport const isBkOTSampled = ({ sampling }: Pick<ResolvedBkOTConfig, 'sampling'>) => {\n if (sampling.rate <= 0) {\n return false;\n }\n if (sampling.rate >= 1) {\n return true;\n }\n\n try {\n const cached = sessionStorage.getItem(SAMPLE_STORAGE_KEY);\n if (cached) {\n return cached === '1';\n }\n const sampled = Math.random() < sampling.rate;\n sessionStorage.setItem(SAMPLE_STORAGE_KEY, sampled ? '1' : '0');\n writeMemory(SAMPLE_STORAGE_KEY, sampled);\n return sampled;\n } catch {\n const cached = readMemory(SAMPLE_STORAGE_KEY);\n if (typeof cached === 'boolean') {\n return cached;\n }\n return writeMemory(SAMPLE_STORAGE_KEY, Math.random() < sampling.rate);\n }\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n type Attributes,\n type Span,\n context,\n metrics,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n} from '@opentelemetry/api';\nimport { logs } from '@opentelemetry/api-logs';\nimport { ZoneContextManager } from '@opentelemetry/context-zone';\nimport { W3CTraceContextPropagator } from '@opentelemetry/core';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { type Instrumentation, registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';\nimport { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\nimport {\n BatchSpanProcessor,\n ParentBasedSampler,\n SimpleSpanProcessor,\n TraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-base';\nimport { WebTracerProvider } from '@opentelemetry/sdk-trace-web';\n\nimport { createActionPlugin } from '../plugins/action';\nimport { createBlankScreenPlugin } from '../plugins/blank-screen';\nimport { createCommonInstrumentationsPlugin } from '../plugins/common';\nimport { createCspViolationPlugin } from '../plugins/csp-violation';\nimport { createDevicePlugin } from '../plugins/device';\nimport { createErrorPlugin } from '../plugins/error';\nimport { createHttpBodyPlugin } from '../plugins/http-body';\nimport { createLongTaskPlugin } from '../plugins/long-task';\nimport { createPageViewPlugin } from '../plugins/page-view';\nimport { createResourcePlugin } from '../plugins/resource';\nimport { createRouteTimingPlugin } from '../plugins/route-timing';\nimport { createSessionPlugin } from '../plugins/session';\nimport { createWebVitalsPlugin } from '../plugins/web-vitals';\nimport { createWebSocketPlugin } from '../plugins/websocket';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\nimport {\n type BkOTActionEventPayload,\n type BkOTCustomEventPayload,\n type BkOTOptions,\n type BkOTPluginsConfig,\n type ResolvedBkOTConfig,\n resolveConfig,\n} from './config';\nimport { type BkOTPlugin, type BkOTRuntimeContext, type BkOTStartSpanOptions, PluginManager } from './plugin';\nimport { FilteringSpanProcessor } from './processor';\nimport { type BkOTUserContext, type BkOTViewContext, RumRuntimeContext } from './runtime-context';\nimport { isBkOTSampled } from './sampling';\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\n\nexport interface BkOTInstance extends BkOTRuntimeContext {\n flush: () => Promise<void>;\n reportAction: (payload: BkOTActionEventPayload) => void;\n reportCustomEvent: (payload: BkOTCustomEventPayload) => void;\n setUser: (user: BkOTUserContext) => void;\n setView: (view: BkOTViewContext) => void;\n shutdown: () => Promise<void>;\n start: () => Promise<void>;\n use: (plugin: BkOTPlugin) => BkOTInstance;\n useInstrumentation: (instrumentation: Instrumentation) => BkOTInstance;\n}\n\nconst createExporterOptions = (config: ResolvedBkOTConfig['transport']['traces']) => ({\n url: config.endpoint,\n headers: config.headers,\n concurrencyLimit: config.concurrencyLimit,\n timeoutMillis: config.timeoutMillis,\n});\n\ntype DebugSignal = 'logs' | 'metrics' | 'traces';\n\nconst BK_OT_INSTRUMENTATION_NAME = 'bk-rum';\n\n/**\n * 用 Proxy 包装一层 debug 旁路,避免直接 mutate 第三方 exporter 实例(其内部可能含 #private fields)。\n */\nconst wrapWithDebug = <TExporter extends { export: (...args: any[]) => any }>(\n exporter: TExporter,\n signal: DebugSignal,\n enabled: boolean,\n): TExporter => {\n if (!enabled) {\n return exporter;\n }\n return new Proxy(exporter, {\n get(target, prop, receiver) {\n if (prop === 'export') {\n return (records: unknown, callback: unknown) => {\n globalThis.console?.log(`[bk-ot][${signal}]`, records);\n return (target as { export: (...args: unknown[]) => unknown }).export(records, callback);\n };\n }\n const value = Reflect.get(target, prop, receiver);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n};\n\n// 负责给原生 fetch/xhr 注入 W3C traceparent 头;仅 SDK 启用时装载,避免 disabled 状态仍 patch 全局 API\nconst getCorePlugins = (config: ResolvedBkOTConfig): BkOTPlugin[] => [\n createCommonInstrumentationsPlugin(config.instrumentations),\n];\n\nconst isBuiltInPluginEnabled = <TOptions extends object>(\n option: boolean | TOptions | undefined,\n defaultEnabled: boolean,\n) => (option === undefined ? defaultEnabled : option !== false);\n\nconst getBuiltInPluginOptions = <TOptions extends object>(option: boolean | TOptions | undefined) =>\n typeof option === 'object' && option !== null ? option : undefined;\n\nconst createBuiltInPlugin = <TOptions extends object>(\n option: boolean | TOptions | undefined,\n defaultEnabled: boolean,\n factory: (options?: TOptions) => BkOTPlugin,\n) => (isBuiltInPluginEnabled(option, defaultEnabled) ? [factory(getBuiltInPluginOptions(option))] : []);\n\n// 内置 RUM 插件仅在采样命中时启用;上下文插件先启动,确保后续 span/log 能带上关联字段。\nconst getRumPlugins = (plugins: BkOTPluginsConfig): BkOTPlugin[] => [\n ...createBuiltInPlugin(plugins.device, true, createDevicePlugin),\n ...createBuiltInPlugin(plugins.session, true, createSessionPlugin),\n ...createBuiltInPlugin(plugins.pageView, true, createPageViewPlugin),\n ...createBuiltInPlugin(plugins.httpBody, false, createHttpBodyPlugin),\n ...createBuiltInPlugin(plugins.error, true, createErrorPlugin),\n ...createBuiltInPlugin(plugins.webVitals, true, createWebVitalsPlugin),\n ...createBuiltInPlugin(plugins.resource, true, createResourcePlugin),\n ...createBuiltInPlugin(plugins.blankScreen, true, createBlankScreenPlugin),\n ...createBuiltInPlugin(plugins.action, false, createActionPlugin),\n ...createBuiltInPlugin(plugins.websocket, false, createWebSocketPlugin),\n ...createBuiltInPlugin(plugins.longTask, false, createLongTaskPlugin),\n ...createBuiltInPlugin(plugins.cspViolation, false, createCspViolationPlugin),\n ...createBuiltInPlugin(plugins.routeTiming, false, createRouteTimingPlugin),\n];\n\ntype SdkLifecyclePhase = 'idle' | 'started' | 'starting' | 'stopped';\n\nexport class BkOpenTelemetry implements BkOTInstance {\n private readonly loggerProvider: LoggerProvider;\n private readonly meterProvider: MeterProvider;\n private phase: SdkLifecyclePhase = 'idle';\n private readonly pluginManager: PluginManager;\n private readonly plugins: BkOTPlugin[];\n private readonly registeredInstrumentations: Instrumentation[];\n private readonly runtimeContext: RumRuntimeContext;\n private readonly sampled: boolean;\n\n private readonly teardownCallbacks: Array<() => void> = [];\n private readonly tracerProvider: WebTracerProvider;\n private assertBeforeStart(method: string) {\n if (this.phase !== 'idle') {\n throw new Error(`[bk-ot] ${method}() must be called before start(); set autoStart: false before registration.`);\n }\n }\n public readonly applyRedact = (attributes: Attributes) => this.config.hooks.redactAttributes(attributes);\n\n public readonly config: ResolvedBkOTConfig;\n\n public readonly emitLog = (record: LogRecord) => {\n const merged: LogRecord = {\n ...record,\n attributes: this.applyRedact({\n ...this.getRuntimeAttributes(),\n ...((record.attributes ?? {}) as Attributes),\n }),\n };\n this.logger.emit(merged);\n };\n public readonly flush = async () => {\n await this.pluginManager.flush();\n await this.tracerProvider.forceFlush();\n await this.meterProvider.forceFlush();\n await this.loggerProvider.forceFlush();\n };\n\n public readonly getRuntimeAttributes = () => this.runtimeContext.getAttributes();\n public readonly logger: BkOTRuntimeContext['logger'];\n\n public readonly meter: BkOTRuntimeContext['meter'];\n\n public readonly reportAction = ({ attributes = {}, targetTag, targetText, type }: BkOTActionEventPayload) => {\n if (!this.sampled) {\n return;\n }\n const textShort = targetText?.slice(0, 32);\n const span = this.startSpan(`action.${type}`, {\n ...this.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: '用户操作',\n spanSubtype: type,\n spanType: RUM_SPAN_TYPE.action,\n targetLabel: textShort ?? targetTag ?? type,\n }),\n [RUM_ATTRIBUTES.actionType]: type,\n ...(textShort ? { [RUM_ATTRIBUTES.targetTextShort]: textShort } : {}),\n ...(targetTag ? { [RUM_ATTRIBUTES.targetTag]: targetTag } : {}),\n ...attributes,\n });\n span.end();\n };\n\n public readonly reportCustomEvent = ({ attributes = {}, error, name }: BkOTCustomEventPayload) => {\n if (!this.sampled) {\n // 未采样时静默丢弃;如需调试请把 debug 选项打开\n if (this.config.debug) {\n globalThis.console?.warn('[bk-ot] custom event dropped (not sampled):', name);\n }\n return;\n }\n\n const span = this.startSpan(`custom.${name}`, {\n ...this.config.hooks.getPageAttributes(),\n ...this.config.hooks.getCustomAttributes(),\n ...buildRumSpanAttributes({\n errorType: error ? 'custom' : 'none',\n eventLabel: '自定义事件',\n result: error ? RUM_SPAN_RESULT.error : RUM_SPAN_RESULT.success,\n spanSubtype: name,\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel: name,\n }),\n ...attributes,\n 'rum.custom.name': name,\n });\n\n if (error) {\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n }\n\n span.end();\n };\n\n public readonly setRuntimeAttributes = (attributes: Attributes) => {\n this.runtimeContext.setAttributes(attributes);\n };\n\n public readonly setUser = (user: BkOTUserContext) => {\n this.runtimeContext.setUser(user);\n };\n\n public readonly setView = (view: BkOTViewContext) => {\n this.runtimeContext.setView(view);\n };\n\n public readonly shutdown = async () => {\n if (this.phase === 'stopped') {\n return;\n }\n await this.flush();\n while (this.teardownCallbacks.length) {\n this.teardownCallbacks.pop()?.();\n }\n await this.pluginManager.shutdown();\n while (this.registeredInstrumentations.length) {\n this.registeredInstrumentations.pop()?.disable?.();\n }\n await this.tracerProvider.shutdown();\n await this.meterProvider.shutdown();\n await this.loggerProvider.shutdown();\n trace.disable();\n metrics.disable();\n context.disable();\n this.phase = 'stopped';\n };\n\n public readonly start = async () => {\n if (this.phase === 'started' || this.phase === 'starting') {\n return;\n }\n if (this.phase === 'stopped') {\n // shutdown 已经销毁了 provider/processor,此处禁止再次启动\n throw new Error('[bk-ot] instance has been shut down and cannot be restarted; create a new instance instead.');\n }\n if (!this.config.enabled) {\n this.phase = 'started';\n return;\n }\n this.phase = 'starting';\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n this.tracerProvider.register({\n contextManager: new ZoneContextManager(),\n });\n metrics.setGlobalMeterProvider(this.meterProvider);\n logs.setGlobalLoggerProvider(this.loggerProvider);\n try {\n if (this.registeredInstrumentations.length) {\n registerInstrumentations({\n instrumentations: this.registeredInstrumentations,\n meterProvider: this.meterProvider,\n tracerProvider: this.tracerProvider,\n });\n }\n await this.pluginManager.start(this);\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n const flushOnPageHide = () => {\n void this.flush();\n };\n const flushOnHidden = () => {\n if (document.visibilityState === 'hidden') {\n void this.flush();\n }\n };\n window.addEventListener('pagehide', flushOnPageHide);\n document.addEventListener('visibilitychange', flushOnHidden);\n this.teardownCallbacks.push(\n () => window.removeEventListener('pagehide', flushOnPageHide),\n () => document.removeEventListener('visibilitychange', flushOnHidden),\n );\n }\n } catch (error) {\n while (this.teardownCallbacks.length) {\n this.teardownCallbacks.pop()?.();\n }\n await this.pluginManager.shutdown();\n for (const instrumentation of this.registeredInstrumentations) {\n instrumentation.disable?.();\n }\n trace.disable();\n metrics.disable();\n context.disable();\n this.phase = 'idle';\n throw error;\n }\n this.phase = 'started';\n };\n\n public readonly startSpan = (name: string, attributes: Attributes = {}, options?: BkOTStartSpanOptions): Span =>\n this.tracer.startSpan(name, {\n kind: SpanKind.INTERNAL,\n startTime: options?.startTime,\n attributes: this.applyRedact({\n ...this.getRuntimeAttributes(),\n ...attributes,\n }),\n });\n\n public readonly tracer: BkOTRuntimeContext['tracer'];\n\n public readonly use = (plugin: BkOTPlugin): this => {\n this.assertBeforeStart('use');\n if (this.sampled) {\n this.plugins.push(plugin);\n }\n return this;\n };\n\n public readonly useInstrumentation = (instrumentation: Instrumentation): this => {\n this.assertBeforeStart('useInstrumentation');\n if (this.config.enabled) {\n this.registeredInstrumentations.push(instrumentation);\n }\n return this;\n };\n\n constructor(inputConfig: BkOTOptions) {\n this.config = resolveConfig(inputConfig);\n this.sampled = this.config.enabled && isBkOTSampled(this.config);\n\n const resource = resourceFromAttributes(this.config.resourceAttributes);\n const spanExporter = wrapWithDebug(\n new OTLPTraceExporter(createExporterOptions(this.config.transport.traces)),\n 'traces',\n this.config.debug,\n );\n const metricExporter = wrapWithDebug(\n new OTLPMetricExporter(createExporterOptions(this.config.transport.metrics)),\n 'metrics',\n this.config.debug,\n );\n const logExporter = wrapWithDebug(\n new OTLPLogExporter(createExporterOptions(this.config.transport.logs)),\n 'logs',\n this.config.debug,\n );\n const innerSpanProcessor =\n this.config.spanProcessor === 'simple'\n ? new SimpleSpanProcessor(spanExporter)\n : new BatchSpanProcessor(spanExporter, this.config.spanBatch);\n const spanProcessor = new FilteringSpanProcessor(innerSpanProcessor, this.config);\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: this.config.metricIntervalMillis,\n });\n const logProcessor = new BatchLogRecordProcessor(logExporter);\n\n this.tracerProvider = new WebTracerProvider({\n resource,\n sampler: new ParentBasedSampler({\n root: new TraceIdRatioBasedSampler(this.sampled ? 1 : 0),\n }),\n spanProcessors: [spanProcessor],\n });\n this.meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n this.loggerProvider = new LoggerProvider({\n resource,\n processors: [logProcessor],\n });\n this.tracer = this.tracerProvider.getTracer(BK_OT_INSTRUMENTATION_NAME);\n this.meter = this.meterProvider.getMeter(BK_OT_INSTRUMENTATION_NAME);\n this.logger = this.loggerProvider.getLogger(BK_OT_INSTRUMENTATION_NAME);\n this.runtimeContext = new RumRuntimeContext(\n {\n 'rum.sampled': this.sampled,\n },\n this.config.user,\n );\n this.registeredInstrumentations = this.config.enabled ? [...(this.config.instrumentations.custom ?? [])] : [];\n // 核心插件在 SDK 启用时装载;RUM 数据上报类插件仅在采样命中时装载\n this.plugins = [\n ...(this.config.enabled ? getCorePlugins(this.config) : []),\n ...(this.sampled ? [...getRumPlugins(this.config.plugins), ...(this.config.plugins.custom ?? [])] : []),\n ];\n this.pluginManager = new PluginManager(this.plugins);\n\n if (this.config.autoStart) {\n void this.start().catch(error => {\n globalThis.console?.warn('[bk-ot] auto start failed:', error);\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/schema/attributes.ts","../src/core/config.ts","../src/core/plugin.ts","../src/core/url.ts","../src/core/processor.ts","../src/core/runtime-context.ts","../src/schema/span.ts","../src/plugins/action.ts","../src/core/telemetry-attributes.ts","../src/plugins/blank-screen.ts","../src/plugins/common.ts","../src/core/throttle.ts","../src/plugins/csp-violation.ts","../src/core/id.ts","../src/plugins/device.ts","../src/plugins/error.ts","../src/plugins/http-body.ts","../src/plugins/long-task.ts","../src/core/route-observer.ts","../src/plugins/page-view.ts","../src/plugins/resource.ts","../src/plugins/route-timing.ts","../src/plugins/session.ts","../src/plugins/web-vitals.ts","../src/plugins/websocket.ts","../src/core/sampling.ts","../src/core/sdk.ts"],"sourcesContent":["/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n/**\n * RUM 上报字段 key 集中定义。\n *\n * 插件里不要散落硬编码字符串,后续字段命名调整只改这里,避免采集端与字段文档逐步漂移。\n */\nexport const RUM_ATTRIBUTES = {\n actionType: 'action.type',\n deploymentEnvironmentName: 'deployment.environment.name',\n browserName: 'browser_name',\n browserVersion: 'browser_version',\n cacheHit: 'cache_hit',\n deviceType: 'device_type',\n durationBucket: 'duration_bucket',\n errorType: 'error_type',\n eventLabel: 'event_label',\n exceptionFingerprint: 'exception.fingerprint',\n exceptionMessageShort: 'exception.message_short',\n exceptionStackTopFrame: 'exception.stack_top_frame',\n longtaskAttributionScript: 'longtask.attribution_script',\n longtaskBlockingDuration: 'longtask.blocking_duration',\n networkEffectiveType: 'network.effective_type',\n nextHopProtocol: 'next_hop_protocol',\n result: 'result',\n serviceName: 'service.name',\n serviceVersion: 'service.version',\n osName: 'os_name',\n sessionHasReplay: 'session.has_replay',\n sessionId: 'session.id',\n sessionStartTime: 'session.start_time',\n spanSubtype: 'span_subtype',\n spanType: 'span_type',\n statusClass: 'status_class',\n targetDomain: 'target_domain',\n targetLabel: 'target_label',\n targetPathTemplate: 'target_path_template',\n targetTag: 'target.tag',\n targetTextShort: 'target.text_short',\n targetValue: 'target_value',\n traceScene: 'trace_scene',\n transferSize: 'transfer_size',\n userId: 'user.id',\n vitalMetric: 'vital.metric',\n vitalRating: 'vital.rating',\n vitalValue: 'vital.value',\n viewId: 'view.id',\n viewLoadingType: 'view.loading_type',\n viewUrl: 'view.url',\n viewUrlPathGroup: 'view.url_path_group',\n} as const;\n\nexport type RumAttributeKey = (typeof RUM_ATTRIBUTES)[keyof typeof RUM_ATTRIBUTES];\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { BkOTActionPluginOptions } from '../plugins/action';\nimport type { BkOTBlankScreenPluginOptions } from '../plugins/blank-screen';\nimport type { BkOTDevicePluginOptions } from '../plugins/device';\nimport type { BkOTErrorPluginOptions } from '../plugins/error';\nimport type { BkOTLongTaskPluginOptions } from '../plugins/long-task';\nimport type { BkOTSessionPluginOptions } from '../plugins/session';\nimport type { BkOTPlugin } from './plugin';\nimport type { BkOTUserContext } from './runtime-context';\nimport type { Attributes } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\n\nexport interface BkOTActionEventPayload {\n attributes?: Attributes;\n targetTag?: string;\n targetText?: string;\n type: 'click' | 'custom' | 'input' | 'keydown' | 'pointerdown' | 'scroll' | 'submit' | string;\n}\n\nexport interface BkOTAppConfig {\n environment?: string;\n name?: string;\n version?: string;\n}\n\nexport interface BkOTAttributesConfig {\n custom?: () => Attributes;\n error?: () => Attributes;\n metric?: () => Attributes;\n page?: () => Attributes;\n}\n\nexport type BkOTAttributeValue = boolean | number | string;\n\nexport interface BkOTBatchConfig {\n exportTimeoutMillis?: number;\n maxExportBatchSize?: number;\n maxQueueSize?: number;\n scheduledDelayMillis?: number;\n}\n\nexport interface BkOTCustomEventPayload {\n attributes?: Attributes;\n error?: Error;\n name: string;\n}\n\nexport interface BkOTHttpBodyConfig {\n maxBodySize?: number;\n redact?: (payload: BkOTHttpBodyRedactPayload) => string;\n}\n\nexport interface BkOTHttpBodyRedactPayload {\n body: string;\n contentType?: string;\n method: string;\n status?: number;\n truncated: boolean;\n type: 'request' | 'response';\n url: string;\n}\n\nexport interface BkOTInstrumentationsConfig {\n // 自定义/第三方 OTel instrumentation 实例;SDK 启用时直接注册,不受采样影响\n custom?: Instrumentation[];\n documentLoad?: boolean;\n fetch?: boolean;\n /**\n * 控制 fetch/xhr 请求向哪些后台 API 注入 W3C `traceparent`(内含 OpenTelemetry traceId)请求头。\n *\n * 同源请求始终注入;该配置用于把跨域后台 API 也纳入注入范围(在同源基础上扩展):\n * - `'all'`:所有请求都注入(含跨域,使用前确认涉及的隐私/CORS 影响)。\n * - `string`:URL 包含该子串即注入,例如 `'api.example.com'`。\n * - `RegExp`:URL 匹配该正则即注入。\n * - 以上字符串/正则的数组:命中任意一个即注入。\n * - `(url: string) => boolean`:完全自定义判断。\n *\n * 注意:向跨域服务注入自定义头会触发 CORS 预检,目标服务需在\n * `Access-Control-Allow-Headers` 中放行 `traceparent`(以及按需的 `tracestate`)。\n *\n * 默认未配置时仅同源请求注入,保持既有行为。\n */\n propagateTraceHeaderUrls?: BkOTPropagateTraceHeaderUrls;\n userInteraction?: boolean | { eventNames?: string[] };\n xhr?: boolean;\n}\n\nexport interface BkOTOptions {\n app?: BkOTAppConfig;\n attributes?: BkOTAttributesConfig;\n autoStart?: boolean;\n debug?: boolean;\n enabled?: boolean;\n instrumentations?: BkOTInstrumentationsConfig;\n plugins?: BkOTPluginsConfig;\n privacy?: BkOTPrivacyConfig;\n route?: BkOTRouteConfig;\n sampling?: BkOTSamplingConfig;\n spanBatch?: BkOTBatchConfig;\n spanProcessor?: BkOTSpanProcessorMode;\n transport?: BkOTTransportConfig;\n user?: BkOTUserContext;\n}\n\nexport interface BkOTPluginsConfig {\n action?: BkOTActionPluginOptions | boolean;\n blankScreen?: BkOTBlankScreenPluginOptions | boolean;\n cspViolation?: boolean;\n custom?: BkOTPlugin[];\n device?: BkOTDevicePluginOptions | boolean;\n error?: BkOTErrorPluginOptions | boolean;\n httpBody?: BkOTHttpBodyConfig | boolean;\n longTask?: BkOTLongTaskPluginOptions | boolean;\n pageView?: boolean;\n resource?: boolean;\n routeTiming?: boolean;\n session?: BkOTSessionPluginOptions | boolean;\n websocket?: boolean;\n webVitals?: boolean;\n}\n\nexport interface BkOTPrivacyConfig {\n // 用于隐私脱敏:在所有 RUM 自定义插件 emit 之前过滤一次属性\n redactAttributes?: (attributes: Attributes) => Attributes;\n // 用于隐私脱敏:所有上报到 attribute 的 URL 在写入前会经过此函数\n redactUrl?: (url: string) => string;\n}\n\nexport type BkOTPropagateTraceHeaderUrls =\n | 'all'\n | ((url: string) => boolean)\n | Array<RegExp | string>\n | RegExp\n | string;\n\nexport interface BkOTRouteConfig {\n /**\n * 返回低基数路由分组,例如 `/trace/:id`。\n * SDK 默认退化为 URL pathname;复杂业务路由建议接入方显式提供模板。\n */\n getPathGroup?: (url: string) => string | undefined;\n}\n\nexport interface BkOTSamplingConfig {\n rate?: number;\n}\n\nexport type BkOTSpanProcessorMode = 'batch' | 'simple';\n\nexport interface BkOTTransportConfig {\n endpoint?: string;\n headers?: Record<string, string>;\n signals?: SignalExportersInputConfig;\n token?: string;\n}\n\nexport interface ResolvedAppConfig {\n environment: string;\n name: string;\n version?: string;\n}\n\nexport interface ResolvedBkOTConfig {\n app: ResolvedAppConfig;\n autoStart: boolean;\n debug: boolean;\n enabled: boolean;\n hooks: ResolvedConfigHooks;\n instrumentations: BkOTInstrumentationsConfig;\n metricIntervalMillis: number;\n plugins: BkOTPluginsConfig;\n resourceAttributes: Attributes;\n sampling: ResolvedSamplingConfig;\n spanBatch?: BkOTBatchConfig;\n spanProcessor: BkOTSpanProcessorMode;\n transport: ResolvedTransportConfig;\n user?: BkOTUserContext;\n}\n\nexport interface ResolvedConfigHooks {\n getCustomAttributes: () => Attributes;\n getErrorAttributes: () => Attributes;\n getMetricAttributes: () => Attributes;\n getPageAttributes: () => Attributes;\n getViewPathGroup: (url: string) => string;\n redactAttributes: (attributes: Attributes) => Attributes;\n redactUrl: (url: string) => string;\n}\n\nexport interface ResolvedSamplingConfig {\n rate: number;\n}\n\nexport interface ResolvedTransportConfig {\n endpoint: string;\n logs: SignalExporterConfig;\n metrics: SignalExporterConfig;\n traces: SignalExporterConfig;\n}\n\nexport interface SignalEndpointsConfig {\n logs?: string;\n metrics?: string;\n traces?: string;\n}\n\nexport interface SignalExporterConfig {\n concurrencyLimit?: number;\n endpoint: string;\n headers?: Record<string, string>;\n timeoutMillis?: number;\n}\n\nexport type SignalExporterInputConfig = Partial<SignalExporterConfig>;\n\nexport interface SignalExportersInputConfig {\n endpoints?: SignalEndpointsConfig;\n logs?: SignalExporterInputConfig;\n metrics?: SignalExporterInputConfig;\n traces?: SignalExporterInputConfig;\n}\n\nconst DEFAULT_OTLP_ENDPOINT = 'http://localhost:4318'; // 默认上报到本机 OTLP collector\nconst DEFAULT_METRIC_INTERVAL_MILLIS = 60 * 1000; // 60s 上报一次\nconst DEFAULT_SERVICE_NAME = 'unknown_service';\n\nconst trimTrailingSlash = (value: string) => value.replace(/\\/+$/, '');\n\nconst resolveSignalEndpoint = (endpoint: string | undefined, signalPath: 'logs' | 'metrics' | 'traces') => {\n const normalized = trimTrailingSlash(endpoint || DEFAULT_OTLP_ENDPOINT);\n if (normalized.endsWith(`/v1/${signalPath}`)) {\n return normalized;\n }\n if (normalized.endsWith('/v1')) {\n return `${normalized}/${signalPath}`;\n }\n return `${normalized}/v1/${signalPath}`;\n};\n\nconst resolveSignalConfig = (\n transport: BkOTTransportConfig | undefined,\n signalPath: 'logs' | 'metrics' | 'traces',\n): SignalExporterConfig => {\n const signalConfig = transport?.signals?.[signalPath];\n const signalEndpoint = transport?.signals?.endpoints?.[signalPath];\n // token 只负责提供默认鉴权头;headers 保持显式配置优先,便于特殊场景覆盖。\n const mergedHeaders =\n transport?.token || transport?.headers || signalConfig?.headers\n ? {\n ...(transport?.token ? { Authorization: `Bearer ${transport.token}` } : {}),\n ...(transport?.headers ?? {}),\n ...(signalConfig?.headers ?? {}),\n }\n : undefined;\n\n return {\n concurrencyLimit: signalConfig?.concurrencyLimit,\n endpoint: signalEndpoint\n ? trimTrailingSlash(signalEndpoint)\n : resolveSignalEndpoint(signalConfig?.endpoint ?? transport?.endpoint, signalPath),\n headers: mergedHeaders,\n timeoutMillis: signalConfig?.timeoutMillis,\n };\n};\n\nconst clampSampleRate = (sampleRate?: number) => {\n if (typeof sampleRate !== 'number' || !Number.isFinite(sampleRate)) {\n return 1;\n }\n return Math.max(0, Math.min(1, sampleRate));\n};\n\nconst identityRedactAttributes = (attributes: Attributes) => attributes;\nconst identityRedactUrl = (url: string) => url;\n\nconst getDefaultPathGroup = (url: string) => {\n try {\n return new URL(url, typeof location === 'undefined' ? 'http://localhost' : location.href).pathname || '/';\n } catch {\n return url || '/';\n }\n};\n\nconst resolveAppConfig = (app: BkOTOptions['app']): ResolvedAppConfig => ({\n environment: app?.environment ?? 'production',\n name: app?.name ?? DEFAULT_SERVICE_NAME,\n version: app?.version,\n});\n\nconst resolveResourceAttributes = (app: ResolvedAppConfig): Attributes => ({\n [RUM_ATTRIBUTES.deploymentEnvironmentName]: app.environment,\n 'rum.provider': 'blueking',\n [RUM_ATTRIBUTES.serviceName]: app.name,\n ...(app.version ? { [RUM_ATTRIBUTES.serviceVersion]: app.version } : {}),\n 'telemetry.sdk.language': 'webjs',\n});\n\nconst resolveTransportConfig = (transport: BkOTOptions['transport']): ResolvedTransportConfig => ({\n endpoint: trimTrailingSlash(transport?.endpoint || DEFAULT_OTLP_ENDPOINT),\n traces: resolveSignalConfig(transport, 'traces'),\n metrics: resolveSignalConfig(transport, 'metrics'),\n logs: resolveSignalConfig(transport, 'logs'),\n});\n\nconst resolveSamplingConfig = (sampling: BkOTOptions['sampling']): ResolvedSamplingConfig => ({\n rate: clampSampleRate(sampling?.rate),\n});\n\nconst resolveHooks = (config: BkOTOptions): ResolvedConfigHooks => ({\n getPageAttributes:\n config.attributes?.page ??\n (() => ({\n 'rum.page.host': typeof window === 'undefined' ? '' : window.location.host,\n 'rum.page.path': typeof window === 'undefined' ? '' : window.location.pathname,\n })),\n getMetricAttributes: config.attributes?.metric ?? (() => ({})),\n getErrorAttributes: config.attributes?.error ?? (() => ({})),\n getCustomAttributes: config.attributes?.custom ?? (() => ({})),\n getViewPathGroup: url => config.route?.getPathGroup?.(url) ?? getDefaultPathGroup(url),\n redactAttributes: config.privacy?.redactAttributes ?? identityRedactAttributes,\n redactUrl: config.privacy?.redactUrl ?? identityRedactUrl,\n});\n\nexport const resolveConfig = (config: BkOTOptions): ResolvedBkOTConfig => {\n const app = resolveAppConfig(config.app);\n\n return {\n app,\n autoStart: config.autoStart ?? true,\n debug: config.debug ?? false,\n enabled: config.enabled ?? true,\n hooks: resolveHooks(config),\n instrumentations: config.instrumentations ?? {},\n metricIntervalMillis: DEFAULT_METRIC_INTERVAL_MILLIS,\n plugins: config.plugins ?? {},\n resourceAttributes: resolveResourceAttributes(app),\n sampling: resolveSamplingConfig(config.sampling),\n spanBatch: config.spanBatch,\n spanProcessor: config.spanProcessor ?? 'batch',\n transport: resolveTransportConfig(config.transport),\n user: config.user,\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport type { ResolvedBkOTConfig } from './config';\nimport type { BkOTUserContext, BkOTViewContext } from './runtime-context';\nimport type { Attributes, Meter, Span, TimeInput, Tracer } from '@opentelemetry/api';\nimport type { Logger, LogRecord } from '@opentelemetry/api-logs';\n\nexport interface BkOTPlugin {\n enabled?: ((config: ResolvedBkOTConfig) => boolean) | boolean;\n name: string;\n flush?: () => Promise<void> | void;\n init: (context: BkOTRuntimeContext) => Promise<void> | void;\n shutdown?: () => Promise<void> | void;\n}\n\nexport interface BkOTRuntimeContext {\n config: ResolvedBkOTConfig;\n logger: Logger;\n meter: Meter;\n tracer: Tracer;\n /** 对外提供的属性脱敏入口,所有插件在 emit 前都应过一次 */\n applyRedact: (attributes: Attributes) => Attributes;\n /** 包装 logger.emit,自动应用 redact 与 runtime attributes */\n emitLog: (record: LogRecord) => void;\n getRuntimeAttributes: () => Attributes;\n setRuntimeAttributes: (attributes: Attributes) => void;\n /** 更新用户上下文;user.id 由接入方传入,SDK 原样透传 */\n setUser: (user: BkOTUserContext) => void;\n /** 更新当前 View 上下文,后续 span/log 会自动带上 view.* 关联字段 */\n setView: (view: BkOTViewContext) => void;\n startSpan: (name: string, attributes?: Attributes, options?: BkOTStartSpanOptions) => Span;\n}\n\n/**\n * startSpan 的扩展选项。\n *\n * 用于\"事件实际发生在过去某个时刻,但 span 现在才创建\"的场景:\n * 例如 route-timing 在 raf+setTimeout 链路结束后才上报,但希望 span 的时间窗\n * 反映真实的\"路由切换瞬间到渲染完成\"区间,方便 trace 视图与并发请求对齐。\n *\n * startTime 接受 OT TimeInput;常用 epoch 毫秒(performance.timeOrigin + performance.now())。\n * 配合 span.end(endTime?) 可控制结束时间。\n */\nexport interface BkOTStartSpanOptions {\n startTime?: TimeInput;\n}\n\nexport const isPluginEnabled = (plugin: BkOTPlugin, config: ResolvedBkOTConfig) => {\n if (typeof plugin.enabled === 'function') {\n return plugin.enabled(config);\n }\n return plugin.enabled !== false;\n};\n\nexport const createPlugin = (plugin: BkOTPlugin) => plugin;\n\nexport class PluginManager {\n private readonly plugins: BkOTPlugin[];\n private startedPlugins: BkOTPlugin[] = [];\n\n public constructor(plugins: BkOTPlugin[]) {\n this.plugins = plugins;\n }\n\n public async flush() {\n // 并行 flush,避免单个慢插件拖累整体\n await Promise.all(this.startedPlugins.map(plugin => Promise.resolve().then(() => plugin.flush?.())));\n }\n\n public async shutdown() {\n const plugins = [...this.startedPlugins].reverse();\n for (const plugin of plugins) {\n await plugin.shutdown?.();\n }\n this.startedPlugins = [];\n }\n\n public async start(context: BkOTRuntimeContext) {\n // 每个插件独立 try/catch,单个插件 init 失败不应拖累其他插件:\n // - 失败的插件不会进入 startedPlugins(shutdown 时不会被反向 teardown)\n // - debug 模式打 warn,方便排查;非 debug 静默失败避免污染业务日志\n for (const plugin of this.plugins) {\n if (!isPluginEnabled(plugin, context.config)) {\n continue;\n }\n try {\n await plugin.init(context);\n this.startedPlugins.push(plugin);\n } catch (error) {\n if (context.config.debug) {\n globalThis.console?.warn(`[bk-ot] plugin \"${plugin.name}\" init failed:`, error);\n }\n }\n }\n }\n}\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport type { BkOTPropagateTraceHeaderUrls, ResolvedBkOTConfig } from './config';\n\nconst getBaseUrl = () => (typeof window === 'undefined' ? 'http://localhost' : window.location.origin);\n\nconst toAbsoluteUrl = (url: string) => {\n try {\n return new URL(url, getBaseUrl()).href;\n } catch {\n return url;\n }\n};\n\n// 去掉 trailing slash,便于 path 边界对齐比较\nconst stripTrailingSlash = (value: string) => (value.length > 1 ? value.replace(/\\/+$/, '') : value);\n\n// 比较两个 URL 是否指向同一资源(忽略 query/fragment 与末尾斜杠)\nconst isSameResource = (left: string, right: string) => {\n const stripped = (value: string) => stripTrailingSlash(value.replace(/[?#].*$/, ''));\n return stripped(left) === stripped(right);\n};\n\nconst isSameOrigin = (url: string) => {\n try {\n return new URL(url, getBaseUrl()).origin === getBaseUrl();\n } catch {\n return false;\n }\n};\n\nexport const isBkOTEndpoint = (config: ResolvedBkOTConfig, url: string) => {\n const targetUrl = toAbsoluteUrl(url);\n\n return [config.transport.traces.endpoint, config.transport.metrics.endpoint, config.transport.logs.endpoint].some(endpoint =>\n isSameResource(targetUrl, toAbsoluteUrl(endpoint)),\n );\n};\n\nexport const shouldIgnoreUrl = (config: ResolvedBkOTConfig, url: string) => isBkOTEndpoint(config, url);\n\nconst matchesPropagationRule = (rule: BkOTPropagateTraceHeaderUrls, url: string): boolean => {\n if (rule === 'all') {\n return true;\n }\n if (typeof rule === 'function') {\n return rule(url);\n }\n const patterns = Array.isArray(rule) ? rule : [rule];\n return patterns.some(pattern => (typeof pattern === 'string' ? url.includes(pattern) : pattern.test(url)));\n};\n\n// 同源始终注入 traceparent;跨域后台 API 仅在命中 propagateTraceHeaderUrls 配置时注入。\nexport const shouldPropagateTraceHeader = (config: ResolvedBkOTConfig, url: string) => {\n if (isSameOrigin(url)) {\n return true;\n }\n const rule = config.instrumentations.propagateTraceHeaderUrls;\n return rule ? matchesPropagationRule(rule, toAbsoluteUrl(url)) : false;\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { isBkOTEndpoint } from './url';\n\nimport type { ResolvedBkOTConfig } from './config';\nimport type { Context } from '@opentelemetry/api';\nimport type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nconst SELF_TELEMETRY_URL_KEYS = ['http.url', 'url.full'];\n\nconst getSpanUrl = (span: ReadableSpan): string | undefined => {\n for (const key of SELF_TELEMETRY_URL_KEYS) {\n const value = span.attributes?.[key];\n if (typeof value === 'string') {\n return value;\n }\n }\n return undefined;\n};\n\n/**\n * 包装 SpanProcessor,在 onEnd 阶段过滤指向自身上报 endpoint 的 span,\n * 兜底防止 official fetch/xhr 拦截器对 OTel 自身请求产生回环 span。\n */\nexport class FilteringSpanProcessor implements SpanProcessor {\n public constructor(\n private readonly inner: SpanProcessor,\n private readonly config: ResolvedBkOTConfig,\n ) {}\n\n public forceFlush(): Promise<void> {\n return this.inner.forceFlush();\n }\n\n public onEnd(span: ReadableSpan): void {\n const url = getSpanUrl(span);\n if (url && isBkOTEndpoint(this.config, url)) {\n return;\n }\n this.inner.onEnd(span);\n }\n\n public onStart(span: Span, parentContext: Context): void {\n this.inner.onStart(span, parentContext);\n }\n\n public shutdown(): Promise<void> {\n return this.inner.shutdown();\n }\n}\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { Attributes } from '@opentelemetry/api';\n\nexport interface BkOTUserContext {\n /** 由接入方传入并由 SDK 原样透传;SDK 不 hash、不脱敏、不改写。 */\n id?: string;\n}\n\nexport interface BkOTViewContext {\n id?: string;\n loadingType?: 'initial_load' | 'route_change';\n url?: string;\n urlPathGroup?: string;\n}\n\n/**\n * 管理 SDK 运行期公共属性快照。\n *\n * 这里仅保存“后续事件都需要带上”的低成本上下文,Session/View/用户切换时更新快照,\n * 插件发 span/log 时读取当前快照即可,避免每个插件重复维护公共字段。\n */\nexport class RumRuntimeContext {\n private readonly attributes: Attributes = {};\n private readonly setOptionalAttribute = (key: string, value: string | undefined) => {\n if (value === undefined) {\n delete this.attributes[key];\n return;\n }\n this.attributes[key] = value;\n };\n private user: BkOTUserContext = {};\n\n private view: BkOTViewContext = {};\n\n public getAttributes = (): Attributes => ({ ...this.attributes });\n\n public getUser = (): BkOTUserContext => ({ ...this.user });\n\n public getView = (): BkOTViewContext => ({ ...this.view });\n\n public setAttributes = (attributes: Attributes) => {\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined) {\n delete this.attributes[key];\n continue;\n }\n this.attributes[key] = value;\n }\n };\n\n public setUser = (user: BkOTUserContext) => {\n this.user = {\n ...this.user,\n ...user,\n };\n\n if ('id' in user) {\n if (user.id === undefined) {\n delete this.attributes[RUM_ATTRIBUTES.userId];\n return;\n }\n this.attributes[RUM_ATTRIBUTES.userId] = user.id;\n }\n };\n\n public setView = (view: BkOTViewContext) => {\n this.view = {\n ...this.view,\n ...view,\n };\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewId, view.id);\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewUrl, view.url);\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewUrlPathGroup, view.urlPathGroup);\n this.setOptionalAttribute(RUM_ATTRIBUTES.viewLoadingType, view.loadingType);\n };\n\n public constructor(initialAttributes: Attributes = {}, user?: BkOTUserContext) {\n this.setAttributes(initialAttributes);\n if (user) {\n this.setUser(user);\n }\n }\n}\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\nimport { RUM_ATTRIBUTES } from './attributes';\n\nimport type { Attributes } from '@opentelemetry/api';\n\nexport const RUM_SPAN_RESULT = {\n abort: 'abort',\n error: 'error',\n success: 'success',\n timeout: 'timeout',\n warning: 'warning',\n} as const;\n\nexport const RUM_SPAN_TYPE = {\n action: 'action',\n custom: 'custom',\n document: 'document',\n error: 'error',\n http: 'http',\n longtask: 'longtask',\n resource: 'resource',\n route: 'route',\n vital: 'vital',\n} as const;\n\nexport interface RumHttpSpanInput {\n duration?: number;\n method?: string;\n statusCode?: number;\n subtype: 'fetch' | 'xhr';\n url?: string;\n urlPathTemplate?: string;\n}\n\nexport interface RumResourceSpanInput {\n duration: number;\n initiatorType?: string;\n responseStatus?: number;\n transferSize?: number;\n url: string;\n urlPathTemplate?: string;\n}\n\nexport interface RumSpanClassification {\n duration?: number;\n errorType?: string;\n eventLabel?: string;\n result?: RumSpanResult;\n spanSubtype: string;\n spanType: RumSpanType;\n targetLabel?: string;\n targetValue?: boolean | number | string;\n traceScene?: string;\n}\n\nexport type RumSpanResult = (typeof RUM_SPAN_RESULT)[keyof typeof RUM_SPAN_RESULT];\n\nexport type RumSpanType = (typeof RUM_SPAN_TYPE)[keyof typeof RUM_SPAN_TYPE];\n\nexport interface RumUrlTarget {\n domain?: string;\n label?: string;\n pathTemplate?: string;\n}\n\nexport const getDurationBucket = (duration: number) => {\n if (duration < 100) return '<100ms';\n if (duration < 500) return '100~500ms';\n if (duration < 2000) return '500ms~2s';\n return '>2s';\n};\n\nexport const getStatusClass = (statusCode: number) => `${Math.trunc(statusCode / 100)}xx`;\n\nexport const getHttpResult = (statusCode: number | undefined): RumSpanResult => {\n if (statusCode === 408 || statusCode === 504) return RUM_SPAN_RESULT.timeout;\n if (typeof statusCode === 'number' && statusCode >= 400) return RUM_SPAN_RESULT.error;\n return RUM_SPAN_RESULT.success;\n};\n\nexport const getHttpErrorType = (statusCode: number | undefined) => {\n if (statusCode === 408 || statusCode === 504) return 'network_timeout';\n if (typeof statusCode !== 'number' || statusCode < 400) return 'none';\n return statusCode >= 500 ? 'http_5xx' : 'http_4xx';\n};\n\nexport const getVitalResult = (rating: string | undefined): RumSpanResult =>\n rating === 'good' ? RUM_SPAN_RESULT.success : RUM_SPAN_RESULT.warning;\n\nexport const getUrlTarget = (url: string | undefined, pathTemplate?: string): RumUrlTarget => {\n if (!url) {\n return {};\n }\n try {\n const parsed = new URL(url, typeof location === 'undefined' ? 'http://localhost' : location.href);\n const normalizedPath = pathTemplate ?? parsed.pathname;\n return {\n domain: parsed.host,\n label: `${parsed.host}${normalizedPath}`,\n pathTemplate: normalizedPath,\n };\n } catch {\n return {\n label: url,\n pathTemplate: pathTemplate ?? url,\n };\n }\n};\n\nexport const buildRumSpanAttributes = ({\n duration,\n errorType = 'none',\n eventLabel,\n result = RUM_SPAN_RESULT.success,\n spanSubtype,\n spanType,\n targetLabel,\n targetValue,\n traceScene,\n}: RumSpanClassification): Attributes => ({\n [RUM_ATTRIBUTES.spanType]: spanType,\n [RUM_ATTRIBUTES.spanSubtype]: spanSubtype,\n [RUM_ATTRIBUTES.result]: result,\n [RUM_ATTRIBUTES.errorType]: errorType,\n ...(duration !== undefined ? { [RUM_ATTRIBUTES.durationBucket]: getDurationBucket(duration) } : {}),\n ...(eventLabel !== undefined ? { [RUM_ATTRIBUTES.eventLabel]: eventLabel } : {}),\n ...(targetLabel !== undefined ? { [RUM_ATTRIBUTES.targetLabel]: targetLabel } : {}),\n ...(targetValue !== undefined ? { [RUM_ATTRIBUTES.targetValue]: targetValue } : {}),\n ...(traceScene !== undefined ? { [RUM_ATTRIBUTES.traceScene]: traceScene } : {}),\n});\n\nexport const buildRumHttpSpanAttributes = ({\n duration,\n method,\n statusCode,\n subtype,\n url,\n urlPathTemplate,\n}: RumHttpSpanInput): Attributes => {\n const target = getUrlTarget(url, urlPathTemplate);\n return {\n ...buildRumSpanAttributes({\n duration,\n errorType: getHttpErrorType(statusCode),\n eventLabel: 'API 调用',\n result: getHttpResult(statusCode),\n spanSubtype: subtype,\n spanType: RUM_SPAN_TYPE.http,\n targetLabel: target.label,\n targetValue: statusCode,\n }),\n ...(method ? { 'http.request.method': method.toUpperCase() } : {}),\n ...(target.domain ? { [RUM_ATTRIBUTES.targetDomain]: target.domain } : {}),\n ...(target.pathTemplate ? { [RUM_ATTRIBUTES.targetPathTemplate]: target.pathTemplate } : {}),\n ...(typeof statusCode === 'number'\n ? {\n 'http.response.status_code': statusCode,\n [RUM_ATTRIBUTES.statusClass]: getStatusClass(statusCode),\n }\n : {}),\n };\n};\n\nexport const buildRumResourceSpanAttributes = ({\n duration,\n initiatorType = 'other',\n responseStatus,\n transferSize,\n url,\n urlPathTemplate,\n}: RumResourceSpanInput): Attributes => {\n const target = getUrlTarget(url, urlPathTemplate);\n const result = typeof responseStatus === 'number' ? getHttpResult(responseStatus) : RUM_SPAN_RESULT.success;\n return {\n ...buildRumSpanAttributes({\n duration,\n errorType: typeof responseStatus === 'number' ? getHttpErrorType(responseStatus) : 'none',\n eventLabel: '静态资源',\n result,\n spanSubtype: initiatorType,\n spanType: RUM_SPAN_TYPE.resource,\n targetLabel: target.label ?? url,\n targetValue: transferSize,\n }),\n ...(target.domain ? { [RUM_ATTRIBUTES.targetDomain]: target.domain } : {}),\n ...(target.pathTemplate ? { [RUM_ATTRIBUTES.targetPathTemplate]: target.pathTemplate } : {}),\n ...(typeof responseStatus === 'number'\n ? {\n 'http.response.status_code': responseStatus,\n [RUM_ATTRIBUTES.statusClass]: getStatusClass(responseStatus),\n }\n : {}),\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport type BkOTActionEventName = 'click' | 'input' | 'keydown' | 'pointerdown' | 'scroll' | 'submit';\n\nexport interface BkOTActionPluginOptions {\n eventNames?: BkOTActionEventName[];\n}\n\nconst DEFAULT_EVENT_NAMES: BkOTActionEventName[] = ['click', 'submit', 'keydown'];\n\nconst getElement = (target: EventTarget | null) => (target instanceof Element ? target : undefined);\n\nconst getTargetText = (element: Element | undefined) => {\n if (!element) {\n return undefined;\n }\n const text = element.textContent?.trim() || element.getAttribute('aria-label') || element.getAttribute('title') || '';\n return text ? text.slice(0, 32) : undefined;\n};\n\nexport const createActionPlugin = (options: BkOTActionPluginOptions = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const eventNames = options.eventNames ?? DEFAULT_EVENT_NAMES;\n\n return {\n name: 'action',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const listenerOptions: AddEventListenerOptions = { capture: true, passive: true };\n\n const emitAction = (event: Event) => {\n const element = getElement(event.target);\n const targetText = getTargetText(element);\n const targetTag = element?.tagName.toLowerCase();\n const span = context.startSpan(`action.${event.type}`, {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: '用户操作',\n spanSubtype: event.type,\n spanType: RUM_SPAN_TYPE.action,\n targetLabel: targetText ?? targetTag ?? event.type,\n }),\n [RUM_ATTRIBUTES.actionType]: event.type,\n ...(targetText ? { [RUM_ATTRIBUTES.targetTextShort]: targetText } : {}),\n ...(targetTag ? { [RUM_ATTRIBUTES.targetTag]: targetTag } : {}),\n });\n span.end();\n };\n\n for (const eventName of eventNames) {\n window.addEventListener(eventName, emitAction, listenerOptions);\n teardownCallbacks.push(() => window.removeEventListener(eventName, emitAction, listenerOptions));\n }\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { Attributes } from '@opentelemetry/api';\n\nconst getViewPathGroup = (value: unknown): string | undefined =>\n typeof value === 'string' && value.length > 0 ? value : undefined;\n\nconst buildViewPathGroupAttribute = (viewPathGroup: unknown): Attributes => {\n const normalized = getViewPathGroup(viewPathGroup);\n return normalized ? { [RUM_ATTRIBUTES.viewUrlPathGroup]: normalized } : {};\n};\n\nexport const getMetricViewAttributes = (runtimeAttributes: Attributes): Attributes =>\n buildViewPathGroupAttribute(runtimeAttributes[RUM_ATTRIBUTES.viewUrlPathGroup]);\n\nexport const getInstrumentationSpanRuntimeAttributes = (\n runtimeAttributes: Attributes,\n fallbackViewPathGroup?: string,\n): Attributes => {\n const { [RUM_ATTRIBUTES.viewUrlPathGroup]: rawViewPathGroup, ...restAttributes } = runtimeAttributes;\n return {\n ...restAttributes,\n ...buildViewPathGroupAttribute(getViewPathGroup(rawViewPathGroup) ?? fallbackViewPathGroup),\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 白屏检测插件,基于视口采样判定首屏是否疑似空白。\n *\n * 检测策略:\n * - 在 `DOMContentLoaded` 后延迟 checkDelay(默认 3s)触发一次检测,避免 SPA / 慢加载场景下检测时机过早。\n * - 在视口的 5 个采样点(中心、四象限)调用 `document.elementFromPoint`,命中\"根元素 / body / documentElement\"\n * 即视为该样本\"空白\"。\n * - 命中 `loadingSelectors`(默认含常见 loading mask / spinner,业务可通过 `ignoreSelectors` 追加)的样本\n * 既不计入分子也不计入分母,避免骨架屏被误判为白屏。\n * - 命中比例 `score = empty / (total - loading)` 超过 threshold(默认 0.8)即判白屏;全部样本都是 loading 时\n * 视为待定不上报。\n *\n * 上报形态(仅在判定为白屏时):\n * - metric: `browser.blank_screen.count`,仅带低基数维度 `view.url_path_group` / `blank_screen.root`,控制 cardinality。\n * - error span: `browser.blank_screen`,携带 score、threshold、命中元素、DOM 节点数等信息,用于错误分析。\n *\n * SPA 注意事项:\n * - 默认 `rootSelector` 为 `body`。SPA 项目(React/Vue 等)应显式配置为应用挂载点(如 `#app` / `#root`),\n * 否则一旦应用成功 mount,采样点会落在挂载根的子节点上而非 body,永远无法判定为白屏。\n *\n * 关键 attribute:\n * - `blank_screen.score` / `threshold` / `detected`:判定结果与依据。\n * - `blank_screen.sample_total` / `sample_valid` / `sample_loading`:采样统计明细,便于复盘 loading 排除情况。\n * - `blank_screen.center_element`:视口中心点元素的简化选择器,辅助定位问题。\n * - `blank_screen.dom_node_count`:body 下 DOM 节点总数,用于判断\"是否真的没渲染\"。\n */\n\nimport { getMetricViewAttributes } from '../core/telemetry-attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport interface BkOTBlankScreenPluginOptions {\n checkDelay?: number;\n // 自定义\"页面已渲染\"忽略选择器,命中即认为是 loading 占位、非空白\n ignoreSelectors?: string[];\n rootSelector?: string;\n threshold?: number;\n}\n\nconst DEFAULT_SELECTOR = 'body';\nconst DEFAULT_CHECK_DELAY = 3000;\nconst DEFAULT_THRESHOLD = 0.8;\n// 常见 loading mask / spinner 选择器,命中说明页面正在加载而非空白\nconst DEFAULT_LOADING_SELECTORS = [\n '[data-loading=\"true\"]',\n '[aria-busy=\"true\"]',\n '.loading',\n '.is-loading',\n '.spinner',\n '.skeleton',\n '.bk-loading',\n];\n\nconst SAMPLE_POINTS: Array<[number, number]> = [\n [0.5, 0.5],\n [0.25, 0.25],\n [0.75, 0.25],\n [0.25, 0.75],\n [0.75, 0.75],\n];\n\nconst nonNegativeNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;\n\nconst boundedRatio = (value: number | undefined, fallback: number) => {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return fallback;\n }\n return Math.max(0, Math.min(1, value));\n};\n\nconst getElementSelector = (element: Element | null) => {\n if (!element) {\n return '';\n }\n const tag = element.tagName.toLowerCase();\n if (element.id) {\n return `${tag}#${element.id}`;\n }\n return tag;\n};\n\nconst matchesAny = (element: Element | null, selectors: string[]) => {\n if (!element) return false;\n return selectors.some(selector => {\n try {\n return element.matches(selector) || !!element.closest(selector);\n } catch {\n return false;\n }\n });\n};\n\n// 页面就绪后再开始计时,避免 SPA / 慢加载场景下检测时机过早\n// 返回 dispose 用于在 shutdown 时取消尚未触发的监听,避免事件逃逸\nconst whenDocumentReady = (callback: () => void): (() => void) => {\n if (typeof document === 'undefined') return () => {};\n if (document.readyState === 'complete' || document.readyState === 'interactive') {\n callback();\n return () => {};\n }\n const handler = () => {\n document.removeEventListener('DOMContentLoaded', handler);\n callback();\n };\n document.addEventListener('DOMContentLoaded', handler);\n return () => document.removeEventListener('DOMContentLoaded', handler);\n};\n\nexport const createBlankScreenPlugin = (options: BkOTBlankScreenPluginOptions = {}): BkOTPlugin => {\n let timer: number | undefined;\n let disposeReadyListener: (() => void) | undefined;\n let disposed = false;\n const config = {\n checkDelay: nonNegativeNumber(options.checkDelay, DEFAULT_CHECK_DELAY),\n ignoreSelectors: options.ignoreSelectors ?? [],\n rootSelector: options.rootSelector ?? DEFAULT_SELECTOR,\n threshold: boundedRatio(options.threshold, DEFAULT_THRESHOLD),\n };\n\n return {\n name: 'blank-screen',\n init(context) {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n const { checkDelay, rootSelector, threshold } = config;\n const loadingSelectors = [...DEFAULT_LOADING_SELECTORS, ...config.ignoreSelectors];\n\n // 仅创建一次 counter,避免每次回调都触发一次 meter lookup\n const counter = context.meter.createCounter('browser.blank_screen.count', {\n description: 'Number of suspected blank screen detections',\n });\n\n const runDetection = () => {\n try {\n const root = document.querySelector(rootSelector);\n let emptyCount = 0;\n let loadingCount = 0;\n\n for (const [x, y] of SAMPLE_POINTS) {\n const element = document.elementFromPoint(window.innerWidth * x, window.innerHeight * y);\n if (matchesAny(element, loadingSelectors)) {\n loadingCount += 1;\n continue;\n }\n if (element === root || element === document.body || element === document.documentElement) {\n emptyCount += 1;\n }\n }\n\n // loading 样本既不计入分子也不计入分母,避免稀释空白率;全部为 loading 时视为待定不上报\n const validCount = SAMPLE_POINTS.length - loadingCount;\n if (validCount === 0) {\n return;\n }\n const score = emptyCount / validCount;\n const isBlank = score >= threshold;\n const centerElement = getElementSelector(\n document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2),\n );\n const attributes = {\n ...buildRumSpanAttributes({\n errorType: isBlank ? 'blank_screen' : 'none',\n eventLabel: '白屏',\n result: isBlank ? RUM_SPAN_RESULT.error : RUM_SPAN_RESULT.success,\n spanSubtype: 'blank_screen',\n spanType: RUM_SPAN_TYPE.error,\n targetLabel: rootSelector,\n targetValue: score,\n }),\n 'blank_screen.score': score,\n 'blank_screen.threshold': threshold,\n 'blank_screen.root': rootSelector,\n 'blank_screen.detected': isBlank,\n 'blank_screen.sample_total': SAMPLE_POINTS.length,\n 'blank_screen.sample_valid': validCount,\n 'blank_screen.sample_loading': loadingCount,\n 'blank_screen.center_element': centerElement,\n 'blank_screen.dom_node_count': document.body?.getElementsByTagName('*').length ?? 0,\n };\n\n if (isBlank) {\n counter.add(\n 1,\n context.applyRedact({\n // metric 仅放低基数维度,center_element 等高基数字段只进入 error span\n ...context.config.hooks.getMetricAttributes(),\n ...getMetricViewAttributes(context.getRuntimeAttributes()),\n 'blank_screen.root': rootSelector,\n }),\n );\n context.startSpan('browser.blank_screen', attributes).end();\n }\n } catch (error) {\n // 单次检测异常不应影响 SDK 其它链路;debug 模式下打印便于排查\n if (context.config.debug) {\n globalThis.console?.warn('[bk-ot][blank-screen] detection failed:', error);\n }\n }\n };\n\n disposeReadyListener = whenDocumentReady(() => {\n // 在 shutdown 之后才触发的 ready 事件不应再注册 timer\n if (disposed) return;\n timer = window.setTimeout(runDetection, checkDelay);\n });\n },\n shutdown() {\n disposed = true;\n disposeReadyListener?.();\n disposeReadyListener = undefined;\n if (timer !== undefined && typeof window !== 'undefined') {\n window.clearTimeout(timer);\n timer = undefined;\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 官方 instrumentation 适配插件,把 OpenTelemetry 浏览器侧的 4 个 instrumentation\n * (document-load / fetch / xhr / user-interaction)按需装载并接入 BkOT 的 page attributes、URL 脱敏、\n * endpoint 自循环过滤、同源 traceparent 传播等通用能力。\n *\n * 加载策略:\n * - 4 个 instrumentation 通过动态 import 按需引入,未启用的不会进入 bundle。\n * - 加载使用 Promise.all 并行执行,避免 4 个 chunk 串行 RTT 累积。\n *\n * URL 行为:\n * - `ignoreUrls`:屏蔽对自身上报 endpoint(trace/metric/log)的采集,避免 trace 自循环放大上报量。\n * - `propagateTraceHeaderCorsUrls`:默认仅向同源请求注入 W3C `traceparent`(含 OpenTelemetry traceId);\n * 通过 `instrumentations.propagateTraceHeaderUrls` 可把跨域后台 API 也纳入注入范围。\n * - 上报前对 span 上的 `http.url` / `url.full` 调用 `redactUrl` 做脱敏。\n *\n * span 装饰:\n * - 每个由 fetch/xhr instrumentation 产生的 span,统一补一份 `getPageAttributes()` + `applyRedact`\n * 的属性,与自定义插件保持一致。\n *\n * 隐式依赖:\n * - `registerInstrumentations` 没有显式传 tracerProvider / meterProvider,依赖\n * `BkOpenTelemetry.start()` 此前已经把对应 provider 设为 global。\n * `PluginManager.start` 是在那一步之后才调用,因此时序是稳定的。\n */\n\nimport { type Instrumentation, registerInstrumentations } from '@opentelemetry/instrumentation';\n\nimport { getInstrumentationSpanRuntimeAttributes } from '../core/telemetry-attributes';\nimport { shouldIgnoreUrl, shouldPropagateTraceHeader } from '../core/url';\nimport {\n buildRumHttpSpanAttributes,\n buildRumResourceSpanAttributes,\n buildRumSpanAttributes,\n RUM_SPAN_TYPE,\n} from '../schema/span';\n\nimport type { BkOTInstrumentationsConfig } from '../core/config';\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\nimport type { Span } from '@opentelemetry/api';\n\nconst URL_KEYS = ['http.url', 'url.full'];\nconst STATUS_KEYS = ['http.status_code', 'http.response.status_code'];\nconst METHOD_KEYS = ['http.method', 'http.request.method'];\n\n// OTel SDK 实现的 Span 在 export 前会暴露 `attributes` 字段供消费者读取;\n// 这是 implementation detail 但 OTel SDK 长期保留,比 `as any` 更收敛。\ntype DecoratableSpan = Span & { attributes?: Record<string, unknown> };\n\n// OTel 官方 instrumentation 内部对每个 URL 调用 matcher.test(url),\n// 这里把\"用户函数 + 自身 endpoint 过滤\"包装成具备 .test 的对象,是与 OTel 一致的扩展用法\nconst toUrlPredicateMatcher = (predicate: (url: string) => boolean): RegExp =>\n ({ test: predicate }) as unknown as RegExp;\n\nconst getCurrentViewPathGroup = (context: BkOTRuntimeContext) =>\n typeof location === 'undefined' ? undefined : context.config.hooks.getViewPathGroup(location.href);\n\n// 对官方 instrumentation 创建的 span,统一附加 page attributes 与 URL 脱敏\nconst decorateSpan = (context: BkOTRuntimeContext, span: DecoratableSpan, subtype: 'fetch' | 'xhr') => {\n span.setAttributes(\n context.applyRedact({\n ...getInstrumentationSpanRuntimeAttributes(context.getRuntimeAttributes(), getCurrentViewPathGroup(context)),\n ...context.config.hooks.getPageAttributes(),\n }),\n );\n let redactedUrl: string | undefined;\n for (const key of URL_KEYS) {\n const value = span.attributes?.[key];\n if (typeof value === 'string') {\n const redactedAttributes = context.applyRedact({ [key]: context.config.hooks.redactUrl(value) });\n const nextUrl = redactedAttributes[key];\n if (typeof nextUrl === 'string') {\n redactedUrl = nextUrl;\n span.setAttribute(key, nextUrl);\n }\n }\n }\n const statusCode = STATUS_KEYS.map(key => span.attributes?.[key]).find(value => typeof value === 'number') as\n | number\n | undefined;\n const method = METHOD_KEYS.map(key => span.attributes?.[key]).find(value => typeof value === 'string') as\n | string\n | undefined;\n const sourceUrl =\n redactedUrl ??\n (URL_KEYS.map(key => span.attributes?.[key]).find(value => typeof value === 'string') as string | undefined);\n\n span.setAttributes(\n context.applyRedact(\n buildRumHttpSpanAttributes({\n method,\n statusCode,\n subtype,\n url: sourceUrl,\n urlPathTemplate: sourceUrl ? context.config.hooks.getViewPathGroup(sourceUrl) : undefined,\n }),\n ),\n );\n};\n\nconst decorateDocumentSpan = (context: BkOTRuntimeContext, span: Span, subtype: string) => {\n const viewPathGroup = getCurrentViewPathGroup(context);\n span.setAttributes(\n context.applyRedact({\n ...getInstrumentationSpanRuntimeAttributes(context.getRuntimeAttributes(), viewPathGroup),\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: '文档加载',\n spanSubtype: subtype,\n spanType: RUM_SPAN_TYPE.document,\n targetLabel: viewPathGroup,\n traceScene: 'page_load',\n }),\n }),\n );\n};\n\nconst decorateDocumentResourceSpan = (context: BkOTRuntimeContext, span: Span, resource: PerformanceResourceTiming) => {\n const redactedUrl = context.config.hooks.redactUrl(resource.name);\n span.setAttributes(\n context.applyRedact({\n ...getInstrumentationSpanRuntimeAttributes(context.getRuntimeAttributes(), getCurrentViewPathGroup(context)),\n ...context.config.hooks.getPageAttributes(),\n ...buildRumResourceSpanAttributes({\n duration: resource.duration,\n initiatorType: resource.initiatorType || 'other',\n responseStatus: (resource as PerformanceResourceTiming & { responseStatus?: number }).responseStatus,\n transferSize: resource.transferSize,\n url: redactedUrl,\n urlPathTemplate: context.config.hooks.getViewPathGroup(resource.name),\n }),\n 'url.full': redactedUrl,\n }),\n );\n};\n\nexport const createCommonInstrumentationsPlugin = (option: BkOTInstrumentationsConfig = {}): BkOTPlugin => {\n const instrumentations: Instrumentation[] = [];\n const instrumentationOptions = {\n documentLoad: option.documentLoad ?? true,\n fetch: option.fetch ?? true,\n xhr: option.xhr ?? true,\n userInteraction: option.userInteraction ?? true,\n };\n\n return {\n name: 'official-instrumentations',\n enabled: Boolean(\n instrumentationOptions.documentLoad ||\n instrumentationOptions.fetch ||\n instrumentationOptions.xhr ||\n instrumentationOptions.userInteraction,\n ),\n async init(context) {\n const ignoreUrls = [toUrlPredicateMatcher(url => shouldIgnoreUrl(context.config, url))];\n const propagateTraceHeaderCorsUrls = [\n toUrlPredicateMatcher(url => shouldPropagateTraceHeader(context.config, url)),\n ];\n\n // 并行加载所有按需启用的 chunk,避免串行 RTT 累积\n const [docLoadMod, fetchMod, xhrMod, uiMod] = await Promise.all([\n instrumentationOptions.documentLoad\n ? import('@opentelemetry/instrumentation-document-load')\n : Promise.resolve(null),\n instrumentationOptions.fetch ? import('@opentelemetry/instrumentation-fetch') : Promise.resolve(null),\n instrumentationOptions.xhr ? import('@opentelemetry/instrumentation-xml-http-request') : Promise.resolve(null),\n instrumentationOptions.userInteraction\n ? import('@opentelemetry/instrumentation-user-interaction')\n : Promise.resolve(null),\n ]);\n\n // 每加载一个就立刻 push 到外层 instrumentations,确保 init 中途失败时 shutdown 仍能清理已构造的实例\n if (docLoadMod) {\n instrumentations.push(\n new docLoadMod.DocumentLoadInstrumentation({\n semconvStabilityOptIn: 'http',\n applyCustomAttributesOnSpan: {\n documentLoad: span => decorateDocumentSpan(context, span, 'navigate'),\n documentFetch: span => decorateDocumentSpan(context, span, 'document_fetch'),\n resourceFetch: (span, resource) => decorateDocumentResourceSpan(context, span, resource),\n },\n }),\n );\n }\n\n if (fetchMod) {\n instrumentations.push(\n new fetchMod.FetchInstrumentation({\n applyCustomAttributesOnSpan: span => decorateSpan(context, span as DecoratableSpan, 'fetch'),\n ignoreUrls,\n propagateTraceHeaderCorsUrls,\n semconvStabilityOptIn: 'http',\n ignoreNetworkEvents: false,\n }),\n );\n }\n\n if (xhrMod) {\n instrumentations.push(\n new xhrMod.XMLHttpRequestInstrumentation({\n applyCustomAttributesOnSpan: span => decorateSpan(context, span as DecoratableSpan, 'xhr'),\n ignoreUrls,\n propagateTraceHeaderCorsUrls,\n semconvStabilityOptIn: 'http',\n ignoreNetworkEvents: false,\n }),\n );\n }\n\n if (uiMod) {\n // typeof null === 'object' 是 JS 历史遗留坑;这里显式排除,防止外部传入 null 时取 .eventNames 抛错\n const userInteractionConfig =\n instrumentationOptions.userInteraction !== null && typeof instrumentationOptions.userInteraction === 'object'\n ? { eventNames: instrumentationOptions.userInteraction.eventNames as Array<keyof HTMLElementEventMap> }\n : undefined;\n instrumentations.push(new uiMod.UserInteractionInstrumentation(userInteractionConfig));\n }\n\n // 这里没有显式传 tracerProvider / meterProvider,依赖 BkOpenTelemetry.start() 此前已通过\n // tracerProvider.register() 与 setGlobalMeterProvider() 把 BkOT 的 provider 注册为 global,\n // 否则官方 instrumentation 会拿到 NoopProvider 导致数据丢失。\n // PluginManager.start 在 sdk.ts 中是在上述两步之后才调用,因此该顺序是稳定的。\n registerInstrumentations({ instrumentations });\n },\n shutdown() {\n while (instrumentations.length) {\n instrumentations.pop()?.disable?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 简易 djb2 字符串 hash。结果稳定、短(base36),足以区分常见 fingerprint 字符串组合。\n * 不是密码学安全 hash,仅用于事件聚合分组场景。\n */\nexport const hashString = (input: string): string => {\n let hash = 5381;\n for (let i = 0; i < input.length; i++) {\n hash = (hash * 33) ^ input.charCodeAt(i);\n }\n return (hash >>> 0).toString(36);\n};\n\nexport interface ThrottleResult {\n /** 是否允许本次上报 */\n allowed: boolean;\n /** 当前窗口内同 fingerprint 的总触发次数(含本次,含被丢弃次数),便于下游感知是否被节流 */\n count: number;\n}\n\ninterface ThrottleEntry {\n count: number;\n windowStart: number;\n}\n\n/**\n * 时间窗节流:同一 key 在 windowMs 窗口内最多允许 maxPerWindow 次通过;\n * 超出则被拦截,但 `count` 仍累加,便于下游感知风暴规模。\n *\n * 长会话场景下避免 records 无限累积——按窗口长度节奏轻量清理一次过期 entry。\n */\nexport const createThrottle = (windowMs: number, maxPerWindow: number) => {\n const records = new Map<string, ThrottleEntry>();\n let lastSweep = 0;\n const sweep = (now: number) => {\n if (now - lastSweep < windowMs) return;\n lastSweep = now;\n for (const [key, entry] of records) {\n if (now - entry.windowStart > windowMs) records.delete(key);\n }\n };\n return {\n allow(key: string): ThrottleResult {\n const now = Date.now();\n sweep(now);\n const entry = records.get(key);\n if (!entry || now - entry.windowStart > windowMs) {\n records.set(key, { count: 1, windowStart: now });\n return { allowed: true, count: 1 };\n }\n entry.count += 1;\n return { allowed: entry.count <= maxPerWindow, count: entry.count };\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { createThrottle, hashString } from '../core/throttle';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nconst CSP_THROTTLE_WINDOW_MS = 60_000;\nconst CSP_THROTTLE_MAX_PER_WINDOW = 5;\n\n// blockedURI 除了 URL 还可能是 'inline' / 'eval' / 'wasm-eval' / 'self' 等关键字(CSP spec),\n// 这些不是 URL,不应该走 redactUrl\nconst URL_LIKE_BLOCKED_URI = /^https?:|^\\/\\//i;\n\nconst redactBlockedUri = (redactUrl: (url: string) => string, blockedURI: string | undefined) => {\n if (!blockedURI) return undefined;\n return URL_LIKE_BLOCKED_URI.test(blockedURI) ? redactUrl(blockedURI) : blockedURI;\n};\n\n/**\n * CSP 违规事件采集插件,通过监听 `securitypolicyviolation` 事件捕获被 CSP 策略拦截的脚本/资源,\n * 以 error span 上报,进入统一错误分析口径。\n *\n * 节流策略:\n * - 同 fingerprint(`violatedDirective + blockedURI + sourceFile + lineNumber`)\n * 在 60s 窗口内最多上报 5 条,避免风暴打爆 collector(如某段被拦截的 inline 脚本被反复渲染)。\n * - 每条 attribute 带 `csp.window_count`,反映当前窗口内同 fingerprint 的总触发次数(含被丢弃的)。\n *\n * 体积优化:\n * - `csp.original_policy`(完整 CSP 策略,可能数 KB)仅在窗口首条上报,后续同 fingerprint 不再重复携带。\n *\n * URL 处理:\n * - `blockedURI` / `sourceFile` 中的 URL 类值会经 `redactUrl` 脱敏。\n * - `blockedURI` 是 'inline' / 'eval' 等 CSP 关键字时直接保留,不调用 redactUrl。\n */\nexport const createCspViolationPlugin = (): BkOTPlugin => {\n let teardown: (() => void) | undefined;\n\n return {\n name: 'csp-violation',\n init(context) {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n const throttle = createThrottle(CSP_THROTTLE_WINDOW_MS, CSP_THROTTLE_MAX_PER_WINDOW);\n\n const handler = (event: SecurityPolicyViolationEvent) => {\n const fingerprint = hashString(\n `${event.violatedDirective}|${event.blockedURI}|${event.sourceFile}|${event.lineNumber}`,\n );\n const result = throttle.allow(fingerprint);\n if (!result.allowed) return;\n\n const blockedUri = redactBlockedUri(context.config.hooks.redactUrl, event.blockedURI);\n const sourceFile = event.sourceFile ? context.config.hooks.redactUrl(event.sourceFile) : undefined;\n // 窗口首条带完整 originalPolicy 用于排查策略本身;后续同 fingerprint 省略,避免数 KB 字段反复传输\n const includeOriginalPolicy = result.count === 1;\n\n context\n .startSpan('csp.violation', {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'csp',\n eventLabel: 'CSP 错误',\n result: RUM_SPAN_RESULT.error,\n spanSubtype: 'csp',\n spanType: RUM_SPAN_TYPE.error,\n targetLabel: blockedUri ?? event.effectiveDirective,\n targetValue: event.statusCode,\n }),\n 'csp.blocked_uri': blockedUri,\n 'csp.violated_directive': event.violatedDirective,\n 'csp.effective_directive': event.effectiveDirective,\n 'csp.disposition': event.disposition,\n 'csp.source_file': sourceFile,\n 'csp.line_number': event.lineNumber,\n 'csp.column_number': event.columnNumber,\n 'csp.status_code': event.statusCode,\n 'csp.fingerprint': fingerprint,\n 'csp.window_count': result.count,\n [RUM_ATTRIBUTES.exceptionFingerprint]: fingerprint,\n 'error.source': 'csp',\n ...(includeOriginalPolicy ? { 'csp.original_policy': event.originalPolicy } : {}),\n })\n .end();\n };\n\n document.addEventListener('securitypolicyviolation', handler);\n teardown = () => document.removeEventListener('securitypolicyviolation', handler);\n },\n shutdown() {\n teardown?.();\n teardown = undefined;\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 生成随机 ID。优先用 `crypto.randomUUID`(standards-compliant UUID v4),不可用时回退到\n * `${prefix}-${timestamp}-${rand}` 形式。\n *\n * 注意:fallback 实现使用 Math.random,**不是密码学安全**,仅适用于设备/会话/事件标识等场景,\n * 不要复用此函数生成 token、CSRF 防护等安全相关的值。\n */\nexport const createRandomId = (prefix: string) => {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { createRandomId } from '../core/id';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { BkOTPlugin } from '../core/plugin';\nimport type { Attributes } from '@opentelemetry/api';\n\nexport interface BkOTDevicePluginOptions {\n storageKey?: string;\n}\n\nconst DEFAULT_DEVICE_STORAGE_KEY = 'BK_RUM_DEVICE_ID';\n// 用户拖窗口时 resize 高频触发,debounce 静默期内只在最后一次更新,避免无谓的 setRuntimeAttributes 风暴\nconst VIEWPORT_DEBOUNCE_MS = 250;\n\ninterface NetworkInformationLike {\n downlink?: number;\n effectiveType?: string;\n rtt?: number;\n saveData?: boolean;\n type?: string;\n addEventListener?: (type: 'change', listener: () => void) => void;\n removeEventListener?: (type: 'change', listener: () => void) => void;\n}\n\ninterface UserAgentDataLike {\n mobile?: boolean;\n platform?: string;\n}\n\nconst getConnection = (): NetworkInformationLike | undefined =>\n (\n navigator as Navigator & {\n connection?: NetworkInformationLike;\n }\n ).connection;\n\nconst getUserAgentData = (): undefined | UserAgentDataLike =>\n (\n navigator as Navigator & {\n userAgentData?: UserAgentDataLike;\n }\n ).userAgentData;\n\nconst getBrowserInfo = (ua: string) => {\n const matchers: Array<[string, RegExp]> = [\n ['Edge', /Edg\\/([\\d.]+)/],\n ['Chrome', /Chrome\\/([\\d.]+)/],\n ['Firefox', /Firefox\\/([\\d.]+)/],\n ['Safari', /Version\\/([\\d.]+).*Safari/],\n ];\n for (const [name, pattern] of matchers) {\n const matched = ua.match(pattern);\n if (matched?.[1]) {\n return { name, version: matched[1].split('.')[0] };\n }\n }\n return { name: 'Other', version: undefined };\n};\n\nconst getOsName = (ua: string, platform?: string) => {\n const value = `${platform ?? ''} ${ua}`;\n if (/iphone|ipad|ipod/i.test(value)) return 'iOS';\n if (/android/i.test(value)) return 'Android';\n if (/mac/i.test(value)) return 'macOS';\n if (/win/i.test(value)) return 'Windows';\n if (/linux/i.test(value)) return 'Linux';\n return 'Other';\n};\n\nconst readDeviceId = (storageKey: string) => {\n let existed: null | string = null;\n try {\n existed = window.localStorage.getItem(storageKey);\n } catch {\n /* localStorage 不可用(隐私模式 / sandbox iframe),下面会创建一个 in-memory id */\n }\n if (existed) {\n return existed;\n }\n const deviceId = createRandomId('device');\n try {\n window.localStorage.setItem(storageKey, deviceId);\n } catch {\n /* 写不进去也行,至少本次 SDK 生命周期内 deviceId 仍然稳定 */\n }\n return deviceId;\n};\n\n// 视口尺寸:随窗口大小 / 屏幕旋转变化,需要在 resize / orientationchange 时刷新\nconst getViewportAttributes = (): Attributes => {\n if (typeof window === 'undefined') {\n return {};\n }\n return {\n 'browser.viewport.width': window.innerWidth,\n 'browser.viewport.height': window.innerHeight,\n 'browser.screen.width': window.screen?.width,\n 'browser.screen.height': window.screen?.height,\n };\n};\n\n// 网络信息:随 connection.change 变化\nconst getNetworkAttributes = (): Attributes => {\n const connection = getConnection();\n if (!connection) {\n return {};\n }\n return {\n 'network.effective_type': connection.effectiveType,\n 'network.connection_type': connection.type,\n 'network.downlink': connection.downlink,\n 'network.rtt': connection.rtt,\n 'network.save_data': connection.saveData,\n };\n};\n\n// 设备硬件能力:进入页面时一次性读取,运行期不会变化\nconst getHardwareAttributes = (): Attributes => {\n if (typeof navigator === 'undefined') {\n return {};\n }\n const uaData = getUserAgentData();\n const ua = navigator.userAgent;\n const browser = getBrowserInfo(ua);\n const osName = getOsName(ua, uaData?.platform);\n const deviceType = uaData?.mobile || /mobi|android|iphone|ipad|ipod/i.test(ua) ? 'mobile' : 'desktop';\n return {\n 'device.memory': (navigator as Navigator & { deviceMemory?: number }).deviceMemory,\n 'device.cpu_cores': navigator.hardwareConcurrency,\n 'device.platform': uaData?.platform,\n 'device.mobile': uaData?.mobile,\n [RUM_ATTRIBUTES.browserName]: browser.name,\n [RUM_ATTRIBUTES.browserVersion]: browser.version,\n [RUM_ATTRIBUTES.deviceType]: deviceType,\n [RUM_ATTRIBUTES.osName]: osName,\n 'user_agent.name': browser.name,\n 'user_agent.version': browser.version,\n 'user_agent.os.name': osName,\n };\n};\n\n// trailing debounce:windowMs 静默期内的多次触发只执行最后一次。\n// 适合 resize 这类\"用户停下来后才需要拿到最终值\"的场景。\nconst createDebounce = (fn: () => void, windowMs: number) => {\n let timer: number | undefined;\n return () => {\n if (timer !== undefined) {\n window.clearTimeout(timer);\n }\n timer = window.setTimeout(() => {\n timer = undefined;\n fn();\n }, windowMs);\n };\n};\n\n/**\n * 设备元数据采集插件,把\"设备级\"信息写入 SDK 的 runtimeAttributes,\n * 让所有 log/span 都自动带上这些维度。\n *\n * 上报字段:\n * - `device.id`:设备级永久标识,存于 localStorage(默认 key 复用旧 session 插件以保留现网用户)。\n * - `device.memory` / `device.cpu_cores` / `device.platform` / `device.mobile`:硬件能力快照(运行期不变)。\n * - `browser.viewport.*` / `browser.screen.*`:视口与屏幕尺寸。\n * - `network.effective_type` / `network.connection_type` / `network.downlink` / `network.rtt` / `network.save_data`:\n * 网络信息。\n *\n * 实时更新:\n * - viewport 监听 `resize` / `orientationchange`,并经 250ms debounce 合并,避免拖窗口时高频触发;\n * 静默期结束后用最终值刷新 runtimeAttributes。\n * - network 监听 `connection.change`(本身低频,不需要 throttle)。\n * - 二者只更新 in-memory 的 runtimeAttributes,不会主动产生新的 span/log,cardinality 安全;\n * 但更新后续生成的 log/span 会带最新值。\n *\n * 与 session 插件的关系:device 跨会话持久(终身),session 有过期与续期(连续访问语义)。\n */\nexport const createDevicePlugin = (options: BkOTDevicePluginOptions = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const storageKey = options.storageKey ?? DEFAULT_DEVICE_STORAGE_KEY;\n\n return {\n name: 'device',\n init(context) {\n const deviceId = typeof window === 'undefined' ? createRandomId('device') : readDeviceId(storageKey);\n\n context.setRuntimeAttributes({\n 'device.id': deviceId,\n ...getHardwareAttributes(),\n ...getViewportAttributes(),\n ...getNetworkAttributes(),\n });\n\n if (typeof window === 'undefined') {\n return;\n }\n\n const refreshViewport = createDebounce(() => {\n context.setRuntimeAttributes(getViewportAttributes());\n }, VIEWPORT_DEBOUNCE_MS);\n window.addEventListener('resize', refreshViewport);\n window.addEventListener('orientationchange', refreshViewport);\n teardownCallbacks.push(\n () => window.removeEventListener('resize', refreshViewport),\n () => window.removeEventListener('orientationchange', refreshViewport),\n );\n\n const connection = getConnection();\n // NetworkInformation 实现了 EventTarget;旧浏览器或某些 polyfill 可能没有 addEventListener,做能力检测\n if (connection && typeof connection.addEventListener === 'function') {\n const refreshNetwork = () => {\n context.setRuntimeAttributes(getNetworkAttributes());\n };\n connection.addEventListener('change', refreshNetwork);\n teardownCallbacks.push(() => {\n connection.removeEventListener?.('change', refreshNetwork);\n });\n }\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * 浏览器错误采集插件,负责捕获 JS 运行时错误、未处理的 Promise rejection、资源加载失败三类错误。\n *\n * 上报策略:\n * - 错误分析主数据统一使用 `span_type=error` 的 error span。\n * - 不再为同一个错误额外 emit error log,避免后端错误数按 span/log 混算时重复。\n *\n * 节流策略:\n * - 以 source + message + stack 前 256 字符做 djb2 hash 作为 fingerprint。\n * - 同 fingerprint 在 windowMs(默认 60s)内最多上报 maxPerWindow(默认 5)条;超过的会被丢弃。\n * - 每条上报的 attribute 都带 `error.window_count`,反映当前窗口内同 fingerprint 的总触发次数(含被丢弃次数),\n * 用于下游识别错误风暴。\n *\n * 关键 attribute:\n * - `error.source`:错误来源,取值 `window.error` / `unhandledrejection` / `resource`。\n * - `exception.fingerprint`:节流分组用的指纹 hash,可用于聚合同类错误。\n * - `error.window_count`:当前节流窗口内同指纹的总触发次数。\n * - `error.cross_origin`:仅在跨域脚本错误(\"Script error.\")时为 true,便于过滤这类无 stack 噪声。\n * - 资源错误额外带 `url.full`(经 redactUrl 脱敏)与 `html.tag`。\n * - JS 错误额外带 `code.filepath` / `code.lineno` / `code.column`。\n */\n\nimport { SpanStatusCode } from '@opentelemetry/api';\n\nimport { createThrottle, hashString } from '../core/throttle';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\n\nexport interface BkOTErrorPluginOptions {\n // 同 hash 错误窗口内最多上报多少条,默认 5\n maxPerWindow?: number;\n // 节流窗口长度(ms),默认 60_000\n windowMs?: number;\n}\n\nconst DEFAULT_WINDOW_MS = 60_000;\nconst DEFAULT_MAX_PER_WINDOW = 5;\n\nconst positiveNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n\nconst getErrorMessage = (value: unknown) => {\n if (value instanceof Error) {\n return value.message;\n }\n if (typeof value === 'string') {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n};\n\nconst getErrorStack = (value: unknown) => (value instanceof Error ? (value.stack ?? '') : '');\n\nconst getMessageShort = (message: string) => message.slice(0, 80);\n\nconst getStackTopFrame = (stack: string) =>\n stack\n .split('\\n')\n .find(line => line.trim().startsWith('at '))\n ?.trim() ?? '';\n\nconst getErrorSubtype = (source: string) => {\n if (source === 'unhandledrejection') return 'promise';\n if (source === 'resource') return 'resource_load';\n return 'js';\n};\n\ninterface EmitErrorOptions {\n context: BkOTRuntimeContext;\n error: unknown;\n exceptionType?: string;\n extra?: Record<string, unknown>;\n source: string;\n spanName: string;\n throttle: ReturnType<typeof createThrottle>;\n}\n\nconst emitError = ({ context, error, exceptionType, extra = {}, source, spanName, throttle }: EmitErrorOptions) => {\n const message = getErrorMessage(error);\n const stack = getErrorStack(error);\n const throttleKey = hashString(`${source}|${message}|${stack.slice(0, 256)}`);\n\n const throttleResult = throttle.allow(throttleKey);\n if (!throttleResult.allowed) {\n return;\n }\n\n const exceptionLike = error instanceof Error ? error : { message, name: exceptionType ?? source };\n const attributes = {\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getErrorAttributes(),\n ...buildRumSpanAttributes({\n errorType: getErrorSubtype(source),\n eventLabel: '错误',\n result: RUM_SPAN_RESULT.error,\n spanSubtype: getErrorSubtype(source),\n spanType: RUM_SPAN_TYPE.error,\n targetLabel: error instanceof Error ? error.name : (exceptionType ?? source),\n }),\n 'exception.message': message,\n [RUM_ATTRIBUTES.exceptionMessageShort]: getMessageShort(message),\n 'exception.stacktrace': stack,\n [RUM_ATTRIBUTES.exceptionStackTopFrame]: getStackTopFrame(stack),\n 'exception.type': error instanceof Error ? error.name : (exceptionType ?? source),\n [RUM_ATTRIBUTES.exceptionFingerprint]: throttleKey,\n 'error.handled': false,\n 'error.source': source,\n // 当前窗口内同 fingerprint 的总触发次数(含被节流丢弃的次数),用于下游识别错误风暴\n 'error.window_count': throttleResult.count,\n ...extra,\n };\n // 错误分析统一沉淀为 error span;若当前存在 active context,OTel 会自动建立父子关系。\n const errorSpan = context.startSpan(spanName, attributes);\n errorSpan.recordException(exceptionLike);\n errorSpan.setStatus({ code: SpanStatusCode.ERROR, message });\n errorSpan.end();\n};\n\nexport const createErrorPlugin = (options: BkOTErrorPluginOptions = {}): BkOTPlugin => {\n const listeners: Array<() => void> = [];\n const windowMs = positiveNumber(options.windowMs, DEFAULT_WINDOW_MS);\n const maxPerWindow = positiveNumber(options.maxPerWindow, DEFAULT_MAX_PER_WINDOW);\n\n return {\n name: 'error',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const throttle = createThrottle(windowMs, maxPerWindow);\n\n const onError = (event: ErrorEvent) => {\n // 跨域脚本错误浏览器会屏蔽 stack/filename,message 通常是 \"Script error.\"\n const isCrossOrigin = !event.error && !event.filename && event.message === 'Script error.';\n emitError({\n context,\n throttle,\n spanName: 'browser.error',\n source: 'window.error',\n error: event.error ?? event.message,\n extra: {\n 'code.filepath': event.filename,\n 'code.lineno': event.lineno,\n 'code.column': event.colno,\n ...(isCrossOrigin ? { 'error.cross_origin': true } : {}),\n },\n });\n };\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n emitError({\n context,\n throttle,\n spanName: 'browser.unhandledrejection',\n source: 'unhandledrejection',\n error: event.reason,\n exceptionType: event.reason instanceof Error ? event.reason.name : 'UnhandledRejection',\n });\n };\n const onResourceError = (event: Event) => {\n // JS 运行时错误是 ErrorEvent,已由 bubble 阶段的 onError 处理;这里只关心资源加载失败\n if (event instanceof ErrorEvent) return;\n const target = event.target;\n if (!(target instanceof Element)) return;\n const elementWithUrl = target as Element & { href?: string; src?: string };\n const resourceUrl = elementWithUrl.src || elementWithUrl.href || (target as HTMLMediaElement).currentSrc || '';\n emitError({\n context,\n throttle,\n spanName: 'browser.resource_error',\n source: 'resource',\n error: new Error(`Resource load failed: ${target.tagName.toLowerCase()} ${resourceUrl}`),\n exceptionType: 'ResourceError',\n extra: {\n 'url.full': context.config.hooks.redactUrl(resourceUrl),\n 'html.tag': target.tagName,\n },\n });\n };\n\n window.addEventListener('error', onError);\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n window.addEventListener('error', onResourceError, true);\n listeners.push(\n () => window.removeEventListener('error', onError),\n () => window.removeEventListener('unhandledrejection', onUnhandledRejection),\n () => window.removeEventListener('error', onResourceError, true),\n );\n },\n shutdown() {\n while (listeners.length) {\n listeners.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * HTTP body 采集插件,通过 monkey-patch `window.fetch` 与 `XMLHttpRequest.prototype` 三个方法\n * 在请求出错(抛错或 4xx+)时上报请求/响应 body,便于线上问题诊断。\n *\n * 上报策略:\n * - 仅在 `isError` 时(fetch reject 或 status >= 400)才把 body 写入 attribute;\n * 成功请求只上报 method/url/duration/status 等元数据。\n * - 所有 body 上报前先通过 `redact(payload)` 自定义函数脱敏,避免泄漏敏感字段。\n * - body 大小超过 `maxBodySize`(默认 10KB)会截断,并通过 `http_body.*.truncated` 标记。\n *\n * 内存保护:\n * - response 在读取前通过 `content-length` 预判,超过 `maxBodySize * 10` 的响应直接放弃 body 内容\n * (仍上报 truncated=true 与元数据),避免把超大响应整体吃进内存再截断。\n *\n * XHR meta 存储:\n * - 使用模块级 WeakMap 关联 XHR 实例与 meta(method / url / startTime / requestHeaders / requestPromise / aborted),\n * 避免在 XMLHttpRequest 接口上挂全局类型扩展,且实例 GC 时自动释放。\n *\n * 多重 patch 兼容:\n * - shutdown 时仅在 `window.fetch` / XHR prototype 仍是自己 patch 的版本时才回退,\n * 避免抹掉后来者(如 axios、其它 RUM SDK)的 patch。\n *\n * 已知限制:\n * - fetch/XHR 的 request body 仅在错误路径懒序列化;高频错误场景仍需注意 body 处理成本。\n * - 与 OTel 官方 fetch instrumentation 共存时会有两层 patch,行为可叠加但 fetch 调用栈会更深。\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\n\nimport { shouldIgnoreUrl } from '../core/url';\nimport { buildRumHttpSpanAttributes } from '../schema/span';\n\nimport type { BkOTHttpBodyConfig, BkOTHttpBodyRedactPayload } from '../core/config';\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\nimport type { Attributes } from '@opentelemetry/api';\n\ninterface BodySnapshot {\n body: string;\n contentType?: string;\n truncated: boolean;\n}\n\ninterface ReportHttpBodyOptions {\n config: Required<BkOTHttpBodyConfig>;\n context: BkOTRuntimeContext;\n duration: number;\n error?: unknown;\n method: string;\n request?: BodySnapshot;\n response?: BodySnapshot;\n status?: number;\n subtype: 'fetch' | 'xhr';\n url: string;\n}\n\ninterface XhrRequestMeta {\n aborted?: boolean;\n method: string;\n networkError?: boolean;\n requestBody?: Document | null | XMLHttpRequestBodyInit;\n requestContentType?: string;\n requestHeaders?: Record<string, string>;\n startTime: number;\n url: string;\n}\n\nconst HTTP_BODY_MAX_SIZE = 10 * 1024;\n// 响应 content-length 超过 maxBodySize * 此倍数 时直接放弃读 body,避免把超大响应整体加载到内存\nconst RESPONSE_BODY_SIZE_GUARD_FACTOR = 10;\nconst identityRedactHttpBody = (payload: BkOTHttpBodyRedactPayload) => payload.body;\n\nconst positiveNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n\n// WeakMap 替代 XMLHttpRequest 上的实例字段:避免污染全局类型,且实例 GC 时自动释放 meta\nconst xhrMetaMap = new WeakMap<XMLHttpRequest, XhrRequestMeta>();\n\ntype HttpBodyInput = BodyInit | Document | null | undefined;\n\nconst truncateBody = (body: string, maxBodySize: number): Pick<BodySnapshot, 'body' | 'truncated'> => {\n if (body.length <= maxBodySize) {\n return {\n body,\n truncated: false,\n };\n }\n return {\n body: body.slice(0, maxBodySize),\n truncated: true,\n };\n};\n\nconst getHeaderValue = (headers: HeadersInit | undefined, key: string) => {\n if (!headers) {\n return undefined;\n }\n if (headers instanceof Headers) {\n return headers.get(key) || undefined;\n }\n const normalizedKey = key.toLowerCase();\n if (Array.isArray(headers)) {\n return headers.find(([itemKey]) => itemKey.toLowerCase() === normalizedKey)?.[1];\n }\n const matchedKey = Object.keys(headers).find(item => item.toLowerCase() === normalizedKey);\n return matchedKey ? headers[matchedKey] : undefined;\n};\n\nconst stringifyFormData = (body: FormData) => {\n const entries: Record<string, string> = {};\n body.forEach((value, key) => {\n entries[key] =\n value instanceof File ? `[File name=${value.name} type=${value.type || 'unknown'} size=${value.size}]` : value;\n });\n return JSON.stringify(entries);\n};\n\nconst stringifyBody = async (body: HttpBodyInput): Promise<string> => {\n if (body == null) {\n return '';\n }\n if (typeof body === 'string') {\n return body;\n }\n if (body instanceof URLSearchParams) {\n return body.toString();\n }\n if (body instanceof FormData) {\n return stringifyFormData(body);\n }\n if (body instanceof Blob) {\n return body.text();\n }\n if (body instanceof ArrayBuffer) {\n return new TextDecoder().decode(body);\n }\n if (ArrayBuffer.isView(body)) {\n return new TextDecoder().decode(body);\n }\n if (body instanceof Document) {\n return new XMLSerializer().serializeToString(body);\n }\n return String(body);\n};\n\nconst createBodySnapshot = async (\n body: HttpBodyInput,\n maxBodySize: number,\n contentType?: string,\n): Promise<BodySnapshot | undefined> => {\n const rawBody = await stringifyBody(body);\n if (!rawBody) {\n return undefined;\n }\n return {\n ...truncateBody(rawBody, maxBodySize),\n contentType,\n };\n};\n\nconst safeCreateBodySnapshot = async (body: HttpBodyInput, maxBodySize: number, contentType?: string) => {\n try {\n return await createBodySnapshot(body, maxBodySize, contentType);\n } catch {\n return undefined;\n }\n};\n\nconst getFetchInputUrl = (input: RequestInfo | URL) => {\n if (input instanceof Request) {\n return input.url;\n }\n return String(input);\n};\n\nconst getFetchMethod = (input: RequestInfo | URL, init?: RequestInit) => {\n if (init?.method) {\n return init.method;\n }\n if (input instanceof Request) {\n return input.method;\n }\n return 'GET';\n};\n\ntype BodySnapshotReader = () => Promise<BodySnapshot | undefined>;\n\nconst getFetchRequestBodyReader = (\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n maxBodySize: number,\n): BodySnapshotReader => {\n if (init?.body) {\n return () =>\n safeCreateBodySnapshot(\n init.body,\n maxBodySize,\n getHeaderValue(init.headers as Headers | undefined, 'content-type'),\n );\n }\n if (input instanceof Request) {\n // 为了极致性能,不为所有 Request 预 clone body;接入方如需 body 诊断建议通过 init.body 发起请求。\n return () => Promise.resolve(undefined);\n }\n return () => Promise.resolve(undefined);\n};\n\nconst getResponseBody = async (response: Response, maxBodySize: number) => {\n try {\n const contentType = response.headers.get('content-type') || undefined;\n // 用 content-length 预判,超出阈值直接降级为 truncated 占位,避免吃下整个超大响应\n const contentLengthHeader = response.headers.get('content-length');\n if (contentLengthHeader) {\n const contentLength = Number(contentLengthHeader);\n if (Number.isFinite(contentLength) && contentLength > maxBodySize * RESPONSE_BODY_SIZE_GUARD_FACTOR) {\n return {\n body: '',\n contentType,\n truncated: true,\n };\n }\n }\n const clonedResponse = response.clone();\n return safeCreateBodySnapshot(clonedResponse.body ? await clonedResponse.text() : '', maxBodySize, contentType);\n } catch {\n return undefined;\n }\n};\n\nconst getXhrResponseBody = (xhr: XMLHttpRequest, maxBodySize: number): BodySnapshot | undefined => {\n if (xhr.responseType === '' || xhr.responseType === 'text') {\n return {\n ...truncateBody(xhr.responseText || '', maxBodySize),\n contentType: xhr.getResponseHeader('content-type') || undefined,\n };\n }\n if (xhr.responseType === 'json') {\n return {\n ...truncateBody(JSON.stringify(xhr.response), maxBodySize),\n contentType: xhr.getResponseHeader('content-type') || undefined,\n };\n }\n return undefined;\n};\n\nconst redactBody = (\n config: Required<BkOTHttpBodyConfig>,\n payload: Omit<BkOTHttpBodyRedactPayload, 'body' | 'truncated'>,\n snapshot?: BodySnapshot,\n) => {\n if (!snapshot) {\n return undefined;\n }\n return config.redact({\n ...payload,\n body: snapshot.body,\n truncated: snapshot.truncated,\n });\n};\n\nconst reportHttpBody = ({\n config,\n context,\n duration,\n error,\n method,\n request,\n response,\n status,\n subtype,\n url,\n}: ReportHttpBodyOptions) => {\n const isError = Boolean(error) || (typeof status === 'number' && status >= 400);\n const normalizedMethod = method.toUpperCase();\n const requestBody = isError\n ? redactBody(\n config,\n {\n contentType: request?.contentType,\n method: normalizedMethod,\n status,\n type: 'request',\n url,\n },\n request,\n )\n : undefined;\n const responseBody = isError\n ? redactBody(\n config,\n {\n contentType: response?.contentType,\n method: normalizedMethod,\n status,\n type: 'response',\n url,\n },\n response,\n )\n : undefined;\n const attributes: Attributes = {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumHttpSpanAttributes({\n duration,\n method: normalizedMethod,\n statusCode: status,\n subtype,\n url: context.config.hooks.redactUrl(url),\n urlPathTemplate: context.config.hooks.getViewPathGroup(url),\n }),\n 'http.duration': duration,\n 'http.request.method': normalizedMethod,\n 'url.full': context.config.hooks.redactUrl(url),\n };\n\n if (typeof status === 'number') {\n attributes['http.response.status_code'] = status;\n }\n if (requestBody != null) {\n attributes['http.request.body'] = requestBody;\n }\n if (responseBody != null) {\n attributes['http.response.body'] = responseBody;\n }\n if (isError) {\n attributes['http_body.request.truncated'] = request?.truncated ?? false;\n attributes['http_body.response.truncated'] = response?.truncated ?? false;\n }\n if (error instanceof Error) {\n attributes['exception.message'] = error.message;\n attributes['exception.type'] = error.name;\n attributes['exception.stacktrace'] = error.stack ?? '';\n }\n\n context.emitLog({\n severityNumber: isError ? SeverityNumber.ERROR : SeverityNumber.INFO,\n severityText: isError ? 'ERROR' : 'INFO',\n body: isError ? 'HTTP request completed with error' : 'HTTP request completed',\n attributes,\n });\n};\n\nexport const createHttpBodyPlugin = (options: BkOTHttpBodyConfig = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const config: Required<BkOTHttpBodyConfig> = {\n maxBodySize: positiveNumber(options.maxBodySize, HTTP_BODY_MAX_SIZE),\n redact: options.redact ?? identityRedactHttpBody,\n };\n\n return {\n name: 'http-body',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const { maxBodySize } = config;\n\n // ===== fetch patch =====\n const originalFetch = window.fetch;\n if (typeof originalFetch === 'function') {\n const patchedFetch: typeof window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\n const url = getFetchInputUrl(input);\n if (shouldIgnoreUrl(context.config, url)) {\n return originalFetch(input, init);\n }\n const method = getFetchMethod(input, init);\n const getRequestBody = getFetchRequestBodyReader(input, init, maxBodySize);\n const startTime = performance.now();\n try {\n const response = await originalFetch(input, init);\n const duration = performance.now() - startTime;\n const isError = response.status >= 400;\n const request = isError ? await getRequestBody() : undefined;\n const responseBody = isError ? await getResponseBody(response, maxBodySize) : undefined;\n reportHttpBody({\n config,\n context,\n duration,\n method,\n request,\n response: responseBody,\n status: response.status,\n subtype: 'fetch',\n url,\n });\n return response;\n } catch (error) {\n const request = await getRequestBody();\n reportHttpBody({\n config,\n context,\n duration: performance.now() - startTime,\n error,\n method,\n request,\n subtype: 'fetch',\n url,\n });\n throw error;\n }\n };\n window.fetch = patchedFetch;\n teardownCallbacks.push(() => {\n // 仅当 window.fetch 仍是我们 patch 的版本时才回退,避免抹掉后来者(如 axios、其它 RUM SDK)的 patch\n if (window.fetch === patchedFetch) {\n window.fetch = originalFetch;\n }\n });\n }\n\n // ===== XMLHttpRequest patch =====\n const originalOpen = XMLHttpRequest.prototype.open;\n const originalSend = XMLHttpRequest.prototype.send;\n const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;\n\n const patchedOpen = function open(\n this: XMLHttpRequest,\n method: string,\n url: string | URL,\n ...args: [async?: boolean, username?: null | string, password?: null | string]\n ) {\n xhrMetaMap.set(this, {\n method,\n startTime: performance.now(),\n url: String(url),\n });\n return originalOpen.call(this, method, url, args[0] ?? true, args[1], args[2]);\n };\n\n const patchedSetRequestHeader = function setRequestHeader(this: XMLHttpRequest, name: string, value: string) {\n const meta = xhrMetaMap.get(this);\n if (meta) {\n // XHR 规范规定 header name 大小写不敏感、同名重复调用应追加(comma-separated)\n const lowerName = name.toLowerCase();\n const headers = meta.requestHeaders ?? {};\n const prev = headers[lowerName];\n headers[lowerName] = prev ? `${prev}, ${value}` : value;\n meta.requestHeaders = headers;\n }\n return originalSetRequestHeader.call(this, name, value);\n };\n\n const patchedSend = function send(this: XMLHttpRequest, body?: Document | null | XMLHttpRequestBodyInit) {\n const meta = xhrMetaMap.get(this);\n if (meta) {\n // 成功请求不解析 body;仅保留引用,错误路径再懒序列化。\n meta.requestBody = body;\n meta.requestContentType = meta.requestHeaders?.['content-type'];\n }\n // 区分用户主动 abort 与真实网络错误:前者不上报,后者按错误上报\n this.addEventListener(\n 'abort',\n () => {\n const m = xhrMetaMap.get(this);\n if (m) m.aborted = true;\n },\n { once: true },\n );\n this.addEventListener(\n 'error',\n () => {\n const m = xhrMetaMap.get(this);\n if (m) m.networkError = true;\n },\n { once: true },\n );\n this.addEventListener(\n 'loadend',\n () => {\n // addEventListener 期望 void 返回值,这里用 IIFE 包住异步逻辑,避免把 Promise 直接当作 listener 返回\n void (async () => {\n const m = xhrMetaMap.get(this);\n if (!m || shouldIgnoreUrl(context.config, m.url) || m.aborted) {\n return;\n }\n const isXhrError = m.networkError || this.status >= 400;\n const responseBody = isXhrError ? getXhrResponseBody(this, maxBodySize) : undefined;\n const request = isXhrError\n ? await safeCreateBodySnapshot(m.requestBody, maxBodySize, m.requestContentType)\n : undefined;\n reportHttpBody({\n config,\n context,\n duration: performance.now() - m.startTime,\n // status === 0 且非 abort 时构造一个占位 Error,让下游沿用统一的 isError 路径上报 body\n error: m.networkError ? new Error('XHR network error') : undefined,\n method: m.method,\n request,\n response: responseBody,\n status: this.status,\n subtype: 'xhr',\n url: m.url,\n });\n })();\n },\n { once: true },\n );\n return originalSend.call(this, body);\n };\n\n XMLHttpRequest.prototype.open = patchedOpen;\n XMLHttpRequest.prototype.setRequestHeader = patchedSetRequestHeader;\n XMLHttpRequest.prototype.send = patchedSend;\n teardownCallbacks.push(() => {\n // 同 fetch:仅当原型方法仍是自己 patch 的版本时才回退,避免破坏后续 patch 的库\n if (XMLHttpRequest.prototype.open === patchedOpen) {\n XMLHttpRequest.prototype.open = originalOpen;\n }\n if (XMLHttpRequest.prototype.setRequestHeader === patchedSetRequestHeader) {\n XMLHttpRequest.prototype.setRequestHeader = originalSetRequestHeader;\n }\n if (XMLHttpRequest.prototype.send === patchedSend) {\n XMLHttpRequest.prototype.send = originalSend;\n }\n });\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { getMetricViewAttributes } from '../core/telemetry-attributes';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport interface BkOTLongTaskPluginOptions {\n // 任务时长阈值(ms),低于该值的 longtask 不上报,默认 50\n threshold?: number;\n}\n\nconst DEFAULT_THRESHOLD_MS = 50;\n\nconst nonNegativeNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;\n\ninterface LongTaskAttribution {\n containerId?: string;\n containerName?: string;\n containerSrc?: string;\n containerType?: string;\n name?: string;\n}\n\ninterface LongTaskEntry extends PerformanceEntry {\n attribution?: LongTaskAttribution[];\n}\n\n/**\n * Long Task 监控插件,通过 PerformanceObserver 捕获主线程上 >= threshold(默认 50ms)的长任务,\n * 上报为 metric(counter + duration histogram),用于评估页面卡顿趋势。\n *\n * 使用建议:\n * - 默认关闭,建议在性能敏感模块按需开启。\n * - PerformanceObserver 使用 `buffered: true`,能拿到 observer 创建之前已发生的 longtask,避免漏掉早期阶段。\n *\n * 兼容性:\n * - 仅在 PerformanceObserver 与 `supportedEntryTypes` 包含 `longtask` 的浏览器上启用,否则 init 直接 return;\n * Firefox 当前不支持 longtask,会自动跳过。\n */\nexport const createLongTaskPlugin = (options: BkOTLongTaskPluginOptions = {}): BkOTPlugin => {\n let observer: PerformanceObserver | undefined;\n const threshold = nonNegativeNumber(options.threshold, DEFAULT_THRESHOLD_MS);\n\n return {\n name: 'long-task',\n init(context) {\n if (typeof PerformanceObserver === 'undefined') {\n return;\n }\n if (!PerformanceObserver.supportedEntryTypes?.includes('longtask')) {\n return;\n }\n\n const counter = context.meter.createCounter('browser.long_task.count', {\n description: 'Number of long tasks observed (duration >= threshold)',\n });\n const durationHistogram = context.meter.createHistogram('browser.long_task.duration', {\n unit: 'ms',\n description: 'Duration of long tasks',\n });\n\n try {\n observer = new PerformanceObserver(list => {\n // 一次回调可能带多个 entry,把不随 entry 变化的公共属性提到循环外只算一次\n const baseDimensions = context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...getMetricViewAttributes(context.getRuntimeAttributes()),\n });\n for (const entry of list.getEntries() as LongTaskEntry[]) {\n if (entry.duration < threshold) {\n continue;\n }\n const attribution = entry.attribution?.[0];\n const dimensions = {\n ...baseDimensions,\n [RUM_ATTRIBUTES.longtaskAttributionScript]: attribution?.containerSrc ?? attribution?.name ?? 'unknown',\n };\n const result = entry.duration > 100 ? RUM_SPAN_RESULT.warning : RUM_SPAN_RESULT.success;\n const span = context.startSpan(\n 'browser.long_task',\n {\n ...baseDimensions,\n ...buildRumSpanAttributes({\n duration: entry.duration,\n errorType: result === RUM_SPAN_RESULT.warning ? 'longtask_blocking' : 'none',\n eventLabel: '长任务',\n result,\n spanSubtype: attribution?.name ?? 'unknown',\n spanType: RUM_SPAN_TYPE.longtask,\n targetLabel:\n attribution?.containerSrc ?? attribution?.containerName ?? attribution?.name ?? 'unknown',\n targetValue: entry.duration,\n }),\n [RUM_ATTRIBUTES.longtaskBlockingDuration]: entry.duration,\n [RUM_ATTRIBUTES.longtaskAttributionScript]: attribution?.containerSrc ?? attribution?.name ?? 'unknown',\n },\n { startTime: performance.timeOrigin + entry.startTime },\n );\n span.end(performance.timeOrigin + entry.startTime + entry.duration);\n counter.add(1, dimensions);\n durationHistogram.record(entry.duration, dimensions);\n }\n });\n observer.observe({ type: 'longtask', buffered: true });\n } catch {\n observer = undefined;\n }\n },\n shutdown() {\n observer?.disconnect();\n observer = undefined;\n },\n };\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nexport interface RouteChangeEvent {\n fromUrl: string;\n source: RouteChangeSource;\n toUrl: string;\n}\n\nexport type RouteChangeSource = 'hashchange' | 'popstate' | 'pushState' | 'replaceState';\n\ntype RouteChangeHandler = (event: RouteChangeEvent) => void;\n\nconst subscribers = new Set<RouteChangeHandler>();\n\nlet lastUrl = '';\nlet originalPushState: typeof history.pushState | undefined;\nlet originalReplaceState: typeof history.replaceState | undefined;\nlet patched = false;\n\nconst getCurrentUrl = () => (typeof location === 'undefined' ? '' : location.href);\n\nconst emitRouteChange = (source: RouteChangeSource, fromUrl: string) => {\n const toUrl = getCurrentUrl();\n lastUrl = toUrl;\n const event: RouteChangeEvent = { fromUrl, source, toUrl };\n\n for (const subscriber of Array.from(subscribers)) {\n subscriber(event);\n }\n};\n\nconst onPopState = () => {\n emitRouteChange('popstate', lastUrl || getCurrentUrl());\n};\n\nconst onHashChange = () => {\n emitRouteChange('hashchange', lastUrl || getCurrentUrl());\n};\n\nconst patchHistory = () => {\n if (patched || typeof window === 'undefined' || typeof history === 'undefined') {\n return;\n }\n\n patched = true;\n lastUrl = getCurrentUrl();\n originalPushState = history.pushState;\n originalReplaceState = history.replaceState;\n\n history.pushState = function patchedPushState(this: History, data: any, title: string, url?: null | string | URL) {\n const fromUrl = lastUrl || getCurrentUrl();\n const result = originalPushState?.apply(this, [data, title, url]);\n emitRouteChange('pushState', fromUrl);\n return result;\n };\n history.replaceState = function patchedReplaceState(\n this: History,\n data: any,\n title: string,\n url?: null | string | URL,\n ) {\n const fromUrl = lastUrl || getCurrentUrl();\n const result = originalReplaceState?.apply(this, [data, title, url]);\n emitRouteChange('replaceState', fromUrl);\n return result;\n };\n window.addEventListener('popstate', onPopState);\n window.addEventListener('hashchange', onHashChange);\n};\n\nconst restoreHistory = () => {\n if (!patched || typeof window === 'undefined' || typeof history === 'undefined') {\n return;\n }\n\n if (originalPushState) {\n history.pushState = originalPushState;\n }\n if (originalReplaceState) {\n history.replaceState = originalReplaceState;\n }\n window.removeEventListener('popstate', onPopState);\n window.removeEventListener('hashchange', onHashChange);\n originalPushState = undefined;\n originalReplaceState = undefined;\n patched = false;\n lastUrl = '';\n};\n\nexport const subscribeRouteChange = (handler: RouteChangeHandler) => {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return () => {};\n }\n\n patchHistory();\n subscribers.add(handler);\n\n return () => {\n subscribers.delete(handler);\n if (subscribers.size === 0) {\n restoreHistory();\n }\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Page View 插件\n *\n * 监听浏览器 URL 变化并上报 page_view 事件,覆盖:\n * - 初始页面加载(source: 'load')\n * - SPA 路由切换:history.pushState / replaceState / popstate / hashchange\n *\n * 上报形态:\n * - 使用 span(browser.page_view)而非 log,立即 end()。\n * 这是有意的设计取舍:page_view 是一次瞬时事件并无 duration,但用 span 表达\n * 能让它成为 trace 视图的天然\"根节点\",与后续 fetch / xhr / user-interaction\n * span 在同一时间线上对齐,方便排查\"某次访问触发了哪些请求\"。\n * 代价是部分后端会把 0ms span 显示成奇怪的细线,可接受。\n *\n * 同 URL 去重:\n * 路由库(vue-router、react-router)常常会对相同 URL 重复 push(query/hash 微调或\n * 规范化),插件按完整 URL 字符串去重,避免重复上报。\n *\n * 关键属性:\n * - view.url:当前页面 URL(经 redactUrl 脱敏)\n * - url.previous:跳转前的 URL(来自 route-observer 的 fromUrl,初始 load 为空字符串)\n * - document.referrer:仅 'load' 事件携带\n * - event.source:触发源(load / pushState / replaceState / popstate / hashchange)\n */\n\nimport { createRandomId } from '../core/id';\nimport { subscribeRouteChange } from '../core/route-observer';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\nimport type { Span } from '@opentelemetry/api';\n\nconst getCurrentUrl = () => {\n if (typeof location === 'undefined') {\n return '';\n }\n return location.href;\n};\n\nexport const createPageViewPlugin = (): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n\n return {\n name: 'page-view',\n init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n // 同 URL 去重:路由库常常因 query/hash 微调反复 push 同一 URL,去重避免重复上报\n let lastUrl = '';\n let activeViewSpan: Span | undefined;\n\n const emitPageView = (source: string, previousUrl = lastUrl) => {\n const currentUrl = getCurrentUrl();\n if (currentUrl === lastUrl) {\n return;\n }\n activeViewSpan?.setAttribute('view.end_reason', source);\n activeViewSpan?.end();\n\n lastUrl = currentUrl;\n const viewId = createRandomId('view');\n const pathGroup = context.config.hooks.getViewPathGroup(currentUrl);\n const loadingType = source === 'load' ? 'initial_load' : 'route_change';\n const redactedCurrentUrl = context.config.hooks.redactUrl(currentUrl);\n const redactedPreviousUrl = context.config.hooks.redactUrl(previousUrl);\n\n context.setView({\n id: viewId,\n loadingType,\n url: redactedCurrentUrl,\n urlPathGroup: pathGroup,\n });\n\n // referrer 仅在初始 load 时携带,SPA 内路由切换由 url.previous 表达\n const isInitialLoad = source === 'load';\n const viewAttributes = {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: isInitialLoad ? '文档加载' : '路由切换',\n spanSubtype: isInitialLoad ? 'navigate' : source,\n spanType: isInitialLoad ? RUM_SPAN_TYPE.document : RUM_SPAN_TYPE.route,\n targetLabel: pathGroup,\n traceScene: isInitialLoad ? 'page_load' : 'route_change',\n }),\n [RUM_ATTRIBUTES.viewId]: viewId,\n [RUM_ATTRIBUTES.viewUrl]: redactedCurrentUrl,\n [RUM_ATTRIBUTES.viewUrlPathGroup]: pathGroup,\n [RUM_ATTRIBUTES.viewLoadingType]: loadingType,\n 'url.previous': redactedPreviousUrl,\n 'event.source': source,\n ...(isInitialLoad ? { 'document.referrer': context.config.hooks.redactUrl(document.referrer || '') } : {}),\n };\n activeViewSpan = context.startSpan('browser.view', viewAttributes);\n const span = context.startSpan('browser.page_view', {\n ...viewAttributes,\n });\n span.end();\n };\n\n const unsubscribe = subscribeRouteChange(event => {\n emitPageView(event.source, event.fromUrl);\n });\n emitPageView('load');\n\n const endActiveView = () => {\n activeViewSpan?.setAttribute('view.end_reason', 'shutdown');\n activeViewSpan?.end();\n activeViewSpan = undefined;\n };\n window.addEventListener('pagehide', endActiveView);\n\n teardownCallbacks.push(unsubscribe, () => window.removeEventListener('pagehide', endActiveView), endActiveView);\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport { shouldIgnoreUrl } from '../core/url';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumResourceSpanAttributes } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\ntype ResourceTimingWithStatus = PerformanceResourceTiming & {\n responseStatus?: number;\n};\n\nconst getCacheHit = (entry: PerformanceResourceTiming) =>\n entry.transferSize === 0 && entry.decodedBodySize > 0 ? true : undefined;\n\nexport const createResourcePlugin = (): BkOTPlugin => {\n let observer: PerformanceObserver | undefined;\n const seen = new Set<string>();\n\n return {\n name: 'resource',\n init(context) {\n if (typeof PerformanceObserver === 'undefined') {\n return;\n }\n if (!PerformanceObserver.supportedEntryTypes?.includes('resource')) {\n return;\n }\n\n const emitEntry = (entry: ResourceTimingWithStatus) => {\n if (!entry.name || shouldIgnoreUrl(context.config, entry.name)) {\n return;\n }\n const dedupeKey = `${entry.name}|${entry.startTime}`;\n if (seen.has(dedupeKey)) {\n return;\n }\n seen.add(dedupeKey);\n\n const redactedUrl = context.config.hooks.redactUrl(entry.name);\n const duration = entry.duration;\n const span = context.startSpan(\n 'browser.resource',\n {\n ...context.config.hooks.getPageAttributes(),\n ...buildRumResourceSpanAttributes({\n duration,\n initiatorType: entry.initiatorType || 'other',\n responseStatus: entry.responseStatus,\n transferSize: entry.transferSize,\n url: redactedUrl,\n urlPathTemplate: context.config.hooks.getViewPathGroup(entry.name),\n }),\n 'url.full': redactedUrl,\n [RUM_ATTRIBUTES.transferSize]: entry.transferSize,\n [RUM_ATTRIBUTES.cacheHit]: getCacheHit(entry),\n [RUM_ATTRIBUTES.nextHopProtocol]: entry.nextHopProtocol,\n initiator_type: entry.initiatorType || 'other',\n 'resource.encoded_body_size': entry.encodedBodySize,\n 'resource.decoded_body_size': entry.decodedBodySize,\n },\n { startTime: performance.timeOrigin + entry.startTime },\n );\n span.end(performance.timeOrigin + entry.startTime + duration);\n };\n\n try {\n observer = new PerformanceObserver(list => {\n for (const entry of list.getEntries() as ResourceTimingWithStatus[]) {\n emitEntry(entry);\n }\n });\n observer.observe({ type: 'resource', buffered: true });\n } catch {\n observer = undefined;\n }\n },\n shutdown() {\n observer?.disconnect();\n observer = undefined;\n seen.clear();\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Route Timing 插件\n *\n * 估算 SPA 路由切换的\"路由变更 → 渲染完成\"耗时。不依赖具体框架,作为通用兜底方案;\n * 如需更精确的耗时(含异步数据加载),业务可走自定义 plugin 在框架钩子内 startSpan/end。\n *\n * 估算策略:\n * 订阅 route-observer 的路由变化事件 → 记录起始时刻 → 等待 raf + raf + setTimeout(0)\n * 后视为\"渲染完成\"。双 raf 让浏览器至少完成一帧 paint,setTimeout 再覆盖大多数同步\n * 组件挂载的尾巴。\n *\n * 双轨上报:\n * - metric (histogram `browser.route_change.duration`):维度只带 page + `view.url_path_group` + source(不带完整 URL),\n * 适合做 P50 / P99 / 平均耗时的聚合统计,避免 URL 级高基数。\n * - span (`browser.route_change`):携带 url.full / url.previous,方便个例 trace 排查。\n * span 通过 startTime 选项回填到路由变更瞬间,end 时为 finalize 时刻,使 trace 视图里\n * 能看到正确的时间窗,与并发的 fetch / xhr span 在同一时间线上对齐。\n *\n * 已知局限:\n * - tab 切到后台:raf 会被浏览器节流甚至暂停,duration 会被严重高估。\n * 策略:finalize 时若 document.visibilityState === 'hidden' 直接丢弃这条数据,\n * 宁可少报也不污染 metric P99。\n * - 频繁路由切换:100ms 内连续 push A→B→C 时,每次切换都会启动独立 measure,\n * 早期记录的 duration 会被后续渲染拖长(含被覆盖路由的渲染时间)。当前实现\"全报\",\n * 保留所有切换记录,下游分析时需注意这个语义。\n * - 初始 page load:route-observer 不为初始加载触发事件,本插件不覆盖;\n * 首屏耗时由 navigation timing / web-vitals 插件负责。\n *\n * 生命周期:\n * shutdown 后通过 disposed 标志位拦截 pending 的 raf+setTimeout 回调,\n * 避免在已销毁的 provider 上 record/startSpan 造成数据逃逸。\n * raf/setTimeout 句柄不主动取消(成本低,回调内 early return 即可)。\n */\n\nimport { subscribeRouteChange } from '../core/route-observer';\nimport { getMetricViewAttributes } from '../core/telemetry-attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\n\nexport const createRouteTimingPlugin = (): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n let disposed = false;\n\n return {\n name: 'route-timing',\n init(context) {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return;\n }\n\n const histogram = context.meter.createHistogram('browser.route_change.duration', {\n unit: 'ms',\n description: 'Estimated SPA route change duration (route start to next idle)',\n });\n\n const measure = (source: string, fromUrl: string, toUrl: string) => {\n if (disposed) return;\n // 用 performance.now() 计算 duration,再换算成 epoch 毫秒作为 span 的 startTime,\n // 让 span 时间窗对齐真实的路由变更瞬间\n const startedAt = performance.now();\n const startedAtEpochMs = performance.timeOrigin + startedAt;\n\n const finalize = () => {\n if (disposed) return;\n // tab 切到后台时 raf 会暂停,回到前台触发的 finalize 拿到的 duration 会包含切走时间,\n // 直接丢弃这条数据,避免污染 metric 的 P99 / max\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n return;\n }\n const duration = performance.now() - startedAt;\n const result = duration > 500 ? RUM_SPAN_RESULT.warning : RUM_SPAN_RESULT.success;\n const dimensions = context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...getMetricViewAttributes(context.getRuntimeAttributes()),\n 'route.change.source': source,\n });\n histogram.record(duration, dimensions);\n const span = context.startSpan(\n 'browser.route_change',\n {\n ...dimensions,\n ...buildRumSpanAttributes({\n duration,\n errorType: result === RUM_SPAN_RESULT.warning ? 'slow' : 'none',\n eventLabel: '路由切换',\n result,\n spanSubtype: source,\n spanType: RUM_SPAN_TYPE.route,\n targetLabel: context.config.hooks.getViewPathGroup(toUrl),\n targetValue: duration,\n traceScene: 'route_change',\n }),\n 'url.full': context.config.hooks.redactUrl(toUrl),\n 'url.previous': context.config.hooks.redactUrl(fromUrl),\n },\n { startTime: startedAtEpochMs },\n );\n span.end();\n };\n // 双 raf 等到下一帧渲染后,再补一个宏任务,覆盖大多数同步组件挂载场景\n requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(finalize, 0)));\n };\n\n const unsubscribe = subscribeRouteChange(event => {\n if (event.fromUrl === event.toUrl) {\n return;\n }\n measure(event.source, event.fromUrl, event.toUrl);\n });\n\n teardownCallbacks.push(unsubscribe);\n },\n shutdown() {\n disposed = true;\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Session 插件\n *\n * 维护会话级标识 `session.id`,反映用户的\"连续访问\"语义。与 device 插件互补:\n * - device.id:设备终身标识(localStorage 持久),跨会话不变\n * - session.id:本次连续会话标识,过期后轮换\n *\n * 过期策略(满足任一条件触发轮换):\n * - inactivityMs:用户不活跃超过 N ms,默认 30 分钟\n * - maxLifetimeMs:会话总时长超过 N ms(即使一直活跃),默认 24 小时\n *\n * 续期触发:\n * - 用户活动事件(click / keydown / scroll / mousemove / touchstart)throttle 60s 一次\n * 解决\"用户持续在前台浏览,visibility 不变化导致 inactivityMs 永远不刷新\"的语义问题\n * - visibilitychange → visible 时刷新一次,处理\"切回前台\"场景\n *\n * 跨 tab 行为(共享语义):\n * - 读取优先 localStorage(跨 tab 最新视角),fallback sessionStorage\n * - 写入双写 localStorage + sessionStorage\n * - 监听 storage 事件,其他 tab 写入时实时同步本 tab 的 session.id(不重复 emit 事件)\n * - 已知边界:跨 tab 短窗口内并发判过期可能产生不同 sessionId,下游分析需注意\n *\n * 会话事件(log):\n * - session.start:首次新建会话\n * - session.rotate:因 inactivityMs / maxLifetimeMs 触发轮换,含 session.previous_id 与 reason\n * - 跨 tab storage 同步过来的 sessionId 变化不再重复 emit(源 tab 已发)\n *\n * 默认 storageKey:bk_ot_session__<endpoint host hash>,避免同域多个独立应用共用 session\n */\n\nimport { SeverityNumber } from '@opentelemetry/api-logs';\n\nimport { createRandomId } from '../core/id';\nimport { hashString } from '../core/throttle';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\n\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\n\nexport interface BkOTSessionPluginOptions {\n // 不活跃多久后视为新会话,默认 30 分钟\n inactivityMs?: number;\n // 会话最大持续时间,超过则强制轮换(即使用户一直活跃),默认 24 小时\n maxLifetimeMs?: number;\n // 自定义 storage key;不传则按 endpoint host 自动生成隔离后缀\n storageKey?: string;\n}\n\nconst DEFAULT_INACTIVITY_MS = 30 * 60 * 1000;\nconst DEFAULT_MAX_LIFETIME_MS = 24 * 60 * 60 * 1000;\nconst ACTIVITY_THROTTLE_MS = 60_000;\n// 允许 ts 在未来 5 秒内(容忍系统时钟微小回拨 / 多 tab 时钟差),超出则视为非法记录\nconst FUTURE_CLOCK_TOLERANCE_MS = 5_000;\nconst ACTIVITY_EVENTS: Array<keyof WindowEventMap> = ['click', 'keydown', 'scroll', 'mousemove', 'touchstart'];\n\nconst positiveNumber = (value: number | undefined, fallback: number) =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n\ninterface ResolveResult {\n id: string;\n prevId?: string;\n reason?: SessionRotateReason;\n // true 表示本次是新建/轮换;false 表示复用既有会话\n rotated: boolean;\n}\n\ninterface SessionRecord {\n id: string;\n // 会话开始时间,用于 maxLifetimeMs 判定\n startedAt: number;\n // 上次活跃时间戳\n ts: number;\n}\n\ntype SessionRotateReason = 'inactivity' | 'init' | 'maxLifetime';\n\nconst isValidTs = (value: unknown, now: number): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 && value <= now + FUTURE_CLOCK_TOLERANCE_MS;\n\nconst parseRecord = (raw: null | string, now: number): null | SessionRecord => {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as SessionRecord;\n if (typeof parsed?.id === 'string' && isValidTs(parsed.ts, now) && isValidTs(parsed.startedAt, now)) {\n return parsed;\n }\n } catch {\n /* JSON 损坏视为无效记录,下方会重新生成 */\n }\n return null;\n};\n\nconst readRecord = (storageKey: string, now: number): null | SessionRecord => {\n if (typeof window === 'undefined') return null;\n // localStorage 优先:反映跨 tab 最新视角;同 tab 刷新场景下两者通常一致\n let raw: null | string = null;\n try {\n raw = window.localStorage.getItem(storageKey);\n } catch {\n /* storage 不可用时视为无记录 */\n }\n let record = parseRecord(raw, now);\n if (record) return record;\n try {\n raw = window.sessionStorage.getItem(storageKey);\n } catch {\n /* storage 不可用时视为无记录 */\n }\n record = parseRecord(raw, now);\n return record;\n};\n\nconst writeRecord = (storageKey: string, record: SessionRecord) => {\n if (typeof window === 'undefined') return;\n const serialized = JSON.stringify(record);\n // sessionStorage 与 localStorage 分别 try/catch:一个失败不影响另一个\n try {\n window.sessionStorage.setItem(storageKey, serialized);\n } catch {\n /* ignore */\n }\n try {\n window.localStorage.setItem(storageKey, serialized);\n } catch {\n /* ignore */\n }\n};\n\nconst resolveSessionId = (storageKey: string, inactivityMs: number, maxLifetimeMs: number): ResolveResult => {\n const now = Date.now();\n const existed = readRecord(storageKey, now);\n if (existed) {\n const inactive = now - existed.ts >= inactivityMs;\n const tooOld = now - existed.startedAt >= maxLifetimeMs;\n if (!inactive && !tooOld) {\n const refreshed: SessionRecord = { ...existed, ts: now };\n writeRecord(storageKey, refreshed);\n return { id: refreshed.id, rotated: false };\n }\n // inactive 与 tooOld 都成立时优先记录 inactivity(业务上更直观)\n const reason: SessionRotateReason = inactive ? 'inactivity' : 'maxLifetime';\n const rotated: SessionRecord = { id: createRandomId('session'), startedAt: now, ts: now };\n writeRecord(storageKey, rotated);\n return { id: rotated.id, prevId: existed.id, reason, rotated: true };\n }\n const created: SessionRecord = { id: createRandomId('session'), startedAt: now, ts: now };\n writeRecord(storageKey, created);\n return { id: created.id, reason: 'init', rotated: true };\n};\n\n// 默认 storageKey 带 endpoint host hash,避免同域多个独立应用共用 session\nconst buildDefaultStorageKey = (endpoint: string) => {\n let suffix = '';\n try {\n suffix = new URL(endpoint).host;\n } catch {\n suffix = endpoint;\n }\n return `bk_ot_session__${hashString(suffix || 'default')}`;\n};\n\nconst emitSessionEvent = (context: BkOTRuntimeContext, result: ResolveResult) => {\n if (!result.rotated || !result.reason) return;\n const now = Date.now();\n context.emitLog({\n severityNumber: SeverityNumber.INFO,\n severityText: 'INFO',\n body: result.reason === 'init' ? 'session.start' : 'session.rotate',\n attributes: {\n ...context.config.hooks.getPageAttributes(),\n [RUM_ATTRIBUTES.sessionHasReplay]: false,\n [RUM_ATTRIBUTES.sessionId]: result.id,\n [RUM_ATTRIBUTES.sessionStartTime]: now,\n 'session.rotate.reason': result.reason,\n ...(result.prevId ? { 'session.previous_id': result.prevId } : {}),\n },\n });\n};\n\nexport const createSessionPlugin = (options: BkOTSessionPluginOptions = {}): BkOTPlugin => {\n const teardownCallbacks: Array<() => void> = [];\n const inactivityMs = positiveNumber(options.inactivityMs, DEFAULT_INACTIVITY_MS);\n const maxLifetimeMs = positiveNumber(options.maxLifetimeMs, DEFAULT_MAX_LIFETIME_MS);\n\n return {\n name: 'session',\n init(context) {\n const storageKey = options.storageKey ?? buildDefaultStorageKey(context.config.transport.endpoint);\n\n // 当前进程持有的 sessionId 快照,用于:\n // 1. 判定 storage 同步事件是否真的带来变化(避免无谓 emit)\n // 2. 做 user activity throttle 时无需访问 storage\n let currentSessionId = '';\n\n const refresh = () => {\n if (typeof window === 'undefined') {\n // SSR 兜底:直接生成一个一次性 id,不持久化\n if (!currentSessionId) {\n currentSessionId = createRandomId('session');\n context.setRuntimeAttributes({\n [RUM_ATTRIBUTES.sessionHasReplay]: false,\n [RUM_ATTRIBUTES.sessionId]: currentSessionId,\n });\n }\n return;\n }\n const result = resolveSessionId(storageKey, inactivityMs, maxLifetimeMs);\n context.setRuntimeAttributes({\n [RUM_ATTRIBUTES.sessionHasReplay]: false,\n [RUM_ATTRIBUTES.sessionId]: result.id,\n });\n if (result.id !== currentSessionId) {\n currentSessionId = result.id;\n // 仅当 resolve 内部判定为新建/轮换时才 emit;复用既有会话静默\n emitSessionEvent(context, result);\n }\n };\n\n refresh();\n\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return;\n }\n\n // user activity 节流续期:解决\"持续 visible 时 inactivityMs 永不刷新\"的语义问题\n let lastTouchAt = Date.now();\n const touch = () => {\n const now = Date.now();\n if (now - lastTouchAt < ACTIVITY_THROTTLE_MS) return;\n lastTouchAt = now;\n refresh();\n };\n // capture: true 让事件在捕获阶段就被感知,不受业务 stopPropagation 影响\n const activityListenerOptions: AddEventListenerOptions = { capture: true, passive: true };\n ACTIVITY_EVENTS.forEach(evt => {\n window.addEventListener(evt, touch, activityListenerOptions);\n });\n teardownCallbacks.push(() => {\n ACTIVITY_EVENTS.forEach(evt => {\n window.removeEventListener(evt, touch, activityListenerOptions);\n });\n });\n\n // 切回前台时刷新一次,处理\"后台 N 分钟回来\"场景\n const onVisibilityChange = () => {\n if (document.visibilityState === 'visible') {\n refresh();\n }\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n teardownCallbacks.push(() => document.removeEventListener('visibilitychange', onVisibilityChange));\n\n // 跨 tab storage 同步:其他 tab 写入新 sessionId 时本 tab 实时跟进\n // 不调用 refresh / emitSessionEvent,避免 N tab 同时 emit N 条事件\n const onStorage = (event: StorageEvent) => {\n if (event.storageArea !== window.localStorage) return;\n if (event.key !== storageKey) return;\n if (!event.newValue) return;\n const now = Date.now();\n const parsed = parseRecord(event.newValue, now);\n if (!parsed || parsed.id === currentSessionId) return;\n currentSessionId = parsed.id;\n // 同步到当前 tab 的 sessionStorage,保持两个 storage 一致\n try {\n window.sessionStorage.setItem(storageKey, event.newValue);\n } catch {\n /* ignore */\n }\n context.setRuntimeAttributes({ [RUM_ATTRIBUTES.sessionId]: parsed.id });\n };\n window.addEventListener('storage', onStorage);\n teardownCallbacks.push(() => window.removeEventListener('storage', onStorage));\n },\n shutdown() {\n while (teardownCallbacks.length) {\n teardownCallbacks.pop()?.();\n }\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * Web Vitals 插件\n *\n * 基于 google/web-vitals 库(attribution 版本)采集 Core Web Vitals:\n * CLS / FCP / INP / LCP / TTFB\n *\n * 维度分层(关键设计):\n * - metric (histogram):仅带低基数维度(`view.url_path_group` / navigation.type / vital.name / vital.rating),\n * 适合做 P50 / P75 / P99 聚合统计,避免维度爆炸\n * - span (browser.web_vital):带 vital.id(唯一)、vital.value 与 attribution 详情\n * (LCP 元素 url / CLS shift target / INP interaction target 等高基数字段),\n * 方便按个例排查\"哪个元素拖累了 LCP\"\n *\n * Histogram 设计:\n * - browser.web_vital.cls:单位 '1'(无量纲),CLS 是累积分数,不能与 ms 混用\n * - browser.web_vital.duration:单位 'ms',FCP/INP/LCP/TTFB 共享,靠 vital.metric 维度区分\n *\n * Navigation Type:\n * 优先使用 metric.navigationType(web-vitals 内部计算,含 'back-forward-cache' 等准确值),\n * fallback 到 PerformanceNavigationTiming.type(注意 web-vitals 用连字符 'back-forward',\n * PerformanceNavigationTiming 用下划线 'back_forward',统一成连字符)。\n *\n * Page Lifecycle 行为(来自 web-vitals 库的设计):\n * - LCP / INP / CLS 默认仅在 pagehide / visibilitychange→hidden 时触发\"最终值\"回调\n * - 如果 SDK 在 pagehide 之前 shutdown,该 page 的最终值会丢失(无法解决,库本身不暴露 unsubscribe)\n * - SPA 路由切换不会重置 web vitals(库的设计:按浏览器 page lifecycle 计算)\n *\n * 生命周期保护:\n * - disposed 标志位:shutdown 后所有回调入口 early return,避免向已销毁的 provider 上报\n * - dynamic import 完成后再次检查 disposed:处理\"start() 完成前 shutdown()\"的 race\n * - dynamic import 自身 try/catch:与 PluginManager 的 per-plugin 隔离形成纵深防御,\n * 即使加载失败也不影响整体启动\n */\n\nimport { type Attributes } from '@opentelemetry/api';\n\nimport { getMetricViewAttributes } from '../core/telemetry-attributes';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, getVitalResult, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin } from '../core/plugin';\nimport type * as WebVitalsAttribution from 'web-vitals/attribution';\nimport type { MetricWithAttribution } from 'web-vitals/attribution';\n\ntype NavigationTypeName =\n | 'back-forward'\n | 'back-forward-cache'\n | 'navigate'\n | 'prerender'\n | 'reload'\n | 'restore'\n | 'unknown';\n\ntype VitalLike = Pick<MetricWithAttribution, 'attribution' | 'id' | 'name' | 'navigationType' | 'rating' | 'value'>;\n\nconst FALLBACK_NAVIGATION_TYPE: NavigationTypeName = 'unknown';\n\n// PerformanceNavigationTiming.type 用下划线('back_forward'),web-vitals 用连字符('back-forward'),\n// 统一成 web-vitals 的连字符风格作为 fallback,下游聚合时维度值一致\nconst normalizeNavigationType = (raw: string | undefined): NavigationTypeName => {\n if (!raw) return FALLBACK_NAVIGATION_TYPE;\n return raw.replace(/_/g, '-') as NavigationTypeName;\n};\n\nconst computeFallbackNavigationType = (): NavigationTypeName => {\n if (typeof performance === 'undefined') return FALLBACK_NAVIGATION_TYPE;\n const entry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined;\n return normalizeNavigationType(entry?.type);\n};\n\n// 仅挑选低基数、可枚举的字段进 metric attributes,避免指标维度爆炸\nconst buildMetricDimensions = (metric: VitalLike, fallbackNavType: NavigationTypeName): Attributes => ({\n // 优先使用 metric.navigationType(含 bfcache 等准确值),fallback 到首次 navigation entry\n 'rum.navigation.type': metric.navigationType ?? fallbackNavType,\n [RUM_ATTRIBUTES.vitalMetric]: metric.name.toLowerCase(),\n [RUM_ATTRIBUTES.vitalRating]: metric.rating,\n});\n\n// 把 attribution 中价值最高的字段抽出来(仅放进 span,不放进 metric)\nconst pickAttribution = (metric: VitalLike, redactUrl: (url: string) => string): Attributes => {\n const attr = metric.attribution ?? {};\n const result: Attributes = {};\n const getAttrValue = (key: string) => (attr as Record<string, unknown>)[key];\n\n const setIfDefined = (key: string, value: unknown, transform?: (value: string) => string) => {\n if (value === undefined || value === null) return;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n result[key] = typeof value === 'string' && transform ? transform(value) : value;\n }\n };\n\n switch (metric.name) {\n case 'LCP':\n setIfDefined('vital.lcp.url', getAttrValue('url'), redactUrl);\n setIfDefined('vital.lcp.target', getAttrValue('target'));\n setIfDefined('vital.lcp.element_render_delay', getAttrValue('elementRenderDelay'));\n setIfDefined('vital.lcp.resource_load_duration', getAttrValue('resourceLoadDuration'));\n setIfDefined('vital.lcp.time_to_first_byte', getAttrValue('timeToFirstByte'));\n break;\n case 'CLS':\n setIfDefined('vital.cls.largest_shift_target', getAttrValue('largestShiftTarget'));\n setIfDefined('vital.cls.largest_shift_value', getAttrValue('largestShiftValue'));\n setIfDefined('vital.cls.load_state', getAttrValue('loadState'));\n break;\n case 'INP':\n setIfDefined('vital.inp.interaction_target', getAttrValue('interactionTarget'));\n setIfDefined('vital.inp.interaction_type', getAttrValue('interactionType'));\n setIfDefined('vital.inp.input_delay', getAttrValue('inputDelay'));\n setIfDefined('vital.inp.processing_duration', getAttrValue('processingDuration'));\n setIfDefined('vital.inp.presentation_delay', getAttrValue('presentationDelay'));\n break;\n case 'FCP':\n setIfDefined('vital.fcp.time_to_first_byte', getAttrValue('timeToFirstByte'));\n setIfDefined('vital.fcp.load_state', getAttrValue('loadState'));\n break;\n case 'TTFB':\n setIfDefined('vital.ttfb.waiting_duration', getAttrValue('waitingDuration'));\n setIfDefined('vital.ttfb.dns_duration', getAttrValue('dnsDuration'));\n setIfDefined('vital.ttfb.connection_duration', getAttrValue('connectionDuration'));\n setIfDefined('vital.ttfb.request_duration', getAttrValue('requestDuration'));\n break;\n default:\n break;\n }\n return result;\n};\n\nexport const createWebVitalsPlugin = (): BkOTPlugin => {\n let disposed = false;\n\n return {\n name: 'web-vitals',\n async init(context) {\n if (typeof window === 'undefined') {\n return;\n }\n\n // dynamic import 自包 try/catch:与 PluginManager 的 per-plugin 隔离形成纵深防御,\n // 离线 / chunk 加载失败时不影响其他插件,且 debug 模式留下排查痕迹\n let webVitalsModule: typeof WebVitalsAttribution;\n try {\n webVitalsModule = await import('web-vitals/attribution');\n } catch (error) {\n if (context.config.debug) {\n globalThis.console?.warn('[bk-ot] web-vitals load failed:', error);\n }\n return;\n }\n // import 期间用户可能已经 shutdown:此后再注册回调会向已销毁 provider 上报\n if (disposed) return;\n\n const { onCLS, onFCP, onINP, onLCP, onTTFB } = webVitalsModule;\n const fallbackNavType = computeFallbackNavigationType();\n const clsHistogram = context.meter.createHistogram('browser.web_vital.cls', {\n unit: '1',\n description: 'Cumulative Layout Shift',\n });\n const durationHistogram = context.meter.createHistogram('browser.web_vital.duration', {\n unit: 'ms',\n description: 'Web Vitals duration metrics, including FCP, INP, LCP and TTFB',\n });\n\n const recordSpan = (metric: VitalLike) => {\n const metricName = metric.name.toLowerCase();\n const span = context.startSpan('browser.web_vital', {\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...buildMetricDimensions(metric, fallbackNavType),\n ...buildRumSpanAttributes({\n duration: metric.name === 'CLS' ? undefined : metric.value,\n errorType: metric.rating === 'good' ? 'none' : 'slow',\n eventLabel: 'Web 指标',\n result: getVitalResult(metric.rating),\n spanSubtype: metricName,\n spanType: RUM_SPAN_TYPE.vital,\n targetLabel: metricName,\n targetValue: metric.value,\n }),\n [RUM_ATTRIBUTES.vitalMetric]: metricName,\n [RUM_ATTRIBUTES.vitalValue]: metric.value,\n [RUM_ATTRIBUTES.vitalRating]: metric.rating,\n // 单条度量唯一 ID 仅放进 span,避免污染 metric 维度\n 'vital.id': metric.id,\n ...pickAttribution(metric, context.config.hooks.redactUrl),\n });\n span.end();\n };\n\n const recordDurationMetric = (metric: VitalLike) => {\n // 防御非法值:极端浏览器实现 bug 可能给出 NaN/Infinity,OT 后端会拒收\n if (disposed || !Number.isFinite(metric.value)) return;\n durationHistogram.record(\n metric.value,\n context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...getMetricViewAttributes(context.getRuntimeAttributes()),\n ...buildMetricDimensions(metric, fallbackNavType),\n }),\n );\n recordSpan(metric);\n };\n\n onCLS(metric => {\n if (disposed || !Number.isFinite(metric.value)) return;\n clsHistogram.record(\n metric.value,\n context.applyRedact({\n ...context.config.hooks.getPageAttributes(),\n ...context.config.hooks.getMetricAttributes(),\n ...getMetricViewAttributes(context.getRuntimeAttributes()),\n ...buildMetricDimensions(metric, fallbackNavType),\n }),\n );\n recordSpan(metric);\n });\n onFCP(recordDurationMetric);\n onINP(recordDurationMetric);\n onLCP(recordDurationMetric);\n onTTFB(recordDurationMetric);\n },\n shutdown() {\n // web-vitals 的 onXXX 不返回 unsubscribe,无法真正取消订阅;\n // 用 disposed 标志位让回调入口 early return 即可\n disposed = true;\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n/**\n * WebSocket 插件\n *\n * 通过 Proxy 包装 window.WebSocket,对每个 ws 实例采集:\n * - span (`websocket.connect`):仅覆盖建连阶段,避免长连接 span 永远不结束\n * span attributes: url.full / url.scheme / server.address / network.protocol.name\n * - histogram (`browser.websocket.connect.duration`):成功建连耗时(ms),便于聚合 P99\n * - counter (`browser.websocket.message.count` / `.bytes`):双向消息数与字节数,\n * 按 websocket.direction (in/out) 维度区分\n * - counter (`browser.websocket.error.count`):错误次数,按 websocket.error.phase\n * (connect / runtime) 区分建连失败与运行时中断\n * - log (`websocket.error` / `websocket.close`):错误与关闭事件详情;error 按 url\n * 做 throttle 防 reconnect 风暴\n *\n * Patch 策略(关键设计):\n * 使用 Proxy(NativeWebSocket, { construct }),相比传统 monkey-patch 优势:\n * - 自动透传静态常量(WebSocket.OPEN / CONNECTING 等),无需手动复制;\n * Object.assign 复制会失败,因为这些常量是 non-enumerable\n * - 完美支持 `class MyWS extends WebSocket` 子类化场景:\n * Reflect.construct 用 newTarget=MyWS 创建对象,原型链与 instanceof 都正确\n * - 透明的 Symbol.hasInstance 行为,业务侧 instanceof 检查正常\n *\n * 已知行为与限制:\n * - send 方法在每个实例上覆盖(不在 prototype),原因是 native ws 没有 send 回调,\n * 只能包装实例方法计数 outbound;副作用是若业务 `delete socket.send` 会失去统计\n * - shutdown 后已存在的 socket 实例仍带着我们的事件监听与 send 包装;\n * 用 disposed 标志位让回调入口 early return,新建的 ws 走原生路径\n * - 大消息(> 64KB string)字节计算降级为 string.length 估算(CJK 等多字节字符会偏小),\n * 避免 TextEncoder().encode() 的 O(n) 开销在 IM / 流式场景拖累主线程\n * - error throttle:默认 60s 窗口最多 5 条同 url 错误日志,counter 仍精确累计不受影响\n *\n * 多 patch 兼容:\n * shutdown 时检查 window.WebSocket 是否仍是我们的 Proxy,若被其他库覆盖则不强行恢复,\n * 避免抹掉后来者的 patch\n */\n\nimport { SpanStatusCode } from '@opentelemetry/api';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\n\nimport { createThrottle } from '../core/throttle';\nimport { shouldIgnoreUrl } from '../core/url';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\n\nimport type { BkOTPlugin, BkOTRuntimeContext } from '../core/plugin';\nimport type { Span } from '@opentelemetry/api';\n\n// 64KB 以下精确计算 UTF-8 字节,超过则用 string.length 粗估,避免 IM 大文本拖慢主线程\nconst BYTES_PRECISE_THRESHOLD = 64 * 1024;\n// 错误日志节流:60s / 5 条同 url,防 reconnect 风暴\nconst ERROR_THROTTLE_WINDOW_MS = 60_000;\nconst ERROR_THROTTLE_MAX_PER_WINDOW = 5;\n\nconst getMessageByteLength = (data: unknown): number => {\n if (data == null) return 0;\n if (typeof data === 'string') {\n if (data.length > BYTES_PRECISE_THRESHOLD || typeof TextEncoder === 'undefined') {\n // 粗估:纯 ASCII 准确,CJK / emoji 会偏小;权衡性能与准确性\n return data.length;\n }\n return new TextEncoder().encode(data).byteLength;\n }\n if (data instanceof ArrayBuffer) return data.byteLength;\n if (ArrayBuffer.isView(data)) return data.byteLength;\n if (typeof Blob !== 'undefined' && data instanceof Blob) return data.size;\n return 0;\n};\n\ninterface WebSocketAttributeBundle {\n metricAttributes: Record<string, string>;\n spanAttributes: Record<string, string>;\n}\n\nconst buildWebSocketAttributes = (url: string, redactUrl: (url: string) => string): WebSocketAttributeBundle => {\n const spanAttributes: Record<string, string> = {\n 'network.protocol.name': 'websocket',\n 'url.full': redactUrl(url),\n };\n const metricAttributes: Record<string, string> = {\n 'network.protocol.name': 'websocket',\n };\n\n try {\n const parsed = new URL(url, typeof location === 'undefined' ? 'http://localhost' : location.href);\n spanAttributes['server.address'] = parsed.host;\n // url.scheme 区分 ws / wss,下游能按加密/非加密分组\n spanAttributes['url.scheme'] = parsed.protocol.replace(':', '');\n // server.address 进 metric 是中等基数(按域名维度通常 < 10),对 metric 系列影响可控\n metricAttributes['server.address'] = parsed.host;\n metricAttributes['url.scheme'] = spanAttributes['url.scheme'];\n } catch {\n /* malformed URL:保留 redacted 原 url,跳过 host / scheme 解析 */\n }\n\n return { metricAttributes, spanAttributes };\n};\n\ninterface InstrumentDeps {\n bytesCounter: ReturnType<BkOTRuntimeContext['meter']['createCounter']>;\n connectDurationHistogram: ReturnType<BkOTRuntimeContext['meter']['createHistogram']>;\n context: BkOTRuntimeContext;\n errorCounter: ReturnType<BkOTRuntimeContext['meter']['createCounter']>;\n errorThrottle: ReturnType<typeof createThrottle>;\n messageCounter: ReturnType<BkOTRuntimeContext['meter']['createCounter']>;\n url: string;\n isDisposed: () => boolean;\n}\n\nconst instrumentSocket = (socket: WebSocket, deps: InstrumentDeps) => {\n const {\n bytesCounter,\n connectDurationHistogram,\n context,\n errorCounter,\n errorThrottle,\n isDisposed,\n messageCounter,\n url,\n } = deps;\n const { metricAttributes, spanAttributes } = buildWebSocketAttributes(url, context.config.hooks.redactUrl);\n // 补上业务自定义 metric 维度(attributes.metric hook),与 web-vitals / longTask / routeTiming 保持一致。\n // 每个 socket 只计算一次,避免消息热路径反复调用用户 hook。\n const metricBaseAttributes = context.applyRedact({\n ...context.config.hooks.getMetricAttributes(),\n ...metricAttributes,\n });\n const targetLabel = spanAttributes['server.address'] ?? spanAttributes['url.full'];\n\n // connect span 仅覆盖建连阶段,长连接生命周期不绑定 span,避免\"幽灵 span\"\n const connectSpan: Span = context.startSpan('websocket.connect', {\n ...spanAttributes,\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: 'WebSocket',\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n }),\n ...(spanAttributes['server.address'] ? { [RUM_ATTRIBUTES.targetDomain]: spanAttributes['server.address'] } : {}),\n });\n const startTime = performance.now();\n let connectEnded = false;\n\n const endConnectSpan = (status: 'error' | 'opened') => {\n if (connectEnded) return;\n connectEnded = true;\n const duration = performance.now() - startTime;\n if (status === 'error') {\n connectSpan.setStatus({ code: SpanStatusCode.ERROR, message: 'websocket connect failed' });\n }\n connectSpan.setAttributes(\n buildRumSpanAttributes({\n duration,\n errorType: status === 'error' ? 'network' : 'none',\n eventLabel: 'WebSocket',\n result: status === 'error' ? RUM_SPAN_RESULT.error : RUM_SPAN_RESULT.success,\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n targetValue: duration,\n }),\n );\n connectSpan.end();\n // 仅成功建连记入 histogram;错误的 duration 多为 timeout 固定值,统计意义有限\n if (status === 'opened' && !isDisposed()) {\n connectDurationHistogram.record(duration, metricBaseAttributes);\n }\n };\n\n socket.addEventListener('open', () => {\n if (isDisposed()) return;\n endConnectSpan('opened');\n });\n\n socket.addEventListener('message', event => {\n if (isDisposed()) return;\n const inAttrs = { ...metricBaseAttributes, 'websocket.direction': 'in' };\n messageCounter.add(1, inAttrs);\n bytesCounter.add(getMessageByteLength(event.data), inAttrs);\n });\n\n socket.addEventListener('error', () => {\n if (isDisposed()) return;\n // 用 connectEnded 区分阶段:未 open 即出错为 connect 阶段,已 open 后为 runtime\n const phase = connectEnded ? 'runtime' : 'connect';\n errorCounter.add(1, { ...metricBaseAttributes, 'websocket.error.phase': phase });\n endConnectSpan('error');\n // 同 url 错误节流:reconnect 失败循环不至于产生日志风暴;counter 不受影响(已在上方累计)\n const throttleResult = errorThrottle.allow(`ws:error:${url}`);\n if (!throttleResult.allowed) return;\n context.emitLog({\n severityNumber: SeverityNumber.ERROR,\n severityText: 'ERROR',\n body: 'websocket.error',\n attributes: {\n ...spanAttributes,\n ...buildRumSpanAttributes({\n errorType: 'network',\n eventLabel: 'WebSocket 错误',\n result: RUM_SPAN_RESULT.error,\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n }),\n 'websocket.error.phase': phase,\n 'websocket.error.window_count': throttleResult.count,\n },\n });\n });\n\n socket.addEventListener('close', event => {\n if (isDisposed()) return;\n // 从未 open 就 close → 建连失败(部分浏览器只触发 close 不触发 error 的边界)\n if (!connectEnded) {\n endConnectSpan('error');\n }\n context.emitLog({\n severityNumber: SeverityNumber.INFO,\n severityText: 'INFO',\n body: 'websocket.close',\n attributes: {\n ...spanAttributes,\n ...buildRumSpanAttributes({\n errorType: event.wasClean ? 'none' : 'network',\n eventLabel: 'WebSocket 关闭',\n result: event.wasClean ? RUM_SPAN_RESULT.success : RUM_SPAN_RESULT.warning,\n spanSubtype: 'websocket',\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel,\n targetValue: event.code,\n }),\n 'websocket.close.code': event.code,\n 'websocket.close.reason': event.reason,\n 'websocket.close.was_clean': event.wasClean,\n },\n });\n });\n\n // 包装 send 统计 outbound:原生 WebSocket 没有 send 回调或事件,只能逐实例覆盖方法\n const originalSend = socket.send.bind(socket);\n socket.send = (data: ArrayBufferLike | ArrayBufferView | Blob | string) => {\n if (!isDisposed()) {\n const outAttrs = { ...metricBaseAttributes, 'websocket.direction': 'out' };\n messageCounter.add(1, outAttrs);\n bytesCounter.add(getMessageByteLength(data), outAttrs);\n }\n return originalSend(data);\n };\n};\n\nexport const createWebSocketPlugin = (): BkOTPlugin => {\n let originalWebSocket: typeof WebSocket | undefined;\n let patchedProxy: typeof WebSocket | undefined;\n let disposed = false;\n\n return {\n name: 'websocket',\n init(context) {\n if (typeof window === 'undefined' || typeof window.WebSocket === 'undefined') {\n return;\n }\n\n const nativeWebSocket = window.WebSocket;\n originalWebSocket = nativeWebSocket;\n\n const messageCounter = context.meter.createCounter('browser.websocket.message.count', {\n description: 'Total number of WebSocket messages observed',\n });\n const bytesCounter = context.meter.createCounter('browser.websocket.message.bytes', {\n unit: 'By',\n description: 'Total bytes transferred over WebSocket (best-effort)',\n });\n const errorCounter = context.meter.createCounter('browser.websocket.error.count', {\n description: 'Total number of WebSocket error events',\n });\n const connectDurationHistogram = context.meter.createHistogram('browser.websocket.connect.duration', {\n unit: 'ms',\n description: 'WebSocket connection establishment duration (only successful connects)',\n });\n const errorThrottle = createThrottle(ERROR_THROTTLE_WINDOW_MS, ERROR_THROTTLE_MAX_PER_WINDOW);\n const isDisposed = () => disposed;\n\n // Proxy 而非 function 替换:自动透传静态常量、原型与 Symbol.hasInstance;\n // 同时 Reflect.construct 用 newTarget 让 `class extends WebSocket` 子类化正确工作\n patchedProxy = new Proxy(nativeWebSocket, {\n construct(target, args, newTarget) {\n const socket = Reflect.construct(target, args, newTarget) as WebSocket;\n if (disposed) return socket;\n const url = args[0] != null ? String(args[0]) : '';\n // 用户主动 ignore 或上报 endpoint,避免回环监控\n if (!url || shouldIgnoreUrl(context.config, url)) {\n return socket;\n }\n instrumentSocket(socket, {\n bytesCounter,\n connectDurationHistogram,\n context,\n errorCounter,\n errorThrottle,\n isDisposed,\n messageCounter,\n url,\n });\n return socket;\n },\n });\n\n window.WebSocket = patchedProxy;\n },\n shutdown() {\n disposed = true;\n // 多 patch 兼容:仅当当前 window.WebSocket 还是我们的 Proxy 时才恢复,\n // 避免抹掉其他库后来者的 patch\n if (typeof window !== 'undefined' && originalWebSocket && window.WebSocket === patchedProxy) {\n window.WebSocket = originalWebSocket;\n }\n patchedProxy = undefined;\n originalWebSocket = undefined;\n },\n };\n};\n","/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport type { ResolvedBkOTConfig } from './config';\n\nconst SAMPLE_STORAGE_KEY = 'BK_RUM_SAMPLING';\n\n// 在 sessionStorage 不可用时降级到模块内单例缓存,避免同会话内多次结果不一致\nconst memoryCache = new Map<string, boolean>();\n\nconst readMemory = (key: string) => memoryCache.get(key);\nconst writeMemory = (key: string, value: boolean) => {\n memoryCache.set(key, value);\n return value;\n};\n\nexport const isBkOTSampled = ({ sampling }: Pick<ResolvedBkOTConfig, 'sampling'>) => {\n if (sampling.rate <= 0) {\n return false;\n }\n if (sampling.rate >= 1) {\n return true;\n }\n\n try {\n const cached = sessionStorage.getItem(SAMPLE_STORAGE_KEY);\n if (cached) {\n return cached === '1';\n }\n const sampled = Math.random() < sampling.rate;\n sessionStorage.setItem(SAMPLE_STORAGE_KEY, sampled ? '1' : '0');\n writeMemory(SAMPLE_STORAGE_KEY, sampled);\n return sampled;\n } catch {\n const cached = readMemory(SAMPLE_STORAGE_KEY);\n if (typeof cached === 'boolean') {\n return cached;\n }\n return writeMemory(SAMPLE_STORAGE_KEY, Math.random() < sampling.rate);\n }\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/*\n * Tencent is pleased to support the open source community by making\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) available.\n *\n * Copyright (C) 2017-2025 Tencent. All rights reserved.\n *\n * 蓝鲸智云PaaS平台 (BlueKing PaaS) is licensed under the MIT License.\n *\n * License for 蓝鲸智云PaaS平台 (BlueKing PaaS):\n *\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and\n * to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of\n * the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO\n * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\n * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport {\n type Attributes,\n type Span,\n context,\n metrics,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n} from '@opentelemetry/api';\nimport { logs } from '@opentelemetry/api-logs';\nimport { ZoneContextManager } from '@opentelemetry/context-zone';\nimport { W3CTraceContextPropagator } from '@opentelemetry/core';\nimport { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { type Instrumentation, registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';\nimport { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';\nimport {\n BatchSpanProcessor,\n ParentBasedSampler,\n SimpleSpanProcessor,\n TraceIdRatioBasedSampler,\n} from '@opentelemetry/sdk-trace-base';\nimport { WebTracerProvider } from '@opentelemetry/sdk-trace-web';\n\nimport { createActionPlugin } from '../plugins/action';\nimport { createBlankScreenPlugin } from '../plugins/blank-screen';\nimport { createCommonInstrumentationsPlugin } from '../plugins/common';\nimport { createCspViolationPlugin } from '../plugins/csp-violation';\nimport { createDevicePlugin } from '../plugins/device';\nimport { createErrorPlugin } from '../plugins/error';\nimport { createHttpBodyPlugin } from '../plugins/http-body';\nimport { createLongTaskPlugin } from '../plugins/long-task';\nimport { createPageViewPlugin } from '../plugins/page-view';\nimport { createResourcePlugin } from '../plugins/resource';\nimport { createRouteTimingPlugin } from '../plugins/route-timing';\nimport { createSessionPlugin } from '../plugins/session';\nimport { createWebVitalsPlugin } from '../plugins/web-vitals';\nimport { createWebSocketPlugin } from '../plugins/websocket';\nimport { RUM_ATTRIBUTES } from '../schema/attributes';\nimport { buildRumSpanAttributes, RUM_SPAN_RESULT, RUM_SPAN_TYPE } from '../schema/span';\nimport {\n type BkOTActionEventPayload,\n type BkOTCustomEventPayload,\n type BkOTOptions,\n type BkOTPluginsConfig,\n type ResolvedBkOTConfig,\n resolveConfig,\n} from './config';\nimport { type BkOTPlugin, type BkOTRuntimeContext, type BkOTStartSpanOptions, PluginManager } from './plugin';\nimport { FilteringSpanProcessor } from './processor';\nimport { type BkOTUserContext, type BkOTViewContext, RumRuntimeContext } from './runtime-context';\nimport { isBkOTSampled } from './sampling';\n\nimport type { LogRecord } from '@opentelemetry/api-logs';\n\nexport interface BkOTInstance extends BkOTRuntimeContext {\n flush: () => Promise<void>;\n reportAction: (payload: BkOTActionEventPayload) => void;\n reportCustomEvent: (payload: BkOTCustomEventPayload) => void;\n setUser: (user: BkOTUserContext) => void;\n setView: (view: BkOTViewContext) => void;\n shutdown: () => Promise<void>;\n start: () => Promise<void>;\n use: (plugin: BkOTPlugin) => BkOTInstance;\n useInstrumentation: (instrumentation: Instrumentation) => BkOTInstance;\n}\n\nconst createExporterOptions = (config: ResolvedBkOTConfig['transport']['traces']) => ({\n url: config.endpoint,\n headers: config.headers,\n concurrencyLimit: config.concurrencyLimit,\n timeoutMillis: config.timeoutMillis,\n});\n\ntype DebugSignal = 'logs' | 'metrics' | 'traces';\n\nconst BK_OT_INSTRUMENTATION_NAME = 'bk-rum';\n\n/**\n * 用 Proxy 包装一层 debug 旁路,避免直接 mutate 第三方 exporter 实例(其内部可能含 #private fields)。\n */\nconst wrapWithDebug = <TExporter extends { export: (...args: any[]) => any }>(\n exporter: TExporter,\n signal: DebugSignal,\n enabled: boolean,\n): TExporter => {\n if (!enabled) {\n return exporter;\n }\n return new Proxy(exporter, {\n get(target, prop, receiver) {\n if (prop === 'export') {\n return (records: unknown, callback: unknown) => {\n globalThis.console?.log(`[bk-ot][${signal}]`, records);\n return (target as { export: (...args: unknown[]) => unknown }).export(records, callback);\n };\n }\n const value = Reflect.get(target, prop, receiver);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n};\n\n// 负责给原生 fetch/xhr 注入 W3C traceparent 头;仅 SDK 启用时装载,避免 disabled 状态仍 patch 全局 API\nconst getCorePlugins = (config: ResolvedBkOTConfig): BkOTPlugin[] => [\n createCommonInstrumentationsPlugin(config.instrumentations),\n];\n\nconst isBuiltInPluginEnabled = <TOptions extends object>(\n option: boolean | TOptions | undefined,\n defaultEnabled: boolean,\n) => (option === undefined ? defaultEnabled : option !== false);\n\nconst getBuiltInPluginOptions = <TOptions extends object>(option: boolean | TOptions | undefined) =>\n typeof option === 'object' && option !== null ? option : undefined;\n\nconst createBuiltInPlugin = <TOptions extends object>(\n option: boolean | TOptions | undefined,\n defaultEnabled: boolean,\n factory: (options?: TOptions) => BkOTPlugin,\n) => (isBuiltInPluginEnabled(option, defaultEnabled) ? [factory(getBuiltInPluginOptions(option))] : []);\n\n// 内置 RUM 插件仅在采样命中时启用;上下文插件先启动,确保后续 span/log 能带上关联字段。\nconst getRumPlugins = (plugins: BkOTPluginsConfig): BkOTPlugin[] => [\n ...createBuiltInPlugin(plugins.device, true, createDevicePlugin),\n ...createBuiltInPlugin(plugins.session, true, createSessionPlugin),\n ...createBuiltInPlugin(plugins.pageView, true, createPageViewPlugin),\n ...createBuiltInPlugin(plugins.httpBody, false, createHttpBodyPlugin),\n ...createBuiltInPlugin(plugins.error, true, createErrorPlugin),\n ...createBuiltInPlugin(plugins.webVitals, true, createWebVitalsPlugin),\n ...createBuiltInPlugin(plugins.resource, true, createResourcePlugin),\n ...createBuiltInPlugin(plugins.blankScreen, true, createBlankScreenPlugin),\n ...createBuiltInPlugin(plugins.action, false, createActionPlugin),\n ...createBuiltInPlugin(plugins.websocket, false, createWebSocketPlugin),\n ...createBuiltInPlugin(plugins.longTask, false, createLongTaskPlugin),\n ...createBuiltInPlugin(plugins.cspViolation, false, createCspViolationPlugin),\n ...createBuiltInPlugin(plugins.routeTiming, false, createRouteTimingPlugin),\n];\n\ntype SdkLifecyclePhase = 'idle' | 'started' | 'starting' | 'stopped';\n\nexport class BkOpenTelemetry implements BkOTInstance {\n private readonly loggerProvider: LoggerProvider;\n private readonly meterProvider: MeterProvider;\n private phase: SdkLifecyclePhase = 'idle';\n private readonly pluginManager: PluginManager;\n private readonly plugins: BkOTPlugin[];\n private readonly registeredInstrumentations: Instrumentation[];\n private readonly runtimeContext: RumRuntimeContext;\n private readonly sampled: boolean;\n\n private readonly teardownCallbacks: Array<() => void> = [];\n private readonly tracerProvider: WebTracerProvider;\n private assertBeforeStart(method: string) {\n if (this.phase !== 'idle') {\n throw new Error(`[bk-ot] ${method}() must be called before start(); set autoStart: false before registration.`);\n }\n }\n public readonly applyRedact = (attributes: Attributes) => this.config.hooks.redactAttributes(attributes);\n\n public readonly config: ResolvedBkOTConfig;\n\n public readonly emitLog = (record: LogRecord) => {\n const merged: LogRecord = {\n ...record,\n attributes: this.applyRedact({\n ...this.getRuntimeAttributes(),\n ...((record.attributes ?? {}) as Attributes),\n }),\n };\n this.logger.emit(merged);\n };\n public readonly flush = async () => {\n await this.pluginManager.flush();\n await this.tracerProvider.forceFlush();\n await this.meterProvider.forceFlush();\n await this.loggerProvider.forceFlush();\n };\n\n public readonly getRuntimeAttributes = () => this.runtimeContext.getAttributes();\n public readonly logger: BkOTRuntimeContext['logger'];\n\n public readonly meter: BkOTRuntimeContext['meter'];\n\n public readonly reportAction = ({ attributes = {}, targetTag, targetText, type }: BkOTActionEventPayload) => {\n if (!this.sampled) {\n return;\n }\n const textShort = targetText?.slice(0, 32);\n const span = this.startSpan(`action.${type}`, {\n ...this.config.hooks.getPageAttributes(),\n ...buildRumSpanAttributes({\n errorType: 'none',\n eventLabel: '用户操作',\n spanSubtype: type,\n spanType: RUM_SPAN_TYPE.action,\n targetLabel: textShort ?? targetTag ?? type,\n }),\n [RUM_ATTRIBUTES.actionType]: type,\n ...(textShort ? { [RUM_ATTRIBUTES.targetTextShort]: textShort } : {}),\n ...(targetTag ? { [RUM_ATTRIBUTES.targetTag]: targetTag } : {}),\n ...attributes,\n });\n span.end();\n };\n\n public readonly reportCustomEvent = ({ attributes = {}, error, name }: BkOTCustomEventPayload) => {\n if (!this.sampled) {\n // 未采样时静默丢弃;如需调试请把 debug 选项打开\n if (this.config.debug) {\n globalThis.console?.warn('[bk-ot] custom event dropped (not sampled):', name);\n }\n return;\n }\n\n const span = this.startSpan(`custom.${name}`, {\n ...this.config.hooks.getPageAttributes(),\n ...this.config.hooks.getCustomAttributes(),\n ...buildRumSpanAttributes({\n errorType: error ? 'custom' : 'none',\n eventLabel: '自定义事件',\n result: error ? RUM_SPAN_RESULT.error : RUM_SPAN_RESULT.success,\n spanSubtype: name,\n spanType: RUM_SPAN_TYPE.custom,\n targetLabel: name,\n }),\n ...attributes,\n 'rum.custom.name': name,\n });\n\n if (error) {\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n }\n\n span.end();\n };\n\n public readonly setRuntimeAttributes = (attributes: Attributes) => {\n this.runtimeContext.setAttributes(attributes);\n };\n\n public readonly setUser = (user: BkOTUserContext) => {\n this.runtimeContext.setUser(user);\n };\n\n public readonly setView = (view: BkOTViewContext) => {\n this.runtimeContext.setView(view);\n };\n\n public readonly shutdown = async () => {\n if (this.phase === 'stopped') {\n return;\n }\n await this.flush();\n while (this.teardownCallbacks.length) {\n this.teardownCallbacks.pop()?.();\n }\n await this.pluginManager.shutdown();\n while (this.registeredInstrumentations.length) {\n this.registeredInstrumentations.pop()?.disable?.();\n }\n await this.tracerProvider.shutdown();\n await this.meterProvider.shutdown();\n await this.loggerProvider.shutdown();\n trace.disable();\n metrics.disable();\n context.disable();\n this.phase = 'stopped';\n };\n\n public readonly start = async () => {\n if (this.phase === 'started' || this.phase === 'starting') {\n return;\n }\n if (this.phase === 'stopped') {\n // shutdown 已经销毁了 provider/processor,此处禁止再次启动\n throw new Error('[bk-ot] instance has been shut down and cannot be restarted; create a new instance instead.');\n }\n if (!this.config.enabled) {\n this.phase = 'started';\n return;\n }\n this.phase = 'starting';\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n this.tracerProvider.register({\n contextManager: new ZoneContextManager(),\n });\n metrics.setGlobalMeterProvider(this.meterProvider);\n logs.setGlobalLoggerProvider(this.loggerProvider);\n try {\n if (this.registeredInstrumentations.length) {\n registerInstrumentations({\n instrumentations: this.registeredInstrumentations,\n meterProvider: this.meterProvider,\n tracerProvider: this.tracerProvider,\n });\n }\n await this.pluginManager.start(this);\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n const flushOnPageHide = () => {\n void this.flush();\n };\n const flushOnHidden = () => {\n if (document.visibilityState === 'hidden') {\n void this.flush();\n }\n };\n window.addEventListener('pagehide', flushOnPageHide);\n document.addEventListener('visibilitychange', flushOnHidden);\n this.teardownCallbacks.push(\n () => window.removeEventListener('pagehide', flushOnPageHide),\n () => document.removeEventListener('visibilitychange', flushOnHidden),\n );\n }\n } catch (error) {\n while (this.teardownCallbacks.length) {\n this.teardownCallbacks.pop()?.();\n }\n await this.pluginManager.shutdown();\n for (const instrumentation of this.registeredInstrumentations) {\n instrumentation.disable?.();\n }\n trace.disable();\n metrics.disable();\n context.disable();\n this.phase = 'idle';\n throw error;\n }\n this.phase = 'started';\n };\n\n public readonly startSpan = (name: string, attributes: Attributes = {}, options?: BkOTStartSpanOptions): Span =>\n this.tracer.startSpan(name, {\n kind: SpanKind.INTERNAL,\n startTime: options?.startTime,\n attributes: this.applyRedact({\n ...this.getRuntimeAttributes(),\n ...attributes,\n }),\n });\n\n public readonly tracer: BkOTRuntimeContext['tracer'];\n\n public readonly use = (plugin: BkOTPlugin): this => {\n this.assertBeforeStart('use');\n if (this.sampled) {\n this.plugins.push(plugin);\n }\n return this;\n };\n\n public readonly useInstrumentation = (instrumentation: Instrumentation): this => {\n this.assertBeforeStart('useInstrumentation');\n if (this.config.enabled) {\n this.registeredInstrumentations.push(instrumentation);\n }\n return this;\n };\n\n constructor(inputConfig: BkOTOptions) {\n this.config = resolveConfig(inputConfig);\n this.sampled = this.config.enabled && isBkOTSampled(this.config);\n\n const resource = resourceFromAttributes(this.config.resourceAttributes);\n const spanExporter = wrapWithDebug(\n new OTLPTraceExporter(createExporterOptions(this.config.transport.traces)),\n 'traces',\n this.config.debug,\n );\n const metricExporter = wrapWithDebug(\n new OTLPMetricExporter(createExporterOptions(this.config.transport.metrics)),\n 'metrics',\n this.config.debug,\n );\n const logExporter = wrapWithDebug(\n new OTLPLogExporter(createExporterOptions(this.config.transport.logs)),\n 'logs',\n this.config.debug,\n );\n const innerSpanProcessor =\n this.config.spanProcessor === 'simple'\n ? new SimpleSpanProcessor(spanExporter)\n : new BatchSpanProcessor(spanExporter, this.config.spanBatch);\n const spanProcessor = new FilteringSpanProcessor(innerSpanProcessor, this.config);\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: this.config.metricIntervalMillis,\n });\n const logProcessor = new BatchLogRecordProcessor(logExporter);\n\n this.tracerProvider = new WebTracerProvider({\n resource,\n sampler: new ParentBasedSampler({\n root: new TraceIdRatioBasedSampler(this.sampled ? 1 : 0),\n }),\n spanProcessors: [spanProcessor],\n });\n this.meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n this.loggerProvider = new LoggerProvider({\n resource,\n processors: [logProcessor],\n });\n this.tracer = this.tracerProvider.getTracer(BK_OT_INSTRUMENTATION_NAME);\n this.meter = this.meterProvider.getMeter(BK_OT_INSTRUMENTATION_NAME);\n this.logger = this.loggerProvider.getLogger(BK_OT_INSTRUMENTATION_NAME);\n this.runtimeContext = new RumRuntimeContext(\n {\n 'rum.sampled': this.sampled,\n },\n this.config.user,\n );\n this.registeredInstrumentations = this.config.enabled ? [...(this.config.instrumentations.custom ?? [])] : [];\n // 核心插件在 SDK 启用时装载;RUM 数据上报类插件仅在采样命中时装载\n this.plugins = [\n ...(this.config.enabled ? getCorePlugins(this.config) : []),\n ...(this.sampled ? [...getRumPlugins(this.config.plugins), ...(this.config.plugins.custom ?? [])] : []),\n ];\n this.pluginManager = new PluginManager(this.plugins);\n\n if (this.config.autoStart) {\n void this.start().catch(error => {\n globalThis.console?.warn('[bk-ot] auto start failed:', error);\n });\n }\n }\n}\n"],"mappings":"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"}
|