@softprobe/web-record 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -34
- package/dist/http-interceptor.d.ts +21 -9
- package/dist/http-interceptor.d.ts.map +1 -1
- package/dist/http-interceptor.js +334 -67
- package/dist/http-interceptor.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/otlp.d.ts +21 -0
- package/dist/otlp.d.ts.map +1 -1
- package/dist/otlp.js +48 -0
- package/dist/otlp.js.map +1 -1
- package/dist/sdk.d.ts +40 -3
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +259 -13
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +31 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,wBAAwB,EACxB,SAAS,GACV,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC"}
|
package/dist/otlp.d.ts
CHANGED
|
@@ -27,10 +27,31 @@ export type BuildRecordingSpanInput = {
|
|
|
27
27
|
serviceName?: string;
|
|
28
28
|
environment?: string;
|
|
29
29
|
};
|
|
30
|
+
export type BuildClientRequestSpanInput = {
|
|
31
|
+
traceId: string;
|
|
32
|
+
spanId: string;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
method: string;
|
|
35
|
+
scheme: string;
|
|
36
|
+
serverAddress: string;
|
|
37
|
+
path: string;
|
|
38
|
+
statusCode: number;
|
|
39
|
+
failed: boolean;
|
|
40
|
+
requestBody?: string;
|
|
41
|
+
responseBody?: string;
|
|
42
|
+
startTimeUnixNano: string;
|
|
43
|
+
endTimeUnixNano: string;
|
|
44
|
+
serviceName?: string;
|
|
45
|
+
environment?: string;
|
|
46
|
+
};
|
|
30
47
|
/** Build an OTLP JSON ExportTraceServiceRequest for one recording flush. */
|
|
31
48
|
export declare function buildRecordingExport(input: BuildRecordingSpanInput): {
|
|
32
49
|
resourceSpans: unknown[];
|
|
33
50
|
};
|
|
51
|
+
/** Build an OTLP client span for an explicitly selected browser API request. */
|
|
52
|
+
export declare function buildClientRequestExport(input: BuildClientRequestSpanInput): {
|
|
53
|
+
resourceSpans: unknown[];
|
|
54
|
+
};
|
|
34
55
|
export declare function randomHexId(bytes: number): string;
|
|
35
56
|
export declare function msToUnixNano(ms: number): string;
|
|
36
57
|
export { SPAN_NAME, EVENT_NAME, EVENTS_ATTR };
|
package/dist/otlp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,QAAA,MAAM,SAAS,4BAA4B,CAAC;AAC5C,QAAA,MAAM,UAAU,uBAAuB,CAAC;AACxC,QAAA,MAAM,WAAW,wBAAwB,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,WAAW,CAAC;AAEpD,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAyBF,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,uBAAuB,GAAG;IACpE,aAAa,EAAE,OAAO,EAAE,CAAC;CAC1B,CAmDA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,QAAA,MAAM,SAAS,4BAA4B,CAAC;AAC5C,QAAA,MAAM,UAAU,uBAAuB,CAAC;AACxC,QAAA,MAAM,WAAW,wBAAwB,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,WAAW,CAAC;AAEpD,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAyBF,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,uBAAuB,GAAG;IACpE,aAAa,EAAE,OAAO,EAAE,CAAC;CAC1B,CAmDA;AAED,gFAAgF;AAChF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,2BAA2B,GAAG;IAC5E,aAAa,EAAE,OAAO,EAAE,CAAC;CAC1B,CA8CA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/otlp.js
CHANGED
|
@@ -69,6 +69,54 @@ export function buildRecordingExport(input) {
|
|
|
69
69
|
],
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
/** Build an OTLP client span for an explicitly selected browser API request. */
|
|
73
|
+
export function buildClientRequestExport(input) {
|
|
74
|
+
const attrs = [
|
|
75
|
+
stringAttr("sp.session.id", input.sessionId),
|
|
76
|
+
stringAttr("http.request.method", input.method),
|
|
77
|
+
stringAttr("url.scheme", input.scheme),
|
|
78
|
+
stringAttr("server.address", input.serverAddress),
|
|
79
|
+
stringAttr("url.path", input.path),
|
|
80
|
+
intAttr("http.response.status_code", input.statusCode),
|
|
81
|
+
...(input.requestBody === undefined
|
|
82
|
+
? []
|
|
83
|
+
: [stringAttr("http.request.body.content", input.requestBody)]),
|
|
84
|
+
...(input.responseBody === undefined
|
|
85
|
+
? []
|
|
86
|
+
: [stringAttr("http.response.body.content", input.responseBody)]),
|
|
87
|
+
];
|
|
88
|
+
const resourceAttrs = [];
|
|
89
|
+
if (input.serviceName)
|
|
90
|
+
resourceAttrs.push(stringAttr("service.name", input.serviceName));
|
|
91
|
+
if (input.environment) {
|
|
92
|
+
resourceAttrs.push(stringAttr("deployment.environment.name", input.environment));
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
resourceSpans: [
|
|
96
|
+
{
|
|
97
|
+
resource: { attributes: resourceAttrs },
|
|
98
|
+
scopeSpans: [
|
|
99
|
+
{
|
|
100
|
+
scope: { name: "softprobe.web.record", version: "0.1.0" },
|
|
101
|
+
spans: [
|
|
102
|
+
{
|
|
103
|
+
traceId: input.traceId,
|
|
104
|
+
spanId: input.spanId,
|
|
105
|
+
name: `HTTP ${input.method}`,
|
|
106
|
+
kind: 3,
|
|
107
|
+
startTimeUnixNano: input.startTimeUnixNano,
|
|
108
|
+
endTimeUnixNano: input.endTimeUnixNano,
|
|
109
|
+
attributes: attrs,
|
|
110
|
+
events: [],
|
|
111
|
+
status: { code: input.failed ? 2 : 1 },
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
72
120
|
export function randomHexId(bytes) {
|
|
73
121
|
const buf = new Uint8Array(bytes);
|
|
74
122
|
crypto.getRandomValues(buf);
|
package/dist/otlp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,UAAU,GAAG,oBAAoB,CAAC;AACxC,MAAM,WAAW,GAAG,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"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"}
|
package/dist/sdk.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type eventWithTime } from "@rrweb/types";
|
|
2
|
-
import type { RecordingHandle, Tags, WebRecordController, WebRecordInitOptions } from "./types.js";
|
|
2
|
+
import type { RecordingHandle, RecordingUploadStatus, Tags, WebRecordController, WebRecordInitOptions } from "./types.js";
|
|
3
3
|
type Compressible = eventWithTime & {
|
|
4
4
|
data?: unknown;
|
|
5
5
|
isCompressed?: boolean;
|
|
@@ -9,12 +9,17 @@ export declare class RecordSdk implements WebRecordController {
|
|
|
9
9
|
readonly enabled: boolean;
|
|
10
10
|
private _sessionId;
|
|
11
11
|
private readonly otlpEndpoint;
|
|
12
|
-
private readonly
|
|
12
|
+
private readonly apiKey;
|
|
13
|
+
private readonly apiOrigins;
|
|
14
|
+
private readonly sessionIdStorageKey;
|
|
13
15
|
private readonly intervalMs;
|
|
14
16
|
private readonly serviceName?;
|
|
15
17
|
private readonly environment?;
|
|
16
18
|
private readonly userId?;
|
|
17
19
|
private readonly maskAllInputs;
|
|
20
|
+
private readonly maskTextSelector?;
|
|
21
|
+
private readonly blockSelector?;
|
|
22
|
+
private readonly onUploadStatus?;
|
|
18
23
|
private readonly recordOptions;
|
|
19
24
|
private readonly replacers;
|
|
20
25
|
private tags;
|
|
@@ -26,11 +31,22 @@ export declare class RecordSdk implements WebRecordController {
|
|
|
26
31
|
private systemInfo;
|
|
27
32
|
private stopHandle;
|
|
28
33
|
private saving;
|
|
34
|
+
private recordingFlush;
|
|
35
|
+
private readonly pendingClientSpanExports;
|
|
36
|
+
/** 连续导出失败次数;成功即归零。 */
|
|
37
|
+
private consecutiveFailures;
|
|
38
|
+
/** 退避到期时间戳(ms);在此之前定时器不再尝试导出。 */
|
|
39
|
+
private nextAttemptAt;
|
|
40
|
+
/** 熔断标志:连败到上限后置位,导出与采集一起停。 */
|
|
41
|
+
private exportDisabled;
|
|
29
42
|
private fetchImpl;
|
|
30
43
|
/** Serializes setSessionId drains so overlapping navigations cannot race. */
|
|
31
44
|
private sessionSwitch;
|
|
45
|
+
private _uploadStatus;
|
|
46
|
+
private readonly onPageHide;
|
|
32
47
|
private constructor();
|
|
33
48
|
get sessionId(): string;
|
|
49
|
+
get uploadStatus(): RecordingUploadStatus;
|
|
34
50
|
static init(options?: WebRecordInitOptions): WebRecordController;
|
|
35
51
|
record(opts?: {
|
|
36
52
|
tags?: Tags;
|
|
@@ -46,7 +62,28 @@ export declare class RecordSdk implements WebRecordController {
|
|
|
46
62
|
*/
|
|
47
63
|
private drainFlush;
|
|
48
64
|
compressEvent(event: Compressible): Compressible;
|
|
49
|
-
flush(
|
|
65
|
+
flush(options?: {
|
|
66
|
+
keepalive?: boolean;
|
|
67
|
+
}): Promise<boolean>;
|
|
68
|
+
private flushRecordingBatch;
|
|
69
|
+
private flushPendingClientSpanExports;
|
|
70
|
+
private setUploadStatus;
|
|
71
|
+
private createRequestSpan;
|
|
72
|
+
private exportClientRequestSpan;
|
|
73
|
+
/**
|
|
74
|
+
* 导出失败后的退避与熔断。
|
|
75
|
+
*
|
|
76
|
+
* <p>现场实证(2026-08-25,甲方内网):到不了配置的 collector,原实现每 5 秒把同一批
|
|
77
|
+
* 事件原样重发,失败 429 次没停过 —— 控制台被刷爆,主线程每轮还白做一次
|
|
78
|
+
* `JSON.stringify` + 压缩,页面肉眼可见地卡。三条对策:
|
|
79
|
+
* <ul>
|
|
80
|
+
* <li>连败指数退避,不再固定间隔硬打;</li>
|
|
81
|
+
* <li>只在连败的<b>第一条</b>打日志,后续静默;</li>
|
|
82
|
+
* <li>连败到上限就停掉采集并清空缓冲,再打一条写明端点和处置办法。</li>
|
|
83
|
+
* </ul>
|
|
84
|
+
* 单次失败仍然保留缓冲(可能只是网络抖动),这一点不变。
|
|
85
|
+
*/
|
|
86
|
+
private noteExportFailure;
|
|
50
87
|
private collectSystemInfo;
|
|
51
88
|
}
|
|
52
89
|
export {};
|
package/dist/sdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"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"}
|
package/dist/sdk.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { EventType } from "@rrweb/types";
|
|
2
2
|
import { pack, record as rrwebRecord } from "rrweb";
|
|
3
3
|
import { asNonEmptyString, deriveOtlpEndpoint, resolveSoftprobeConfigFromObject, MissingSoftprobeCredentialsError, } from "@softprobe/tracing/config";
|
|
4
|
-
import { initializeHttpInterceptor,
|
|
5
|
-
import { buildRecordingExport, msToUnixNano, randomHexId, } from "./otlp.js";
|
|
4
|
+
import { initializeHttpInterceptor, } from "./http-interceptor.js";
|
|
5
|
+
import { buildClientRequestExport, buildRecordingExport, msToUnixNano, randomHexId, } from "./otlp.js";
|
|
6
6
|
const MAX_EVENTS = 500;
|
|
7
|
+
/** 连续导出失败多少次后彻底放弃采集(见 {@link RecordSdk.noteExportFailure})。 */
|
|
8
|
+
const MAX_CONSECUTIVE_EXPORT_FAILURES = 5;
|
|
9
|
+
/** 退避上限。不可达时不应一直定期打点。 */
|
|
10
|
+
const BACKOFF_MAX_MS = 5 * 60_000;
|
|
7
11
|
const COOKIE_NAME = "_sp_vid";
|
|
8
12
|
const COOKIE_MAX_AGE = 365 * 24 * 60 * 60;
|
|
13
|
+
const SESSION_ID_STORAGE_PREFIX = "_sp_session_id:";
|
|
9
14
|
export class RecordSdk {
|
|
10
15
|
enabled;
|
|
11
16
|
_sessionId;
|
|
12
17
|
otlpEndpoint;
|
|
13
|
-
|
|
18
|
+
apiKey;
|
|
19
|
+
apiOrigins;
|
|
20
|
+
sessionIdStorageKey;
|
|
14
21
|
intervalMs;
|
|
15
22
|
serviceName;
|
|
16
23
|
environment;
|
|
17
24
|
userId;
|
|
18
25
|
maskAllInputs;
|
|
26
|
+
maskTextSelector;
|
|
27
|
+
blockSelector;
|
|
28
|
+
onUploadStatus;
|
|
19
29
|
recordOptions;
|
|
20
30
|
replacers;
|
|
21
31
|
tags;
|
|
@@ -27,19 +37,43 @@ export class RecordSdk {
|
|
|
27
37
|
systemInfo = null;
|
|
28
38
|
stopHandle = null;
|
|
29
39
|
saving = false;
|
|
40
|
+
recordingFlush = null;
|
|
41
|
+
pendingClientSpanExports = new Set();
|
|
42
|
+
/** 连续导出失败次数;成功即归零。 */
|
|
43
|
+
consecutiveFailures = 0;
|
|
44
|
+
/** 退避到期时间戳(ms);在此之前定时器不再尝试导出。 */
|
|
45
|
+
nextAttemptAt = 0;
|
|
46
|
+
/** 熔断标志:连败到上限后置位,导出与采集一起停。 */
|
|
47
|
+
exportDisabled = false;
|
|
30
48
|
fetchImpl;
|
|
31
49
|
/** Serializes setSessionId drains so overlapping navigations cannot race. */
|
|
32
50
|
sessionSwitch = Promise.resolve();
|
|
51
|
+
_uploadStatus = "idle";
|
|
52
|
+
onPageHide = () => {
|
|
53
|
+
if (!this.enabled || (this.events.length === 0 && !this.saving && this.pendingClientSpanExports.size === 0))
|
|
54
|
+
return;
|
|
55
|
+
this.setUploadStatus("incomplete");
|
|
56
|
+
// A keepalive request is best-effort. If an ordinary export is already in
|
|
57
|
+
// flight, do not duplicate its batch; report it as incomplete until it
|
|
58
|
+
// resolves or fails.
|
|
59
|
+
if (!this.saving)
|
|
60
|
+
void this.flush({ keepalive: true });
|
|
61
|
+
};
|
|
33
62
|
constructor(options) {
|
|
34
63
|
this.enabled = options.enabled;
|
|
35
|
-
this.
|
|
64
|
+
this.apiKey = options.apiKey ?? options.publicKey ?? "";
|
|
65
|
+
this.apiOrigins = [...(options.apiOrigins ?? [])];
|
|
36
66
|
this.otlpEndpoint = options.otlpEndpoint;
|
|
37
|
-
this.
|
|
67
|
+
this.sessionIdStorageKey = `${SESSION_ID_STORAGE_PREFIX}${options.serviceName ?? "default"}`;
|
|
68
|
+
this._sessionId = getOrCreateSessionId(options.sessionId, this.sessionIdStorageKey);
|
|
38
69
|
this.intervalMs = Math.max(options.interval ?? 5000, 5000);
|
|
39
70
|
this.serviceName = options.serviceName;
|
|
40
71
|
this.environment = options.environment;
|
|
41
72
|
this.userId = options.userId;
|
|
42
73
|
this.maskAllInputs = options.maskAllInputs !== false;
|
|
74
|
+
this.maskTextSelector = options.maskTextSelector;
|
|
75
|
+
this.blockSelector = options.blockSelector;
|
|
76
|
+
this.onUploadStatus = options.onUploadStatus;
|
|
43
77
|
this.recordOptions = options.recordOptions ?? {};
|
|
44
78
|
this.replacers = options.replacers ?? {};
|
|
45
79
|
this.tags = { ...(options.tags ?? {}) };
|
|
@@ -53,7 +87,11 @@ export class RecordSdk {
|
|
|
53
87
|
throw new Error("fetch is not available");
|
|
54
88
|
});
|
|
55
89
|
if (this.enabled) {
|
|
56
|
-
initializeHttpInterceptor(
|
|
90
|
+
initializeHttpInterceptor({
|
|
91
|
+
apiOrigins: this.apiOrigins,
|
|
92
|
+
httpBodyCapture: options.httpBodyCapture,
|
|
93
|
+
createRequestSpan: (method, url) => this.createRequestSpan(method, url),
|
|
94
|
+
});
|
|
57
95
|
if (!options.manual) {
|
|
58
96
|
this.record();
|
|
59
97
|
}
|
|
@@ -62,11 +100,14 @@ export class RecordSdk {
|
|
|
62
100
|
get sessionId() {
|
|
63
101
|
return this._sessionId;
|
|
64
102
|
}
|
|
103
|
+
get uploadStatus() {
|
|
104
|
+
return this._uploadStatus;
|
|
105
|
+
}
|
|
65
106
|
static init(options = {}) {
|
|
66
107
|
const softDisable = options.softDisable !== false;
|
|
67
108
|
try {
|
|
68
109
|
const resolved = resolveSoftprobeConfigFromObject({
|
|
69
|
-
publicKey: options.publicKey,
|
|
110
|
+
publicKey: options.apiKey ?? options.publicKey,
|
|
70
111
|
baseUrl: options.baseUrl,
|
|
71
112
|
otlpEndpoint: options.otlpEndpoint,
|
|
72
113
|
environment: options.environment,
|
|
@@ -101,13 +142,22 @@ export class RecordSdk {
|
|
|
101
142
|
record(opts) {
|
|
102
143
|
if (!this.enabled)
|
|
103
144
|
return null;
|
|
145
|
+
// 熔断后不再重新武装。宿主(SPA)每次路由变化都会调 record(),不挡住的话
|
|
146
|
+
// 每导航一次就白起一轮 rrweb 采集,下一个 tick 再自己停掉。
|
|
147
|
+
if (this.exportDisabled)
|
|
148
|
+
return null;
|
|
104
149
|
if (this.stopHandle)
|
|
105
150
|
return this.stopHandle;
|
|
106
151
|
if (opts?.tags)
|
|
107
152
|
this.setTags(opts.tags);
|
|
153
|
+
if (typeof window !== "undefined") {
|
|
154
|
+
window.addEventListener("pagehide", this.onPageHide);
|
|
155
|
+
}
|
|
108
156
|
const stopFn = rrwebRecord({
|
|
109
157
|
maskAllInputs: this.maskAllInputs,
|
|
110
158
|
...this.recordOptions,
|
|
159
|
+
...(this.maskTextSelector ? { maskTextSelector: this.maskTextSelector } : {}),
|
|
160
|
+
...(this.blockSelector ? { blockSelector: this.blockSelector } : {}),
|
|
111
161
|
emit: (raw) => {
|
|
112
162
|
const event = raw;
|
|
113
163
|
if (this.events.length >= MAX_EVENTS)
|
|
@@ -120,6 +170,14 @@ export class RecordSdk {
|
|
|
120
170
|
},
|
|
121
171
|
});
|
|
122
172
|
const intervalId = setInterval(() => {
|
|
173
|
+
// 熔断后连采集一起停:导出不出去还继续录,只是白耗主线程和内存。
|
|
174
|
+
if (this.exportDisabled) {
|
|
175
|
+
this.stop();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// 退避未到,这轮跳过。这里刻意不打日志——日志本身就是故障放大器。
|
|
179
|
+
if (Date.now() < this.nextAttemptAt)
|
|
180
|
+
return;
|
|
123
181
|
void this.flush();
|
|
124
182
|
if (this.events.length) {
|
|
125
183
|
const last = this.events[this.events.length - 1];
|
|
@@ -133,6 +191,9 @@ export class RecordSdk {
|
|
|
133
191
|
this.stopHandle = {
|
|
134
192
|
stop: () => {
|
|
135
193
|
clearInterval(intervalId);
|
|
194
|
+
if (typeof window !== "undefined") {
|
|
195
|
+
window.removeEventListener("pagehide", this.onPageHide);
|
|
196
|
+
}
|
|
136
197
|
stopFn?.();
|
|
137
198
|
void this.flush();
|
|
138
199
|
this.stopHandle = null;
|
|
@@ -159,8 +220,13 @@ export class RecordSdk {
|
|
|
159
220
|
async switchSessionId(next) {
|
|
160
221
|
await this.drainFlush();
|
|
161
222
|
this._sessionId = next;
|
|
162
|
-
|
|
163
|
-
|
|
223
|
+
try {
|
|
224
|
+
if (typeof sessionStorage !== "undefined") {
|
|
225
|
+
sessionStorage.setItem(this.sessionIdStorageKey, next);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Storage can be unavailable in restricted browser contexts.
|
|
164
230
|
}
|
|
165
231
|
}
|
|
166
232
|
/**
|
|
@@ -202,10 +268,35 @@ export class RecordSdk {
|
|
|
202
268
|
}
|
|
203
269
|
return event;
|
|
204
270
|
}
|
|
205
|
-
async flush() {
|
|
206
|
-
if (
|
|
271
|
+
async flush(options = {}) {
|
|
272
|
+
if (this.exportDisabled)
|
|
273
|
+
return false;
|
|
274
|
+
if (!this.enabled)
|
|
207
275
|
return true;
|
|
276
|
+
let recordingResult = true;
|
|
277
|
+
if (this.saving) {
|
|
278
|
+
recordingResult = await (this.recordingFlush ?? Promise.resolve(true));
|
|
279
|
+
}
|
|
280
|
+
else if (this.events.length > 0) {
|
|
281
|
+
const recordingFlush = this.flushRecordingBatch(options);
|
|
282
|
+
this.recordingFlush = recordingFlush;
|
|
283
|
+
try {
|
|
284
|
+
recordingResult = await recordingFlush;
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
if (this.recordingFlush === recordingFlush)
|
|
288
|
+
this.recordingFlush = null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const clientSpansResult = await this.flushPendingClientSpanExports();
|
|
292
|
+
return recordingResult && clientSpansResult;
|
|
293
|
+
}
|
|
294
|
+
async flushRecordingBatch(options) {
|
|
295
|
+
if (this.exportDisabled || !this.enabled || this.events.length === 0)
|
|
296
|
+
return !this.exportDisabled;
|
|
208
297
|
this.saving = true;
|
|
298
|
+
if (!options.keepalive)
|
|
299
|
+
this.setUploadStatus("pending");
|
|
209
300
|
// Splice out the batch BEFORE await so events recorded while export is in
|
|
210
301
|
// flight stay in `this.events` and are not dropped on success.
|
|
211
302
|
const batch = this.events.splice(0, this.events.length);
|
|
@@ -239,26 +330,160 @@ export class RecordSdk {
|
|
|
239
330
|
method: "POST",
|
|
240
331
|
headers: {
|
|
241
332
|
"Content-Type": "application/json",
|
|
242
|
-
Authorization: `Bearer ${this.
|
|
333
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
243
334
|
},
|
|
244
335
|
body: JSON.stringify(body),
|
|
245
336
|
redirect: "follow",
|
|
337
|
+
...(options.keepalive ? { keepalive: true } : {}),
|
|
246
338
|
});
|
|
247
339
|
if (!res.ok) {
|
|
248
340
|
throw new Error(`OTLP export failed: ${res.status} ${await res.text()}`);
|
|
249
341
|
}
|
|
250
342
|
this.batchIndex = batchIndex + 1;
|
|
343
|
+
this.consecutiveFailures = 0;
|
|
344
|
+
this.nextAttemptAt = 0;
|
|
345
|
+
this.setUploadStatus("uploaded");
|
|
251
346
|
return true;
|
|
252
347
|
}
|
|
253
348
|
catch (error) {
|
|
254
|
-
console.error("[@softprobe/web-record] Failed to flush recording:", error);
|
|
255
349
|
this.events.unshift(...batch);
|
|
350
|
+
this.setUploadStatus("failed");
|
|
351
|
+
this.noteExportFailure(error);
|
|
256
352
|
return false;
|
|
257
353
|
}
|
|
258
354
|
finally {
|
|
259
355
|
this.saving = false;
|
|
260
356
|
}
|
|
261
357
|
}
|
|
358
|
+
async flushPendingClientSpanExports() {
|
|
359
|
+
let succeeded = true;
|
|
360
|
+
while (this.pendingClientSpanExports.size > 0) {
|
|
361
|
+
const batch = [...this.pendingClientSpanExports];
|
|
362
|
+
const results = await Promise.all(batch);
|
|
363
|
+
succeeded = results.every(Boolean) && succeeded;
|
|
364
|
+
}
|
|
365
|
+
return succeeded;
|
|
366
|
+
}
|
|
367
|
+
setUploadStatus(status) {
|
|
368
|
+
this._uploadStatus = status;
|
|
369
|
+
try {
|
|
370
|
+
this.onUploadStatus?.(status);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// Application status rendering must not interrupt recording or export.
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
createRequestSpan(method, rawUrl) {
|
|
377
|
+
let url;
|
|
378
|
+
try {
|
|
379
|
+
url = new URL(rawUrl, typeof location !== "undefined" ? location.href : undefined);
|
|
380
|
+
const endpoint = new URL(this.otlpEndpoint);
|
|
381
|
+
// Exclude only the collector endpoint itself. An application may use a
|
|
382
|
+
// same-origin gateway for its own API and telemetry ingestion.
|
|
383
|
+
if (url.origin === endpoint.origin && url.pathname === endpoint.pathname)
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
const traceId = randomHexId(16);
|
|
390
|
+
const spanId = randomHexId(8);
|
|
391
|
+
const sessionId = this._sessionId;
|
|
392
|
+
const startMs = Date.now();
|
|
393
|
+
let finished = false;
|
|
394
|
+
let resolveCompletion;
|
|
395
|
+
const completion = new Promise((resolve) => { resolveCompletion = resolve; });
|
|
396
|
+
this.pendingClientSpanExports.add(completion);
|
|
397
|
+
void completion.finally(() => this.pendingClientSpanExports.delete(completion));
|
|
398
|
+
return {
|
|
399
|
+
traceparent: `00-${traceId}-${spanId}-01`,
|
|
400
|
+
finish: (statusCode, error, bodies) => {
|
|
401
|
+
if (finished)
|
|
402
|
+
return;
|
|
403
|
+
finished = true;
|
|
404
|
+
const endMs = Date.now();
|
|
405
|
+
try {
|
|
406
|
+
const body = buildClientRequestExport({
|
|
407
|
+
traceId,
|
|
408
|
+
spanId,
|
|
409
|
+
sessionId,
|
|
410
|
+
method,
|
|
411
|
+
scheme: url.protocol.slice(0, -1),
|
|
412
|
+
serverAddress: url.hostname,
|
|
413
|
+
path: url.pathname,
|
|
414
|
+
statusCode,
|
|
415
|
+
failed: Boolean(error) || statusCode >= 500,
|
|
416
|
+
requestBody: bodies?.requestBody,
|
|
417
|
+
responseBody: bodies?.responseBody,
|
|
418
|
+
startTimeUnixNano: msToUnixNano(startMs),
|
|
419
|
+
endTimeUnixNano: msToUnixNano(endMs),
|
|
420
|
+
serviceName: this.serviceName,
|
|
421
|
+
environment: this.environment,
|
|
422
|
+
});
|
|
423
|
+
void this.exportClientRequestSpan(body).then(resolveCompletion, () => resolveCompletion(false));
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
resolveCompletion(false);
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async exportClientRequestSpan(body) {
|
|
432
|
+
this.setUploadStatus("pending");
|
|
433
|
+
try {
|
|
434
|
+
const response = await this.fetchImpl(this.otlpEndpoint, {
|
|
435
|
+
method: "POST",
|
|
436
|
+
headers: {
|
|
437
|
+
"Content-Type": "application/json",
|
|
438
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
439
|
+
},
|
|
440
|
+
body: JSON.stringify(body),
|
|
441
|
+
redirect: "follow",
|
|
442
|
+
// Client spans close with the application request and are often
|
|
443
|
+
// exported immediately before a full-page navigation.
|
|
444
|
+
keepalive: true,
|
|
445
|
+
});
|
|
446
|
+
if (!response.ok)
|
|
447
|
+
throw new Error(`OTLP export failed: ${response.status}`);
|
|
448
|
+
this.setUploadStatus("uploaded");
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
this.setUploadStatus("failed");
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* 导出失败后的退避与熔断。
|
|
458
|
+
*
|
|
459
|
+
* <p>现场实证(2026-08-25,甲方内网):到不了配置的 collector,原实现每 5 秒把同一批
|
|
460
|
+
* 事件原样重发,失败 429 次没停过 —— 控制台被刷爆,主线程每轮还白做一次
|
|
461
|
+
* `JSON.stringify` + 压缩,页面肉眼可见地卡。三条对策:
|
|
462
|
+
* <ul>
|
|
463
|
+
* <li>连败指数退避,不再固定间隔硬打;</li>
|
|
464
|
+
* <li>只在连败的<b>第一条</b>打日志,后续静默;</li>
|
|
465
|
+
* <li>连败到上限就停掉采集并清空缓冲,再打一条写明端点和处置办法。</li>
|
|
466
|
+
* </ul>
|
|
467
|
+
* 单次失败仍然保留缓冲(可能只是网络抖动),这一点不变。
|
|
468
|
+
*/
|
|
469
|
+
noteExportFailure(error) {
|
|
470
|
+
this.consecutiveFailures += 1;
|
|
471
|
+
if (this.consecutiveFailures === 1) {
|
|
472
|
+
console.error("[@softprobe/web-record] Failed to flush recording:", error);
|
|
473
|
+
}
|
|
474
|
+
if (this.consecutiveFailures >= MAX_CONSECUTIVE_EXPORT_FAILURES) {
|
|
475
|
+
this.exportDisabled = true;
|
|
476
|
+
this.events.length = 0;
|
|
477
|
+
console.error(`[@softprobe/web-record] Giving up after ${this.consecutiveFailures} ` +
|
|
478
|
+
`consecutive export failures; session recording disabled. ` +
|
|
479
|
+
`Endpoint: ${this.otlpEndpoint}. On a closed network, point ` +
|
|
480
|
+
`SOFTPROBE_BASE_URL at a reachable collector or disable web recording.`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.nextAttemptAt =
|
|
484
|
+
Date.now() +
|
|
485
|
+
Math.min(BACKOFF_MAX_MS, this.intervalMs * 2 ** (this.consecutiveFailures - 1));
|
|
486
|
+
}
|
|
262
487
|
async collectSystemInfo() {
|
|
263
488
|
let ua = "";
|
|
264
489
|
let os = "Unknown";
|
|
@@ -314,6 +539,27 @@ function uuid() {
|
|
|
314
539
|
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
|
|
315
540
|
});
|
|
316
541
|
}
|
|
542
|
+
function getOrCreateSessionId(candidate, storageKey) {
|
|
543
|
+
const provided = asNonEmptyString(candidate);
|
|
544
|
+
try {
|
|
545
|
+
if (typeof sessionStorage !== "undefined") {
|
|
546
|
+
if (provided) {
|
|
547
|
+
sessionStorage.setItem(storageKey, provided);
|
|
548
|
+
return provided;
|
|
549
|
+
}
|
|
550
|
+
const stored = asNonEmptyString(sessionStorage.getItem(storageKey));
|
|
551
|
+
if (stored)
|
|
552
|
+
return stored;
|
|
553
|
+
const generated = uuid();
|
|
554
|
+
sessionStorage.setItem(storageKey, generated);
|
|
555
|
+
return generated;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
// sessionStorage may throw for blocked or opaque origins; use an in-memory ID.
|
|
560
|
+
}
|
|
561
|
+
return provided ?? uuid();
|
|
562
|
+
}
|
|
317
563
|
function getOrCreateVisitorId() {
|
|
318
564
|
if (typeof document === "undefined")
|
|
319
565
|
return uuid();
|