@be-link/cls-logger 1.0.11 → 1.0.13
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 +430 -68
- package/dist/ClsLoggerCore.d.ts +38 -5
- package/dist/ClsLoggerCore.d.ts.map +1 -1
- package/dist/index.esm.js +380 -62
- package/dist/index.js +380 -62
- package/dist/index.umd.js +380 -62
- package/dist/mini/ClsLogger.d.ts +6 -0
- package/dist/mini/ClsLogger.d.ts.map +1 -1
- package/dist/mini/behaviorMonitor.d.ts.map +1 -1
- package/dist/mini/deviceInfo.d.ts.map +1 -1
- package/dist/mini/errorMonitor.d.ts.map +1 -1
- package/dist/mini.esm.js +298 -49
- package/dist/mini.js +298 -49
- package/dist/types.d.ts +41 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/web/deviceInfo.d.ts.map +1 -1
- package/dist/web/errorMonitor.d.ts.map +1 -1
- package/dist/web/performanceMonitor.d.ts.map +1 -1
- package/dist/web.esm.js +304 -38
- package/dist/web.js +304 -38
- package/package.json +1 -1
package/dist/web.js
CHANGED
|
@@ -201,6 +201,11 @@ class ClsLoggerCore {
|
|
|
201
201
|
this.batchTimerDueAt = null;
|
|
202
202
|
this.initTs = 0;
|
|
203
203
|
this.startupDelayMs = 0;
|
|
204
|
+
this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
|
|
205
|
+
this.useIdleCallback = false;
|
|
206
|
+
this.idleTimeout = 3000;
|
|
207
|
+
this.pendingIdleCallback = null; // requestIdleCallback 的 id
|
|
208
|
+
this.visibilityCleanup = null;
|
|
204
209
|
// 参考文档:失败缓存 + 重试
|
|
205
210
|
this.failedCacheKey = 'cls_failed_logs';
|
|
206
211
|
this.failedCacheMax = 200;
|
|
@@ -224,7 +229,7 @@ class ClsLoggerCore {
|
|
|
224
229
|
}
|
|
225
230
|
return 'browser';
|
|
226
231
|
}
|
|
227
|
-
init(options) {
|
|
232
|
+
async init(options) {
|
|
228
233
|
this.initTs = Date.now();
|
|
229
234
|
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId;
|
|
230
235
|
const endpoint = options?.tencentCloud?.endpoint ?? options?.endpoint ?? this.endpoint;
|
|
@@ -233,7 +238,7 @@ class ClsLoggerCore {
|
|
|
233
238
|
if (!topicId) {
|
|
234
239
|
// eslint-disable-next-line no-console
|
|
235
240
|
console.warn('ClsLogger.init 没有传 topicID/topic_id');
|
|
236
|
-
return;
|
|
241
|
+
return false;
|
|
237
242
|
}
|
|
238
243
|
const nextEnvType = options.envType ?? this.detectEnvType();
|
|
239
244
|
// envType/endpoint/retryTimes 变化时:重置 client(以及可能的 sdk)
|
|
@@ -270,15 +275,21 @@ class ClsLoggerCore {
|
|
|
270
275
|
this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
|
|
271
276
|
this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
|
|
272
277
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
278
|
+
this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
|
|
279
|
+
this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
|
|
280
|
+
// startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
|
|
281
|
+
this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
|
|
273
282
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
274
283
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
275
284
|
// 预热(避免首条日志触发 import/初始化开销)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
285
|
+
const sdkReadyPromise = this.getInstance()
|
|
286
|
+
.then(() => true)
|
|
287
|
+
.catch(() => false);
|
|
279
288
|
if (this.enabled) {
|
|
280
289
|
// 启动时尝试发送失败缓存
|
|
281
290
|
this.flushFailed();
|
|
291
|
+
// 添加页面可见性监听(确保页面关闭时数据不丢失)
|
|
292
|
+
this.setupVisibilityListener();
|
|
282
293
|
// 初始化后立即启动请求监听
|
|
283
294
|
this.startRequestMonitor(options.requestMonitor);
|
|
284
295
|
// 初始化后立即启动错误监控/性能监控
|
|
@@ -287,6 +298,7 @@ class ClsLoggerCore {
|
|
|
287
298
|
// 初始化后立即启动行为埋点(PV/UV/点击)
|
|
288
299
|
this.startBehaviorMonitor(options.behaviorMonitor);
|
|
289
300
|
}
|
|
301
|
+
return sdkReadyPromise;
|
|
290
302
|
}
|
|
291
303
|
getBaseFields() {
|
|
292
304
|
let auto = undefined;
|
|
@@ -315,6 +327,97 @@ class ClsLoggerCore {
|
|
|
315
327
|
return auto;
|
|
316
328
|
return undefined;
|
|
317
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* 设置页面可见性监听
|
|
332
|
+
* - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
|
|
333
|
+
* - pagehide: 作为移动端 fallback
|
|
334
|
+
* - 子类可覆写此方法以实现平台特定的监听(如小程序的 wx.onAppHide)
|
|
335
|
+
*/
|
|
336
|
+
setupVisibilityListener() {
|
|
337
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
338
|
+
return;
|
|
339
|
+
// 避免重复监听
|
|
340
|
+
if (this.visibilityCleanup)
|
|
341
|
+
return;
|
|
342
|
+
const handleVisibilityChange = () => {
|
|
343
|
+
if (document.visibilityState === 'hidden') {
|
|
344
|
+
// 使用微任务延迟 flush,确保 web-vitals 等第三方库的 visibilitychange 回调先执行
|
|
345
|
+
// 这样 LCP/CLS/INP 等指标能先入队,再被 flush 发送
|
|
346
|
+
// 注意:queueMicrotask 比 setTimeout(0) 更可靠,不会被延迟太久
|
|
347
|
+
queueMicrotask(() => {
|
|
348
|
+
this.flushBatchSync();
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const handlePageHide = () => {
|
|
353
|
+
// pagehide 不能延迟,因为浏览器可能立即关闭页面
|
|
354
|
+
// 但 pagehide 通常在 visibilitychange 之后触发,此时队列应该已经包含 web-vitals 指标
|
|
355
|
+
this.flushBatchSync();
|
|
356
|
+
};
|
|
357
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
358
|
+
window.addEventListener('pagehide', handlePageHide);
|
|
359
|
+
this.visibilityCleanup = () => {
|
|
360
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
361
|
+
window.removeEventListener('pagehide', handlePageHide);
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* 同步发送内存队列(使用 sendBeacon)
|
|
366
|
+
* - 用于页面关闭时确保数据发送
|
|
367
|
+
* - sendBeacon 不可用时降级为缓存到 localStorage
|
|
368
|
+
*/
|
|
369
|
+
flushBatchSync() {
|
|
370
|
+
if (this.memoryQueue.length === 0)
|
|
371
|
+
return;
|
|
372
|
+
// 清除定时器
|
|
373
|
+
if (this.batchTimer) {
|
|
374
|
+
try {
|
|
375
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
376
|
+
cancelIdleCallback(this.batchTimer);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
clearTimeout(this.batchTimer);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// ignore
|
|
384
|
+
}
|
|
385
|
+
this.batchTimer = null;
|
|
386
|
+
}
|
|
387
|
+
this.batchTimerDueAt = null;
|
|
388
|
+
const logs = [...this.memoryQueue];
|
|
389
|
+
this.memoryQueue = [];
|
|
390
|
+
// 优先使用 sendBeacon(页面关闭时可靠发送)
|
|
391
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
392
|
+
try {
|
|
393
|
+
const payload = this.buildSendBeaconPayload(logs);
|
|
394
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
395
|
+
const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
|
|
396
|
+
const success = navigator.sendBeacon(url, blob);
|
|
397
|
+
if (!success) {
|
|
398
|
+
// sendBeacon 返回 false 时,降级缓存
|
|
399
|
+
this.cacheFailedReportLogs(logs);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
this.cacheFailedReportLogs(logs);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
// 不支持 sendBeacon,降级缓存到 localStorage
|
|
408
|
+
this.cacheFailedReportLogs(logs);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* 构建 sendBeacon 的 payload
|
|
413
|
+
*/
|
|
414
|
+
buildSendBeaconPayload(logs) {
|
|
415
|
+
const logList = logs.map((log) => this.buildReportFields(log));
|
|
416
|
+
return JSON.stringify({
|
|
417
|
+
source: this.source,
|
|
418
|
+
logs: logList,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
318
421
|
startRequestMonitor(requestMonitor) {
|
|
319
422
|
if (this.requestMonitorStarted)
|
|
320
423
|
return;
|
|
@@ -560,32 +663,75 @@ class ClsLoggerCore {
|
|
|
560
663
|
return;
|
|
561
664
|
}
|
|
562
665
|
this.memoryQueue.push(log);
|
|
563
|
-
|
|
666
|
+
const now = Date.now();
|
|
667
|
+
// 判断是否在启动合并窗口内
|
|
668
|
+
const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
|
|
669
|
+
// 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
|
|
670
|
+
const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
|
|
671
|
+
if (this.memoryQueue.length >= effectiveMaxSize) {
|
|
564
672
|
void this.flushBatch();
|
|
565
673
|
return;
|
|
566
674
|
}
|
|
567
|
-
const now = Date.now();
|
|
568
675
|
const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
|
|
569
676
|
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
570
677
|
if (!this.batchTimer) {
|
|
571
678
|
this.batchTimerDueAt = desiredDueAt;
|
|
679
|
+
this.scheduleFlush(desiredDelay);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
// 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
|
|
683
|
+
if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
|
|
684
|
+
this.cancelScheduledFlush();
|
|
685
|
+
this.batchTimerDueAt = desiredDueAt;
|
|
686
|
+
this.scheduleFlush(desiredDelay);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 调度批量发送
|
|
691
|
+
* - 先使用 setTimeout 保证最小延迟(desiredDelay)
|
|
692
|
+
* - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
|
|
693
|
+
*/
|
|
694
|
+
scheduleFlush(desiredDelay) {
|
|
695
|
+
if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
|
|
696
|
+
// 先 setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
|
|
697
|
+
this.batchTimer = setTimeout(() => {
|
|
698
|
+
const idleId = requestIdleCallback(() => {
|
|
699
|
+
this.pendingIdleCallback = null;
|
|
700
|
+
void this.flushBatch();
|
|
701
|
+
}, { timeout: this.idleTimeout });
|
|
702
|
+
this.pendingIdleCallback = idleId;
|
|
703
|
+
}, desiredDelay);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
572
706
|
this.batchTimer = setTimeout(() => {
|
|
573
707
|
void this.flushBatch();
|
|
574
708
|
}, desiredDelay);
|
|
575
|
-
return;
|
|
576
709
|
}
|
|
577
|
-
|
|
578
|
-
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* 取消已调度的批量发送
|
|
713
|
+
* - 同时清理 setTimeout 和可能的 requestIdleCallback
|
|
714
|
+
*/
|
|
715
|
+
cancelScheduledFlush() {
|
|
716
|
+
// 清理 setTimeout
|
|
717
|
+
if (this.batchTimer) {
|
|
579
718
|
try {
|
|
580
719
|
clearTimeout(this.batchTimer);
|
|
581
720
|
}
|
|
582
721
|
catch {
|
|
583
722
|
// ignore
|
|
584
723
|
}
|
|
585
|
-
this.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
724
|
+
this.batchTimer = null;
|
|
725
|
+
}
|
|
726
|
+
// 清理可能的 pendingIdleCallback
|
|
727
|
+
if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
|
|
728
|
+
try {
|
|
729
|
+
cancelIdleCallback(this.pendingIdleCallback);
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
// ignore
|
|
733
|
+
}
|
|
734
|
+
this.pendingIdleCallback = null;
|
|
589
735
|
}
|
|
590
736
|
}
|
|
591
737
|
getDesiredBatchFlushDueAt(nowTs) {
|
|
@@ -598,7 +744,7 @@ class ClsLoggerCore {
|
|
|
598
744
|
}
|
|
599
745
|
return nowTs + this.batchIntervalMs;
|
|
600
746
|
}
|
|
601
|
-
info(message, data = {}) {
|
|
747
|
+
info(message, data = {}, options) {
|
|
602
748
|
let msg = '';
|
|
603
749
|
let extra = {};
|
|
604
750
|
if (message instanceof Error) {
|
|
@@ -614,9 +760,18 @@ class ClsLoggerCore {
|
|
|
614
760
|
extra = data;
|
|
615
761
|
}
|
|
616
762
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
|
|
617
|
-
|
|
763
|
+
const log = { type: 'info', data: payload, timestamp: Date.now() };
|
|
764
|
+
// info 默认走批量队列,支持 immediate 选项立即发送
|
|
765
|
+
if (options?.immediate) {
|
|
766
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
767
|
+
this.cacheFailedReportLogs([log]);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
this.report(log);
|
|
772
|
+
}
|
|
618
773
|
}
|
|
619
|
-
warn(message, data = {}) {
|
|
774
|
+
warn(message, data = {}, options) {
|
|
620
775
|
let msg = '';
|
|
621
776
|
let extra = {};
|
|
622
777
|
if (message instanceof Error) {
|
|
@@ -632,9 +787,18 @@ class ClsLoggerCore {
|
|
|
632
787
|
extra = data;
|
|
633
788
|
}
|
|
634
789
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
|
|
635
|
-
|
|
790
|
+
const log = { type: 'warn', data: payload, timestamp: Date.now() };
|
|
791
|
+
// warn 默认走批量队列,支持 immediate 选项立即发送
|
|
792
|
+
if (options?.immediate) {
|
|
793
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
794
|
+
this.cacheFailedReportLogs([log]);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
this.report(log);
|
|
799
|
+
}
|
|
636
800
|
}
|
|
637
|
-
error(message, data = {}) {
|
|
801
|
+
error(message, data = {}, options) {
|
|
638
802
|
let msg = '';
|
|
639
803
|
let extra = {};
|
|
640
804
|
if (message instanceof Error) {
|
|
@@ -650,7 +814,17 @@ class ClsLoggerCore {
|
|
|
650
814
|
extra = data;
|
|
651
815
|
}
|
|
652
816
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
|
|
653
|
-
|
|
817
|
+
const log = { type: 'error', data: payload, timestamp: Date.now() };
|
|
818
|
+
// error 默认即时上报,除非显式指定 immediate: false
|
|
819
|
+
const immediate = options?.immediate ?? true;
|
|
820
|
+
if (immediate) {
|
|
821
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
822
|
+
this.cacheFailedReportLogs([log]);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
this.report(log);
|
|
827
|
+
}
|
|
654
828
|
}
|
|
655
829
|
track(trackType, data = {}) {
|
|
656
830
|
if (!trackType)
|
|
@@ -665,10 +839,7 @@ class ClsLoggerCore {
|
|
|
665
839
|
* 立即发送内存队列
|
|
666
840
|
*/
|
|
667
841
|
async flushBatch() {
|
|
668
|
-
|
|
669
|
-
clearTimeout(this.batchTimer);
|
|
670
|
-
this.batchTimer = null;
|
|
671
|
-
}
|
|
842
|
+
this.cancelScheduledFlush();
|
|
672
843
|
this.batchTimerDueAt = null;
|
|
673
844
|
if (this.memoryQueue.length === 0)
|
|
674
845
|
return;
|
|
@@ -774,7 +945,14 @@ class ClsLoggerCore {
|
|
|
774
945
|
// 先清空,再尝试发送
|
|
775
946
|
writeStringStorage(this.failedCacheKey, JSON.stringify([]));
|
|
776
947
|
this.memoryQueue.unshift(...logs);
|
|
777
|
-
|
|
948
|
+
// 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
|
|
949
|
+
if (!this.batchTimer) {
|
|
950
|
+
const now = Date.now();
|
|
951
|
+
const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
|
|
952
|
+
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
953
|
+
this.batchTimerDueAt = desiredDueAt;
|
|
954
|
+
this.scheduleFlush(desiredDelay);
|
|
955
|
+
}
|
|
778
956
|
}
|
|
779
957
|
/**
|
|
780
958
|
* 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
|
|
@@ -1068,6 +1246,13 @@ function installWebRequestMonitor(report, opts = {}) {
|
|
|
1068
1246
|
const DEFAULT_MAX_TEXT = 4000;
|
|
1069
1247
|
const DEFAULT_DEDUPE_WINDOW_MS = 3000;
|
|
1070
1248
|
const DEFAULT_DEDUPE_MAX_KEYS = 200;
|
|
1249
|
+
/** 默认忽略的无意义错误信息 */
|
|
1250
|
+
const DEFAULT_IGNORE_MESSAGES = [
|
|
1251
|
+
'Script error.',
|
|
1252
|
+
'Script error',
|
|
1253
|
+
/^ResizeObserver loop/,
|
|
1254
|
+
'Permission was denied',
|
|
1255
|
+
];
|
|
1071
1256
|
function truncate$2(s, maxLen) {
|
|
1072
1257
|
if (!s)
|
|
1073
1258
|
return s;
|
|
@@ -1080,11 +1265,22 @@ function sampleHit$1(sampleRate) {
|
|
|
1080
1265
|
return false;
|
|
1081
1266
|
return Math.random() < sampleRate;
|
|
1082
1267
|
}
|
|
1268
|
+
/** 检查消息是否应该被忽略 */
|
|
1269
|
+
function shouldIgnoreMessage(message, ignorePatterns) {
|
|
1270
|
+
if (!message || ignorePatterns.length === 0)
|
|
1271
|
+
return false;
|
|
1272
|
+
return ignorePatterns.some((pattern) => {
|
|
1273
|
+
if (typeof pattern === 'string') {
|
|
1274
|
+
return message === pattern || message.includes(pattern);
|
|
1275
|
+
}
|
|
1276
|
+
return pattern.test(message);
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1083
1279
|
function getPagePath$1() {
|
|
1084
1280
|
try {
|
|
1085
1281
|
if (typeof window === 'undefined')
|
|
1086
1282
|
return '';
|
|
1087
|
-
return window.location?.
|
|
1283
|
+
return window.location?.href ?? '';
|
|
1088
1284
|
}
|
|
1089
1285
|
catch {
|
|
1090
1286
|
return '';
|
|
@@ -1175,6 +1371,9 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
1175
1371
|
};
|
|
1176
1372
|
if (event && typeof event === 'object' && 'message' in event) {
|
|
1177
1373
|
payload.message = truncate$2(String(event.message ?? ''), options.maxTextLength);
|
|
1374
|
+
// 检查是否应该忽略此错误
|
|
1375
|
+
if (shouldIgnoreMessage(String(event.message ?? ''), options.ignoreMessages))
|
|
1376
|
+
return;
|
|
1178
1377
|
payload.filename = truncate$2(String(event.filename ?? ''), 500);
|
|
1179
1378
|
payload.lineno = typeof event.lineno === 'number' ? event.lineno : undefined;
|
|
1180
1379
|
payload.colno = typeof event.colno === 'number' ? event.colno : undefined;
|
|
@@ -1213,6 +1412,9 @@ function installBrowserErrorMonitor(report, options) {
|
|
|
1213
1412
|
return;
|
|
1214
1413
|
const reason = event?.reason;
|
|
1215
1414
|
const e = normalizeErrorLike(reason, options.maxTextLength);
|
|
1415
|
+
// 检查是否应该忽略此错误
|
|
1416
|
+
if (shouldIgnoreMessage(e.message, options.ignoreMessages))
|
|
1417
|
+
return;
|
|
1216
1418
|
const payload = {
|
|
1217
1419
|
pagePath: getPagePath$1(),
|
|
1218
1420
|
source: 'unhandledrejection',
|
|
@@ -1242,6 +1444,7 @@ function installWebErrorMonitor(report, opts = {}) {
|
|
|
1242
1444
|
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT,
|
|
1243
1445
|
dedupeWindowMs: raw.dedupeWindowMs ?? DEFAULT_DEDUPE_WINDOW_MS,
|
|
1244
1446
|
dedupeMaxKeys: raw.dedupeMaxKeys ?? DEFAULT_DEDUPE_MAX_KEYS,
|
|
1447
|
+
ignoreMessages: raw.ignoreMessages ?? DEFAULT_IGNORE_MESSAGES,
|
|
1245
1448
|
};
|
|
1246
1449
|
installBrowserErrorMonitor(report, options);
|
|
1247
1450
|
}
|
|
@@ -1280,7 +1483,7 @@ function getPagePath() {
|
|
|
1280
1483
|
try {
|
|
1281
1484
|
if (typeof window === 'undefined')
|
|
1282
1485
|
return '';
|
|
1283
|
-
return window.location?.
|
|
1486
|
+
return window.location?.href ?? '';
|
|
1284
1487
|
}
|
|
1285
1488
|
catch {
|
|
1286
1489
|
return '';
|
|
@@ -1452,9 +1655,25 @@ function installBrowserPerformanceMonitor(report, options) {
|
|
|
1452
1655
|
if (!name || shouldIgnoreUrl(name, ignoreUrls))
|
|
1453
1656
|
continue;
|
|
1454
1657
|
const initiatorType = String(entry?.initiatorType ?? '');
|
|
1455
|
-
//
|
|
1658
|
+
// 关注 fetch/xhr/img/script(同时允许 css/other 但不强制)
|
|
1456
1659
|
if (!['xmlhttprequest', 'fetch', 'img', 'script', 'css'].includes(initiatorType))
|
|
1457
1660
|
continue;
|
|
1661
|
+
// 时序分解(便于定位慢在哪个阶段)
|
|
1662
|
+
const domainLookupStart = entry.domainLookupStart ?? 0;
|
|
1663
|
+
const domainLookupEnd = entry.domainLookupEnd ?? 0;
|
|
1664
|
+
const connectStart = entry.connectStart ?? 0;
|
|
1665
|
+
const connectEnd = entry.connectEnd ?? 0;
|
|
1666
|
+
const requestStart = entry.requestStart ?? 0;
|
|
1667
|
+
const responseStart = entry.responseStart ?? 0;
|
|
1668
|
+
const responseEnd = entry.responseEnd ?? 0;
|
|
1669
|
+
const dns = domainLookupEnd - domainLookupStart;
|
|
1670
|
+
const tcp = connectEnd - connectStart;
|
|
1671
|
+
const ttfb = responseStart - requestStart;
|
|
1672
|
+
const download = responseEnd - responseStart;
|
|
1673
|
+
// 缓存检测:transferSize=0 且 decodedBodySize>0 表示缓存命中
|
|
1674
|
+
const transferSize = entry.transferSize ?? 0;
|
|
1675
|
+
const decodedBodySize = entry.decodedBodySize ?? 0;
|
|
1676
|
+
const cached = transferSize === 0 && decodedBodySize > 0;
|
|
1458
1677
|
const payload = {
|
|
1459
1678
|
pagePath: getPagePath(),
|
|
1460
1679
|
metric: 'resource',
|
|
@@ -1462,16 +1681,26 @@ function installBrowserPerformanceMonitor(report, options) {
|
|
|
1462
1681
|
url: truncate$1(name, options.maxTextLength),
|
|
1463
1682
|
startTime: typeof entry?.startTime === 'number' ? entry.startTime : undefined,
|
|
1464
1683
|
duration: typeof entry?.duration === 'number' ? entry.duration : undefined,
|
|
1684
|
+
// 时序分解(跨域资源可能为 0)
|
|
1685
|
+
dns: dns > 0 ? Math.round(dns) : undefined,
|
|
1686
|
+
tcp: tcp > 0 ? Math.round(tcp) : undefined,
|
|
1687
|
+
ttfb: ttfb > 0 ? Math.round(ttfb) : undefined,
|
|
1688
|
+
download: download > 0 ? Math.round(download) : undefined,
|
|
1689
|
+
// 缓存标记
|
|
1690
|
+
cached,
|
|
1465
1691
|
};
|
|
1466
|
-
//
|
|
1467
|
-
if (
|
|
1468
|
-
payload.transferSize =
|
|
1469
|
-
if (typeof entry?.encodedBodySize === 'number')
|
|
1692
|
+
// 尺寸字段(跨域资源可能为 0)
|
|
1693
|
+
if (transferSize > 0)
|
|
1694
|
+
payload.transferSize = transferSize;
|
|
1695
|
+
if (typeof entry?.encodedBodySize === 'number' && entry.encodedBodySize > 0) {
|
|
1470
1696
|
payload.encodedBodySize = entry.encodedBodySize;
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1697
|
+
}
|
|
1698
|
+
if (decodedBodySize > 0)
|
|
1699
|
+
payload.decodedBodySize = decodedBodySize;
|
|
1700
|
+
// 协议和状态
|
|
1701
|
+
if (typeof entry?.nextHopProtocol === 'string' && entry.nextHopProtocol) {
|
|
1474
1702
|
payload.nextHopProtocol = entry.nextHopProtocol;
|
|
1703
|
+
}
|
|
1475
1704
|
if (typeof entry?.responseStatus === 'number')
|
|
1476
1705
|
payload.status = entry.responseStatus;
|
|
1477
1706
|
report(options.reportType, payload);
|
|
@@ -1549,7 +1778,7 @@ function writeUvMeta(key, meta) {
|
|
|
1549
1778
|
function getWebPagePath() {
|
|
1550
1779
|
if (typeof window === 'undefined')
|
|
1551
1780
|
return '';
|
|
1552
|
-
return window.location?.
|
|
1781
|
+
return window.location?.href || '';
|
|
1553
1782
|
}
|
|
1554
1783
|
function buildCommonUvFields(uvId, uvMeta, isFirstVisit) {
|
|
1555
1784
|
return {
|
|
@@ -1860,7 +2089,7 @@ function getBrowserDeviceInfo(options) {
|
|
|
1860
2089
|
if (options.includeNetwork) {
|
|
1861
2090
|
try {
|
|
1862
2091
|
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
1863
|
-
if (conn &&
|
|
2092
|
+
if (conn && typeof conn === 'object') {
|
|
1864
2093
|
if (typeof conn.effectiveType === 'string')
|
|
1865
2094
|
out.netEffectiveType = conn.effectiveType;
|
|
1866
2095
|
if (typeof conn.downlink === 'number')
|
|
@@ -1875,6 +2104,43 @@ function getBrowserDeviceInfo(options) {
|
|
|
1875
2104
|
// ignore
|
|
1876
2105
|
}
|
|
1877
2106
|
}
|
|
2107
|
+
if (options.includeNetworkType) {
|
|
2108
|
+
try {
|
|
2109
|
+
const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
2110
|
+
if (conn && typeof conn === 'object') {
|
|
2111
|
+
const type = conn.type;
|
|
2112
|
+
const eff = conn.effectiveType;
|
|
2113
|
+
let val = '';
|
|
2114
|
+
if (type === 'wifi' || type === 'ethernet') {
|
|
2115
|
+
val = 'WIFI';
|
|
2116
|
+
}
|
|
2117
|
+
else if (type === 'cellular' || type === 'wimax') {
|
|
2118
|
+
if (eff === 'slow-2g' || eff === '2g')
|
|
2119
|
+
val = '2G';
|
|
2120
|
+
else if (eff === '3g')
|
|
2121
|
+
val = '3G';
|
|
2122
|
+
else if (eff === '4g')
|
|
2123
|
+
val = '4G';
|
|
2124
|
+
else
|
|
2125
|
+
val = '4G';
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
// type 未知或不支持,降级使用 effectiveType
|
|
2129
|
+
if (eff === 'slow-2g' || eff === '2g')
|
|
2130
|
+
val = '2G';
|
|
2131
|
+
else if (eff === '3g')
|
|
2132
|
+
val = '3G';
|
|
2133
|
+
else if (eff === '4g')
|
|
2134
|
+
val = '4G';
|
|
2135
|
+
}
|
|
2136
|
+
if (val)
|
|
2137
|
+
out.networkType = val;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
catch {
|
|
2141
|
+
// ignore
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
1878
2144
|
return out;
|
|
1879
2145
|
}
|
|
1880
2146
|
function createWebDeviceInfoBaseFields(opts) {
|
|
@@ -1904,7 +2170,7 @@ function createWebDeviceInfoBaseFields(opts) {
|
|
|
1904
2170
|
try {
|
|
1905
2171
|
const g = globalThis;
|
|
1906
2172
|
const extra = g[globalKey];
|
|
1907
|
-
if (extra &&
|
|
2173
|
+
if (extra && typeof extra === 'object')
|
|
1908
2174
|
return { ...base, ...extra };
|
|
1909
2175
|
}
|
|
1910
2176
|
catch {
|