@be-link/cls-logger 1.0.1-beta.16 → 1.0.1-beta.18
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 +18 -12
- package/dist/ClsLoggerCore.d.ts +33 -4
- package/dist/ClsLoggerCore.d.ts.map +1 -1
- package/dist/index.esm.js +170 -25
- package/dist/index.js +170 -25
- package/dist/index.umd.js +170 -25
- package/dist/mini.esm.js +170 -25
- package/dist/mini.js +170 -25
- package/dist/types.d.ts +32 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/web.esm.js +170 -25
- package/dist/web.js +170 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -158,9 +158,9 @@ class ClsLoggerCore {
|
|
|
158
158
|
this.sdkLoaderOverride = null;
|
|
159
159
|
this.client = null;
|
|
160
160
|
this.clientPromise = null;
|
|
161
|
-
this.topicId =
|
|
162
|
-
this.endpoint = 'ap-shanghai.cls.tencentcs.com';
|
|
163
|
-
this.retryTimes =
|
|
161
|
+
this.topicId = '17475bcd-6315-4b20-859c-e7b087fb3683';
|
|
162
|
+
this.endpoint = 'https://ap-shanghai.cls.tencentcs.com';
|
|
163
|
+
this.retryTimes = 3;
|
|
164
164
|
this.source = '127.0.0.1';
|
|
165
165
|
this.enabled = true;
|
|
166
166
|
this.projectId = '';
|
|
@@ -180,6 +180,9 @@ class ClsLoggerCore {
|
|
|
180
180
|
this.batchTimerDueAt = null;
|
|
181
181
|
this.initTs = 0;
|
|
182
182
|
this.startupDelayMs = 0;
|
|
183
|
+
this.useIdleCallback = false;
|
|
184
|
+
this.idleTimeout = 3000;
|
|
185
|
+
this.visibilityCleanup = null;
|
|
183
186
|
// 参考文档:失败缓存 + 重试
|
|
184
187
|
this.failedCacheKey = 'cls_failed_logs';
|
|
185
188
|
this.failedCacheMax = 200;
|
|
@@ -205,10 +208,10 @@ class ClsLoggerCore {
|
|
|
205
208
|
}
|
|
206
209
|
init(options) {
|
|
207
210
|
this.initTs = Date.now();
|
|
208
|
-
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId
|
|
211
|
+
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId;
|
|
209
212
|
const endpoint = options?.tencentCloud?.endpoint ?? options?.endpoint ?? this.endpoint;
|
|
210
213
|
const retryTimes = options?.tencentCloud?.retry_times ?? options?.retry_times ?? this.retryTimes;
|
|
211
|
-
const source = options?.
|
|
214
|
+
const source = options?.source ?? this.source;
|
|
212
215
|
if (!topicId) {
|
|
213
216
|
// eslint-disable-next-line no-console
|
|
214
217
|
console.warn('ClsLogger.init 没有传 topicID/topic_id');
|
|
@@ -249,6 +252,8 @@ class ClsLoggerCore {
|
|
|
249
252
|
this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
|
|
250
253
|
this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
|
|
251
254
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
255
|
+
this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
|
|
256
|
+
this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
|
|
252
257
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
253
258
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
254
259
|
// 预热(避免首条日志触发 import/初始化开销)
|
|
@@ -258,6 +263,8 @@ class ClsLoggerCore {
|
|
|
258
263
|
if (this.enabled) {
|
|
259
264
|
// 启动时尝试发送失败缓存
|
|
260
265
|
this.flushFailed();
|
|
266
|
+
// 添加页面可见性监听(确保页面关闭时数据不丢失)
|
|
267
|
+
this.setupVisibilityListener();
|
|
261
268
|
// 初始化后立即启动请求监听
|
|
262
269
|
this.startRequestMonitor(options.requestMonitor);
|
|
263
270
|
// 初始化后立即启动错误监控/性能监控
|
|
@@ -294,6 +301,89 @@ class ClsLoggerCore {
|
|
|
294
301
|
return auto;
|
|
295
302
|
return undefined;
|
|
296
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* 设置页面可见性监听
|
|
306
|
+
* - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
|
|
307
|
+
* - pagehide: 作为移动端 fallback
|
|
308
|
+
*/
|
|
309
|
+
setupVisibilityListener() {
|
|
310
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
311
|
+
return;
|
|
312
|
+
// 避免重复监听
|
|
313
|
+
if (this.visibilityCleanup)
|
|
314
|
+
return;
|
|
315
|
+
const handleVisibilityChange = () => {
|
|
316
|
+
if (document.visibilityState === 'hidden') {
|
|
317
|
+
this.flushBatchSync();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const handlePageHide = () => {
|
|
321
|
+
this.flushBatchSync();
|
|
322
|
+
};
|
|
323
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
324
|
+
window.addEventListener('pagehide', handlePageHide);
|
|
325
|
+
this.visibilityCleanup = () => {
|
|
326
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
327
|
+
window.removeEventListener('pagehide', handlePageHide);
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* 同步发送内存队列(使用 sendBeacon)
|
|
332
|
+
* - 用于页面关闭时确保数据发送
|
|
333
|
+
* - sendBeacon 不可用时降级为缓存到 localStorage
|
|
334
|
+
*/
|
|
335
|
+
flushBatchSync() {
|
|
336
|
+
if (this.memoryQueue.length === 0)
|
|
337
|
+
return;
|
|
338
|
+
// 清除定时器
|
|
339
|
+
if (this.batchTimer) {
|
|
340
|
+
try {
|
|
341
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
342
|
+
cancelIdleCallback(this.batchTimer);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
clearTimeout(this.batchTimer);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// ignore
|
|
350
|
+
}
|
|
351
|
+
this.batchTimer = null;
|
|
352
|
+
}
|
|
353
|
+
this.batchTimerDueAt = null;
|
|
354
|
+
const logs = [...this.memoryQueue];
|
|
355
|
+
this.memoryQueue = [];
|
|
356
|
+
// 优先使用 sendBeacon(页面关闭时可靠发送)
|
|
357
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
358
|
+
try {
|
|
359
|
+
const payload = this.buildSendBeaconPayload(logs);
|
|
360
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
361
|
+
const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
|
|
362
|
+
const success = navigator.sendBeacon(url, blob);
|
|
363
|
+
if (!success) {
|
|
364
|
+
// sendBeacon 返回 false 时,降级缓存
|
|
365
|
+
this.cacheFailedReportLogs(logs);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
this.cacheFailedReportLogs(logs);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
// 不支持 sendBeacon,降级缓存到 localStorage
|
|
374
|
+
this.cacheFailedReportLogs(logs);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* 构建 sendBeacon 的 payload
|
|
379
|
+
*/
|
|
380
|
+
buildSendBeaconPayload(logs) {
|
|
381
|
+
const logList = logs.map((log) => this.buildReportFields(log));
|
|
382
|
+
return JSON.stringify({
|
|
383
|
+
source: this.source,
|
|
384
|
+
logs: logList,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
297
387
|
startRequestMonitor(requestMonitor) {
|
|
298
388
|
if (this.requestMonitorStarted)
|
|
299
389
|
return;
|
|
@@ -548,25 +638,55 @@ class ClsLoggerCore {
|
|
|
548
638
|
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
549
639
|
if (!this.batchTimer) {
|
|
550
640
|
this.batchTimerDueAt = desiredDueAt;
|
|
551
|
-
this.
|
|
552
|
-
void this.flushBatch();
|
|
553
|
-
}, desiredDelay);
|
|
641
|
+
this.scheduleFlush(desiredDelay);
|
|
554
642
|
return;
|
|
555
643
|
}
|
|
556
|
-
// 启动合并窗口内:如果当前 timer
|
|
644
|
+
// 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
|
|
557
645
|
if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
|
|
558
|
-
|
|
559
|
-
clearTimeout(this.batchTimer);
|
|
560
|
-
}
|
|
561
|
-
catch {
|
|
562
|
-
// ignore
|
|
563
|
-
}
|
|
646
|
+
this.cancelScheduledFlush();
|
|
564
647
|
this.batchTimerDueAt = desiredDueAt;
|
|
648
|
+
this.scheduleFlush(desiredDelay);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* 调度批量发送
|
|
653
|
+
* - 支持 requestIdleCallback(浏览器空闲时执行)
|
|
654
|
+
* - 降级为 setTimeout
|
|
655
|
+
*/
|
|
656
|
+
scheduleFlush(desiredDelay) {
|
|
657
|
+
if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
|
|
658
|
+
// 使用 requestIdleCallback,设置 timeout 保证最终执行
|
|
659
|
+
const idleId = requestIdleCallback(() => {
|
|
660
|
+
void this.flushBatch();
|
|
661
|
+
}, { timeout: Math.max(desiredDelay, this.idleTimeout) });
|
|
662
|
+
// 存储 idleId 以便清理(类型兼容处理)
|
|
663
|
+
this.batchTimer = idleId;
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
565
666
|
this.batchTimer = setTimeout(() => {
|
|
566
667
|
void this.flushBatch();
|
|
567
668
|
}, desiredDelay);
|
|
568
669
|
}
|
|
569
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* 取消已调度的批量发送
|
|
673
|
+
*/
|
|
674
|
+
cancelScheduledFlush() {
|
|
675
|
+
if (!this.batchTimer)
|
|
676
|
+
return;
|
|
677
|
+
try {
|
|
678
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
679
|
+
cancelIdleCallback(this.batchTimer);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
clearTimeout(this.batchTimer);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
// ignore
|
|
687
|
+
}
|
|
688
|
+
this.batchTimer = null;
|
|
689
|
+
}
|
|
570
690
|
getDesiredBatchFlushDueAt(nowTs) {
|
|
571
691
|
const start = this.initTs || nowTs;
|
|
572
692
|
const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
|
|
@@ -577,7 +697,7 @@ class ClsLoggerCore {
|
|
|
577
697
|
}
|
|
578
698
|
return nowTs + this.batchIntervalMs;
|
|
579
699
|
}
|
|
580
|
-
info(message, data = {}) {
|
|
700
|
+
info(message, data = {}, options) {
|
|
581
701
|
let msg = '';
|
|
582
702
|
let extra = {};
|
|
583
703
|
if (message instanceof Error) {
|
|
@@ -593,9 +713,18 @@ class ClsLoggerCore {
|
|
|
593
713
|
extra = data;
|
|
594
714
|
}
|
|
595
715
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
|
|
596
|
-
|
|
716
|
+
const log = { type: 'info', data: payload, timestamp: Date.now() };
|
|
717
|
+
// info 默认走批量队列,支持 immediate 选项立即发送
|
|
718
|
+
if (options?.immediate) {
|
|
719
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
720
|
+
this.cacheFailedReportLogs([log]);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
this.report(log);
|
|
725
|
+
}
|
|
597
726
|
}
|
|
598
|
-
warn(message, data = {}) {
|
|
727
|
+
warn(message, data = {}, options) {
|
|
599
728
|
let msg = '';
|
|
600
729
|
let extra = {};
|
|
601
730
|
if (message instanceof Error) {
|
|
@@ -611,9 +740,18 @@ class ClsLoggerCore {
|
|
|
611
740
|
extra = data;
|
|
612
741
|
}
|
|
613
742
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
|
|
614
|
-
|
|
743
|
+
const log = { type: 'warn', data: payload, timestamp: Date.now() };
|
|
744
|
+
// warn 默认走批量队列,支持 immediate 选项立即发送
|
|
745
|
+
if (options?.immediate) {
|
|
746
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
747
|
+
this.cacheFailedReportLogs([log]);
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
this.report(log);
|
|
752
|
+
}
|
|
615
753
|
}
|
|
616
|
-
error(message, data = {}) {
|
|
754
|
+
error(message, data = {}, options) {
|
|
617
755
|
let msg = '';
|
|
618
756
|
let extra = {};
|
|
619
757
|
if (message instanceof Error) {
|
|
@@ -629,7 +767,17 @@ class ClsLoggerCore {
|
|
|
629
767
|
extra = data;
|
|
630
768
|
}
|
|
631
769
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
|
|
632
|
-
|
|
770
|
+
const log = { type: 'error', data: payload, timestamp: Date.now() };
|
|
771
|
+
// error 默认即时上报,除非显式指定 immediate: false
|
|
772
|
+
const immediate = options?.immediate ?? true;
|
|
773
|
+
if (immediate) {
|
|
774
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
775
|
+
this.cacheFailedReportLogs([log]);
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
this.report(log);
|
|
780
|
+
}
|
|
633
781
|
}
|
|
634
782
|
track(trackType, data = {}) {
|
|
635
783
|
if (!trackType)
|
|
@@ -644,10 +792,7 @@ class ClsLoggerCore {
|
|
|
644
792
|
* 立即发送内存队列
|
|
645
793
|
*/
|
|
646
794
|
async flushBatch() {
|
|
647
|
-
|
|
648
|
-
clearTimeout(this.batchTimer);
|
|
649
|
-
this.batchTimer = null;
|
|
650
|
-
}
|
|
795
|
+
this.cancelScheduledFlush();
|
|
651
796
|
this.batchTimerDueAt = null;
|
|
652
797
|
if (this.memoryQueue.length === 0)
|
|
653
798
|
return;
|
package/dist/index.umd.js
CHANGED
|
@@ -160,9 +160,9 @@
|
|
|
160
160
|
this.sdkLoaderOverride = null;
|
|
161
161
|
this.client = null;
|
|
162
162
|
this.clientPromise = null;
|
|
163
|
-
this.topicId =
|
|
164
|
-
this.endpoint = 'ap-shanghai.cls.tencentcs.com';
|
|
165
|
-
this.retryTimes =
|
|
163
|
+
this.topicId = '17475bcd-6315-4b20-859c-e7b087fb3683';
|
|
164
|
+
this.endpoint = 'https://ap-shanghai.cls.tencentcs.com';
|
|
165
|
+
this.retryTimes = 3;
|
|
166
166
|
this.source = '127.0.0.1';
|
|
167
167
|
this.enabled = true;
|
|
168
168
|
this.projectId = '';
|
|
@@ -182,6 +182,9 @@
|
|
|
182
182
|
this.batchTimerDueAt = null;
|
|
183
183
|
this.initTs = 0;
|
|
184
184
|
this.startupDelayMs = 0;
|
|
185
|
+
this.useIdleCallback = false;
|
|
186
|
+
this.idleTimeout = 3000;
|
|
187
|
+
this.visibilityCleanup = null;
|
|
185
188
|
// 参考文档:失败缓存 + 重试
|
|
186
189
|
this.failedCacheKey = 'cls_failed_logs';
|
|
187
190
|
this.failedCacheMax = 200;
|
|
@@ -207,10 +210,10 @@
|
|
|
207
210
|
}
|
|
208
211
|
init(options) {
|
|
209
212
|
this.initTs = Date.now();
|
|
210
|
-
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId
|
|
213
|
+
const topicId = options?.tencentCloud?.topicID ?? options?.topic_id ?? options?.topicID ?? this.topicId;
|
|
211
214
|
const endpoint = options?.tencentCloud?.endpoint ?? options?.endpoint ?? this.endpoint;
|
|
212
215
|
const retryTimes = options?.tencentCloud?.retry_times ?? options?.retry_times ?? this.retryTimes;
|
|
213
|
-
const source = options?.
|
|
216
|
+
const source = options?.source ?? this.source;
|
|
214
217
|
if (!topicId) {
|
|
215
218
|
// eslint-disable-next-line no-console
|
|
216
219
|
console.warn('ClsLogger.init 没有传 topicID/topic_id');
|
|
@@ -251,6 +254,8 @@
|
|
|
251
254
|
this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
|
|
252
255
|
this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
|
|
253
256
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
257
|
+
this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
|
|
258
|
+
this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
|
|
254
259
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
255
260
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
256
261
|
// 预热(避免首条日志触发 import/初始化开销)
|
|
@@ -260,6 +265,8 @@
|
|
|
260
265
|
if (this.enabled) {
|
|
261
266
|
// 启动时尝试发送失败缓存
|
|
262
267
|
this.flushFailed();
|
|
268
|
+
// 添加页面可见性监听(确保页面关闭时数据不丢失)
|
|
269
|
+
this.setupVisibilityListener();
|
|
263
270
|
// 初始化后立即启动请求监听
|
|
264
271
|
this.startRequestMonitor(options.requestMonitor);
|
|
265
272
|
// 初始化后立即启动错误监控/性能监控
|
|
@@ -296,6 +303,89 @@
|
|
|
296
303
|
return auto;
|
|
297
304
|
return undefined;
|
|
298
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* 设置页面可见性监听
|
|
308
|
+
* - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
|
|
309
|
+
* - pagehide: 作为移动端 fallback
|
|
310
|
+
*/
|
|
311
|
+
setupVisibilityListener() {
|
|
312
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
313
|
+
return;
|
|
314
|
+
// 避免重复监听
|
|
315
|
+
if (this.visibilityCleanup)
|
|
316
|
+
return;
|
|
317
|
+
const handleVisibilityChange = () => {
|
|
318
|
+
if (document.visibilityState === 'hidden') {
|
|
319
|
+
this.flushBatchSync();
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
const handlePageHide = () => {
|
|
323
|
+
this.flushBatchSync();
|
|
324
|
+
};
|
|
325
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
326
|
+
window.addEventListener('pagehide', handlePageHide);
|
|
327
|
+
this.visibilityCleanup = () => {
|
|
328
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
329
|
+
window.removeEventListener('pagehide', handlePageHide);
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 同步发送内存队列(使用 sendBeacon)
|
|
334
|
+
* - 用于页面关闭时确保数据发送
|
|
335
|
+
* - sendBeacon 不可用时降级为缓存到 localStorage
|
|
336
|
+
*/
|
|
337
|
+
flushBatchSync() {
|
|
338
|
+
if (this.memoryQueue.length === 0)
|
|
339
|
+
return;
|
|
340
|
+
// 清除定时器
|
|
341
|
+
if (this.batchTimer) {
|
|
342
|
+
try {
|
|
343
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
344
|
+
cancelIdleCallback(this.batchTimer);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
clearTimeout(this.batchTimer);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// ignore
|
|
352
|
+
}
|
|
353
|
+
this.batchTimer = null;
|
|
354
|
+
}
|
|
355
|
+
this.batchTimerDueAt = null;
|
|
356
|
+
const logs = [...this.memoryQueue];
|
|
357
|
+
this.memoryQueue = [];
|
|
358
|
+
// 优先使用 sendBeacon(页面关闭时可靠发送)
|
|
359
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
360
|
+
try {
|
|
361
|
+
const payload = this.buildSendBeaconPayload(logs);
|
|
362
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
363
|
+
const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
|
|
364
|
+
const success = navigator.sendBeacon(url, blob);
|
|
365
|
+
if (!success) {
|
|
366
|
+
// sendBeacon 返回 false 时,降级缓存
|
|
367
|
+
this.cacheFailedReportLogs(logs);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
this.cacheFailedReportLogs(logs);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
// 不支持 sendBeacon,降级缓存到 localStorage
|
|
376
|
+
this.cacheFailedReportLogs(logs);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 构建 sendBeacon 的 payload
|
|
381
|
+
*/
|
|
382
|
+
buildSendBeaconPayload(logs) {
|
|
383
|
+
const logList = logs.map((log) => this.buildReportFields(log));
|
|
384
|
+
return JSON.stringify({
|
|
385
|
+
source: this.source,
|
|
386
|
+
logs: logList,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
299
389
|
startRequestMonitor(requestMonitor) {
|
|
300
390
|
if (this.requestMonitorStarted)
|
|
301
391
|
return;
|
|
@@ -550,25 +640,55 @@
|
|
|
550
640
|
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
551
641
|
if (!this.batchTimer) {
|
|
552
642
|
this.batchTimerDueAt = desiredDueAt;
|
|
553
|
-
this.
|
|
554
|
-
void this.flushBatch();
|
|
555
|
-
}, desiredDelay);
|
|
643
|
+
this.scheduleFlush(desiredDelay);
|
|
556
644
|
return;
|
|
557
645
|
}
|
|
558
|
-
// 启动合并窗口内:如果当前 timer
|
|
646
|
+
// 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
|
|
559
647
|
if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
|
|
560
|
-
|
|
561
|
-
clearTimeout(this.batchTimer);
|
|
562
|
-
}
|
|
563
|
-
catch {
|
|
564
|
-
// ignore
|
|
565
|
-
}
|
|
648
|
+
this.cancelScheduledFlush();
|
|
566
649
|
this.batchTimerDueAt = desiredDueAt;
|
|
650
|
+
this.scheduleFlush(desiredDelay);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* 调度批量发送
|
|
655
|
+
* - 支持 requestIdleCallback(浏览器空闲时执行)
|
|
656
|
+
* - 降级为 setTimeout
|
|
657
|
+
*/
|
|
658
|
+
scheduleFlush(desiredDelay) {
|
|
659
|
+
if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
|
|
660
|
+
// 使用 requestIdleCallback,设置 timeout 保证最终执行
|
|
661
|
+
const idleId = requestIdleCallback(() => {
|
|
662
|
+
void this.flushBatch();
|
|
663
|
+
}, { timeout: Math.max(desiredDelay, this.idleTimeout) });
|
|
664
|
+
// 存储 idleId 以便清理(类型兼容处理)
|
|
665
|
+
this.batchTimer = idleId;
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
567
668
|
this.batchTimer = setTimeout(() => {
|
|
568
669
|
void this.flushBatch();
|
|
569
670
|
}, desiredDelay);
|
|
570
671
|
}
|
|
571
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* 取消已调度的批量发送
|
|
675
|
+
*/
|
|
676
|
+
cancelScheduledFlush() {
|
|
677
|
+
if (!this.batchTimer)
|
|
678
|
+
return;
|
|
679
|
+
try {
|
|
680
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
681
|
+
cancelIdleCallback(this.batchTimer);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
clearTimeout(this.batchTimer);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
// ignore
|
|
689
|
+
}
|
|
690
|
+
this.batchTimer = null;
|
|
691
|
+
}
|
|
572
692
|
getDesiredBatchFlushDueAt(nowTs) {
|
|
573
693
|
const start = this.initTs || nowTs;
|
|
574
694
|
const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
|
|
@@ -579,7 +699,7 @@
|
|
|
579
699
|
}
|
|
580
700
|
return nowTs + this.batchIntervalMs;
|
|
581
701
|
}
|
|
582
|
-
info(message, data = {}) {
|
|
702
|
+
info(message, data = {}, options) {
|
|
583
703
|
let msg = '';
|
|
584
704
|
let extra = {};
|
|
585
705
|
if (message instanceof Error) {
|
|
@@ -595,9 +715,18 @@
|
|
|
595
715
|
extra = data;
|
|
596
716
|
}
|
|
597
717
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
|
|
598
|
-
|
|
718
|
+
const log = { type: 'info', data: payload, timestamp: Date.now() };
|
|
719
|
+
// info 默认走批量队列,支持 immediate 选项立即发送
|
|
720
|
+
if (options?.immediate) {
|
|
721
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
722
|
+
this.cacheFailedReportLogs([log]);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
this.report(log);
|
|
727
|
+
}
|
|
599
728
|
}
|
|
600
|
-
warn(message, data = {}) {
|
|
729
|
+
warn(message, data = {}, options) {
|
|
601
730
|
let msg = '';
|
|
602
731
|
let extra = {};
|
|
603
732
|
if (message instanceof Error) {
|
|
@@ -613,9 +742,18 @@
|
|
|
613
742
|
extra = data;
|
|
614
743
|
}
|
|
615
744
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
|
|
616
|
-
|
|
745
|
+
const log = { type: 'warn', data: payload, timestamp: Date.now() };
|
|
746
|
+
// warn 默认走批量队列,支持 immediate 选项立即发送
|
|
747
|
+
if (options?.immediate) {
|
|
748
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
749
|
+
this.cacheFailedReportLogs([log]);
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
this.report(log);
|
|
754
|
+
}
|
|
617
755
|
}
|
|
618
|
-
error(message, data = {}) {
|
|
756
|
+
error(message, data = {}, options) {
|
|
619
757
|
let msg = '';
|
|
620
758
|
let extra = {};
|
|
621
759
|
if (message instanceof Error) {
|
|
@@ -631,7 +769,17 @@
|
|
|
631
769
|
extra = data;
|
|
632
770
|
}
|
|
633
771
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
|
|
634
|
-
|
|
772
|
+
const log = { type: 'error', data: payload, timestamp: Date.now() };
|
|
773
|
+
// error 默认即时上报,除非显式指定 immediate: false
|
|
774
|
+
const immediate = options?.immediate ?? true;
|
|
775
|
+
if (immediate) {
|
|
776
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
777
|
+
this.cacheFailedReportLogs([log]);
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
this.report(log);
|
|
782
|
+
}
|
|
635
783
|
}
|
|
636
784
|
track(trackType, data = {}) {
|
|
637
785
|
if (!trackType)
|
|
@@ -646,10 +794,7 @@
|
|
|
646
794
|
* 立即发送内存队列
|
|
647
795
|
*/
|
|
648
796
|
async flushBatch() {
|
|
649
|
-
|
|
650
|
-
clearTimeout(this.batchTimer);
|
|
651
|
-
this.batchTimer = null;
|
|
652
|
-
}
|
|
797
|
+
this.cancelScheduledFlush();
|
|
653
798
|
this.batchTimerDueAt = null;
|
|
654
799
|
if (this.memoryQueue.length === 0)
|
|
655
800
|
return;
|