@be-link/cls-logger 1.0.1-beta.17 → 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/dist/ClsLoggerCore.d.ts +33 -4
- package/dist/ClsLoggerCore.d.ts.map +1 -1
- package/dist/index.esm.js +165 -20
- package/dist/index.js +165 -20
- package/dist/index.umd.js +165 -20
- package/dist/mini.esm.js +165 -20
- package/dist/mini.js +165 -20
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web.esm.js +165 -20
- package/dist/web.js +165 -20
- package/package.json +1 -1
package/dist/mini.js
CHANGED
|
@@ -201,6 +201,9 @@ class ClsLoggerCore {
|
|
|
201
201
|
this.batchTimerDueAt = null;
|
|
202
202
|
this.initTs = 0;
|
|
203
203
|
this.startupDelayMs = 0;
|
|
204
|
+
this.useIdleCallback = false;
|
|
205
|
+
this.idleTimeout = 3000;
|
|
206
|
+
this.visibilityCleanup = null;
|
|
204
207
|
// 参考文档:失败缓存 + 重试
|
|
205
208
|
this.failedCacheKey = 'cls_failed_logs';
|
|
206
209
|
this.failedCacheMax = 200;
|
|
@@ -270,6 +273,8 @@ class ClsLoggerCore {
|
|
|
270
273
|
this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
|
|
271
274
|
this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
|
|
272
275
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
276
|
+
this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
|
|
277
|
+
this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
|
|
273
278
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
274
279
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
275
280
|
// 预热(避免首条日志触发 import/初始化开销)
|
|
@@ -279,6 +284,8 @@ class ClsLoggerCore {
|
|
|
279
284
|
if (this.enabled) {
|
|
280
285
|
// 启动时尝试发送失败缓存
|
|
281
286
|
this.flushFailed();
|
|
287
|
+
// 添加页面可见性监听(确保页面关闭时数据不丢失)
|
|
288
|
+
this.setupVisibilityListener();
|
|
282
289
|
// 初始化后立即启动请求监听
|
|
283
290
|
this.startRequestMonitor(options.requestMonitor);
|
|
284
291
|
// 初始化后立即启动错误监控/性能监控
|
|
@@ -315,6 +322,89 @@ class ClsLoggerCore {
|
|
|
315
322
|
return auto;
|
|
316
323
|
return undefined;
|
|
317
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* 设置页面可见性监听
|
|
327
|
+
* - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
|
|
328
|
+
* - pagehide: 作为移动端 fallback
|
|
329
|
+
*/
|
|
330
|
+
setupVisibilityListener() {
|
|
331
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
332
|
+
return;
|
|
333
|
+
// 避免重复监听
|
|
334
|
+
if (this.visibilityCleanup)
|
|
335
|
+
return;
|
|
336
|
+
const handleVisibilityChange = () => {
|
|
337
|
+
if (document.visibilityState === 'hidden') {
|
|
338
|
+
this.flushBatchSync();
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
const handlePageHide = () => {
|
|
342
|
+
this.flushBatchSync();
|
|
343
|
+
};
|
|
344
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
345
|
+
window.addEventListener('pagehide', handlePageHide);
|
|
346
|
+
this.visibilityCleanup = () => {
|
|
347
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
348
|
+
window.removeEventListener('pagehide', handlePageHide);
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 同步发送内存队列(使用 sendBeacon)
|
|
353
|
+
* - 用于页面关闭时确保数据发送
|
|
354
|
+
* - sendBeacon 不可用时降级为缓存到 localStorage
|
|
355
|
+
*/
|
|
356
|
+
flushBatchSync() {
|
|
357
|
+
if (this.memoryQueue.length === 0)
|
|
358
|
+
return;
|
|
359
|
+
// 清除定时器
|
|
360
|
+
if (this.batchTimer) {
|
|
361
|
+
try {
|
|
362
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
363
|
+
cancelIdleCallback(this.batchTimer);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
clearTimeout(this.batchTimer);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// ignore
|
|
371
|
+
}
|
|
372
|
+
this.batchTimer = null;
|
|
373
|
+
}
|
|
374
|
+
this.batchTimerDueAt = null;
|
|
375
|
+
const logs = [...this.memoryQueue];
|
|
376
|
+
this.memoryQueue = [];
|
|
377
|
+
// 优先使用 sendBeacon(页面关闭时可靠发送)
|
|
378
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
379
|
+
try {
|
|
380
|
+
const payload = this.buildSendBeaconPayload(logs);
|
|
381
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
382
|
+
const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
|
|
383
|
+
const success = navigator.sendBeacon(url, blob);
|
|
384
|
+
if (!success) {
|
|
385
|
+
// sendBeacon 返回 false 时,降级缓存
|
|
386
|
+
this.cacheFailedReportLogs(logs);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
this.cacheFailedReportLogs(logs);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// 不支持 sendBeacon,降级缓存到 localStorage
|
|
395
|
+
this.cacheFailedReportLogs(logs);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 构建 sendBeacon 的 payload
|
|
400
|
+
*/
|
|
401
|
+
buildSendBeaconPayload(logs) {
|
|
402
|
+
const logList = logs.map((log) => this.buildReportFields(log));
|
|
403
|
+
return JSON.stringify({
|
|
404
|
+
source: this.source,
|
|
405
|
+
logs: logList,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
318
408
|
startRequestMonitor(requestMonitor) {
|
|
319
409
|
if (this.requestMonitorStarted)
|
|
320
410
|
return;
|
|
@@ -569,25 +659,55 @@ class ClsLoggerCore {
|
|
|
569
659
|
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
570
660
|
if (!this.batchTimer) {
|
|
571
661
|
this.batchTimerDueAt = desiredDueAt;
|
|
572
|
-
this.
|
|
573
|
-
void this.flushBatch();
|
|
574
|
-
}, desiredDelay);
|
|
662
|
+
this.scheduleFlush(desiredDelay);
|
|
575
663
|
return;
|
|
576
664
|
}
|
|
577
|
-
// 启动合并窗口内:如果当前 timer
|
|
665
|
+
// 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
|
|
578
666
|
if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
|
|
579
|
-
|
|
580
|
-
clearTimeout(this.batchTimer);
|
|
581
|
-
}
|
|
582
|
-
catch {
|
|
583
|
-
// ignore
|
|
584
|
-
}
|
|
667
|
+
this.cancelScheduledFlush();
|
|
585
668
|
this.batchTimerDueAt = desiredDueAt;
|
|
669
|
+
this.scheduleFlush(desiredDelay);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* 调度批量发送
|
|
674
|
+
* - 支持 requestIdleCallback(浏览器空闲时执行)
|
|
675
|
+
* - 降级为 setTimeout
|
|
676
|
+
*/
|
|
677
|
+
scheduleFlush(desiredDelay) {
|
|
678
|
+
if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
|
|
679
|
+
// 使用 requestIdleCallback,设置 timeout 保证最终执行
|
|
680
|
+
const idleId = requestIdleCallback(() => {
|
|
681
|
+
void this.flushBatch();
|
|
682
|
+
}, { timeout: Math.max(desiredDelay, this.idleTimeout) });
|
|
683
|
+
// 存储 idleId 以便清理(类型兼容处理)
|
|
684
|
+
this.batchTimer = idleId;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
586
687
|
this.batchTimer = setTimeout(() => {
|
|
587
688
|
void this.flushBatch();
|
|
588
689
|
}, desiredDelay);
|
|
589
690
|
}
|
|
590
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* 取消已调度的批量发送
|
|
694
|
+
*/
|
|
695
|
+
cancelScheduledFlush() {
|
|
696
|
+
if (!this.batchTimer)
|
|
697
|
+
return;
|
|
698
|
+
try {
|
|
699
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
700
|
+
cancelIdleCallback(this.batchTimer);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
clearTimeout(this.batchTimer);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
catch {
|
|
707
|
+
// ignore
|
|
708
|
+
}
|
|
709
|
+
this.batchTimer = null;
|
|
710
|
+
}
|
|
591
711
|
getDesiredBatchFlushDueAt(nowTs) {
|
|
592
712
|
const start = this.initTs || nowTs;
|
|
593
713
|
const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
|
|
@@ -598,7 +718,7 @@ class ClsLoggerCore {
|
|
|
598
718
|
}
|
|
599
719
|
return nowTs + this.batchIntervalMs;
|
|
600
720
|
}
|
|
601
|
-
info(message, data = {}) {
|
|
721
|
+
info(message, data = {}, options) {
|
|
602
722
|
let msg = '';
|
|
603
723
|
let extra = {};
|
|
604
724
|
if (message instanceof Error) {
|
|
@@ -614,9 +734,18 @@ class ClsLoggerCore {
|
|
|
614
734
|
extra = data;
|
|
615
735
|
}
|
|
616
736
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
|
|
617
|
-
|
|
737
|
+
const log = { type: 'info', data: payload, timestamp: Date.now() };
|
|
738
|
+
// info 默认走批量队列,支持 immediate 选项立即发送
|
|
739
|
+
if (options?.immediate) {
|
|
740
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
741
|
+
this.cacheFailedReportLogs([log]);
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
this.report(log);
|
|
746
|
+
}
|
|
618
747
|
}
|
|
619
|
-
warn(message, data = {}) {
|
|
748
|
+
warn(message, data = {}, options) {
|
|
620
749
|
let msg = '';
|
|
621
750
|
let extra = {};
|
|
622
751
|
if (message instanceof Error) {
|
|
@@ -632,9 +761,18 @@ class ClsLoggerCore {
|
|
|
632
761
|
extra = data;
|
|
633
762
|
}
|
|
634
763
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
|
|
635
|
-
|
|
764
|
+
const log = { type: 'warn', data: payload, timestamp: Date.now() };
|
|
765
|
+
// warn 默认走批量队列,支持 immediate 选项立即发送
|
|
766
|
+
if (options?.immediate) {
|
|
767
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
768
|
+
this.cacheFailedReportLogs([log]);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
this.report(log);
|
|
773
|
+
}
|
|
636
774
|
}
|
|
637
|
-
error(message, data = {}) {
|
|
775
|
+
error(message, data = {}, options) {
|
|
638
776
|
let msg = '';
|
|
639
777
|
let extra = {};
|
|
640
778
|
if (message instanceof Error) {
|
|
@@ -650,7 +788,17 @@ class ClsLoggerCore {
|
|
|
650
788
|
extra = data;
|
|
651
789
|
}
|
|
652
790
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
|
|
653
|
-
|
|
791
|
+
const log = { type: 'error', data: payload, timestamp: Date.now() };
|
|
792
|
+
// error 默认即时上报,除非显式指定 immediate: false
|
|
793
|
+
const immediate = options?.immediate ?? true;
|
|
794
|
+
if (immediate) {
|
|
795
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
796
|
+
this.cacheFailedReportLogs([log]);
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
this.report(log);
|
|
801
|
+
}
|
|
654
802
|
}
|
|
655
803
|
track(trackType, data = {}) {
|
|
656
804
|
if (!trackType)
|
|
@@ -665,10 +813,7 @@ class ClsLoggerCore {
|
|
|
665
813
|
* 立即发送内存队列
|
|
666
814
|
*/
|
|
667
815
|
async flushBatch() {
|
|
668
|
-
|
|
669
|
-
clearTimeout(this.batchTimer);
|
|
670
|
-
this.batchTimer = null;
|
|
671
|
-
}
|
|
816
|
+
this.cancelScheduledFlush();
|
|
672
817
|
this.batchTimerDueAt = null;
|
|
673
818
|
if (this.memoryQueue.length === 0)
|
|
674
819
|
return;
|
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,31 @@ export interface BatchOptions {
|
|
|
33
33
|
* - 默认:0(不开启)
|
|
34
34
|
*/
|
|
35
35
|
startupDelayMs?: number;
|
|
36
|
+
/**
|
|
37
|
+
* 是否使用浏览器空闲时间上报
|
|
38
|
+
* - 开启后会使用 requestIdleCallback 代替 setTimeout 调度批量发送
|
|
39
|
+
* - 配合 idleTimeout 使用,保证即使浏览器繁忙也能在超时后发送
|
|
40
|
+
* - 默认:false
|
|
41
|
+
*/
|
|
42
|
+
useIdleCallback?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* 空闲回调超时时间(ms)
|
|
45
|
+
* - 当 useIdleCallback 为 true 时生效
|
|
46
|
+
* - 即使浏览器繁忙,也会在此时间后强制执行
|
|
47
|
+
* - 默认:3000
|
|
48
|
+
*/
|
|
49
|
+
idleTimeout?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* info/warn/error 等方法的上报选项
|
|
53
|
+
*/
|
|
54
|
+
export interface ReportOptions {
|
|
55
|
+
/**
|
|
56
|
+
* 是否立即发送,不等待批量队列
|
|
57
|
+
* - error() 默认为 true(即时上报)
|
|
58
|
+
* - info()/warn() 默认为 false(走批量队列)
|
|
59
|
+
*/
|
|
60
|
+
immediate?: boolean;
|
|
36
61
|
}
|
|
37
62
|
export interface ClsLoggerBaseOptions {
|
|
38
63
|
/**
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAC7D,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,EAAE,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC;AAEhE,sCAAsC;AACtC,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAExD,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC;AAEpC,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AAEhD,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/web.esm.js
CHANGED
|
@@ -178,6 +178,9 @@ class ClsLoggerCore {
|
|
|
178
178
|
this.batchTimerDueAt = null;
|
|
179
179
|
this.initTs = 0;
|
|
180
180
|
this.startupDelayMs = 0;
|
|
181
|
+
this.useIdleCallback = false;
|
|
182
|
+
this.idleTimeout = 3000;
|
|
183
|
+
this.visibilityCleanup = null;
|
|
181
184
|
// 参考文档:失败缓存 + 重试
|
|
182
185
|
this.failedCacheKey = 'cls_failed_logs';
|
|
183
186
|
this.failedCacheMax = 200;
|
|
@@ -247,6 +250,8 @@ class ClsLoggerCore {
|
|
|
247
250
|
this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
|
|
248
251
|
this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
|
|
249
252
|
this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
|
|
253
|
+
this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
|
|
254
|
+
this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
|
|
250
255
|
this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
|
|
251
256
|
this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
|
|
252
257
|
// 预热(避免首条日志触发 import/初始化开销)
|
|
@@ -256,6 +261,8 @@ class ClsLoggerCore {
|
|
|
256
261
|
if (this.enabled) {
|
|
257
262
|
// 启动时尝试发送失败缓存
|
|
258
263
|
this.flushFailed();
|
|
264
|
+
// 添加页面可见性监听(确保页面关闭时数据不丢失)
|
|
265
|
+
this.setupVisibilityListener();
|
|
259
266
|
// 初始化后立即启动请求监听
|
|
260
267
|
this.startRequestMonitor(options.requestMonitor);
|
|
261
268
|
// 初始化后立即启动错误监控/性能监控
|
|
@@ -292,6 +299,89 @@ class ClsLoggerCore {
|
|
|
292
299
|
return auto;
|
|
293
300
|
return undefined;
|
|
294
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* 设置页面可见性监听
|
|
304
|
+
* - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
|
|
305
|
+
* - pagehide: 作为移动端 fallback
|
|
306
|
+
*/
|
|
307
|
+
setupVisibilityListener() {
|
|
308
|
+
if (typeof document === 'undefined' || typeof window === 'undefined')
|
|
309
|
+
return;
|
|
310
|
+
// 避免重复监听
|
|
311
|
+
if (this.visibilityCleanup)
|
|
312
|
+
return;
|
|
313
|
+
const handleVisibilityChange = () => {
|
|
314
|
+
if (document.visibilityState === 'hidden') {
|
|
315
|
+
this.flushBatchSync();
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
const handlePageHide = () => {
|
|
319
|
+
this.flushBatchSync();
|
|
320
|
+
};
|
|
321
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
322
|
+
window.addEventListener('pagehide', handlePageHide);
|
|
323
|
+
this.visibilityCleanup = () => {
|
|
324
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
325
|
+
window.removeEventListener('pagehide', handlePageHide);
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* 同步发送内存队列(使用 sendBeacon)
|
|
330
|
+
* - 用于页面关闭时确保数据发送
|
|
331
|
+
* - sendBeacon 不可用时降级为缓存到 localStorage
|
|
332
|
+
*/
|
|
333
|
+
flushBatchSync() {
|
|
334
|
+
if (this.memoryQueue.length === 0)
|
|
335
|
+
return;
|
|
336
|
+
// 清除定时器
|
|
337
|
+
if (this.batchTimer) {
|
|
338
|
+
try {
|
|
339
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
340
|
+
cancelIdleCallback(this.batchTimer);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
clearTimeout(this.batchTimer);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// ignore
|
|
348
|
+
}
|
|
349
|
+
this.batchTimer = null;
|
|
350
|
+
}
|
|
351
|
+
this.batchTimerDueAt = null;
|
|
352
|
+
const logs = [...this.memoryQueue];
|
|
353
|
+
this.memoryQueue = [];
|
|
354
|
+
// 优先使用 sendBeacon(页面关闭时可靠发送)
|
|
355
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
356
|
+
try {
|
|
357
|
+
const payload = this.buildSendBeaconPayload(logs);
|
|
358
|
+
const blob = new Blob([payload], { type: 'application/json' });
|
|
359
|
+
const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
|
|
360
|
+
const success = navigator.sendBeacon(url, blob);
|
|
361
|
+
if (!success) {
|
|
362
|
+
// sendBeacon 返回 false 时,降级缓存
|
|
363
|
+
this.cacheFailedReportLogs(logs);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
this.cacheFailedReportLogs(logs);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
// 不支持 sendBeacon,降级缓存到 localStorage
|
|
372
|
+
this.cacheFailedReportLogs(logs);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* 构建 sendBeacon 的 payload
|
|
377
|
+
*/
|
|
378
|
+
buildSendBeaconPayload(logs) {
|
|
379
|
+
const logList = logs.map((log) => this.buildReportFields(log));
|
|
380
|
+
return JSON.stringify({
|
|
381
|
+
source: this.source,
|
|
382
|
+
logs: logList,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
295
385
|
startRequestMonitor(requestMonitor) {
|
|
296
386
|
if (this.requestMonitorStarted)
|
|
297
387
|
return;
|
|
@@ -546,25 +636,55 @@ class ClsLoggerCore {
|
|
|
546
636
|
const desiredDelay = Math.max(0, desiredDueAt - now);
|
|
547
637
|
if (!this.batchTimer) {
|
|
548
638
|
this.batchTimerDueAt = desiredDueAt;
|
|
549
|
-
this.
|
|
550
|
-
void this.flushBatch();
|
|
551
|
-
}, desiredDelay);
|
|
639
|
+
this.scheduleFlush(desiredDelay);
|
|
552
640
|
return;
|
|
553
641
|
}
|
|
554
|
-
// 启动合并窗口内:如果当前 timer
|
|
642
|
+
// 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
|
|
555
643
|
if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
|
|
556
|
-
|
|
557
|
-
clearTimeout(this.batchTimer);
|
|
558
|
-
}
|
|
559
|
-
catch {
|
|
560
|
-
// ignore
|
|
561
|
-
}
|
|
644
|
+
this.cancelScheduledFlush();
|
|
562
645
|
this.batchTimerDueAt = desiredDueAt;
|
|
646
|
+
this.scheduleFlush(desiredDelay);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* 调度批量发送
|
|
651
|
+
* - 支持 requestIdleCallback(浏览器空闲时执行)
|
|
652
|
+
* - 降级为 setTimeout
|
|
653
|
+
*/
|
|
654
|
+
scheduleFlush(desiredDelay) {
|
|
655
|
+
if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
|
|
656
|
+
// 使用 requestIdleCallback,设置 timeout 保证最终执行
|
|
657
|
+
const idleId = requestIdleCallback(() => {
|
|
658
|
+
void this.flushBatch();
|
|
659
|
+
}, { timeout: Math.max(desiredDelay, this.idleTimeout) });
|
|
660
|
+
// 存储 idleId 以便清理(类型兼容处理)
|
|
661
|
+
this.batchTimer = idleId;
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
563
664
|
this.batchTimer = setTimeout(() => {
|
|
564
665
|
void this.flushBatch();
|
|
565
666
|
}, desiredDelay);
|
|
566
667
|
}
|
|
567
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* 取消已调度的批量发送
|
|
671
|
+
*/
|
|
672
|
+
cancelScheduledFlush() {
|
|
673
|
+
if (!this.batchTimer)
|
|
674
|
+
return;
|
|
675
|
+
try {
|
|
676
|
+
if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
|
|
677
|
+
cancelIdleCallback(this.batchTimer);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
clearTimeout(this.batchTimer);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
// ignore
|
|
685
|
+
}
|
|
686
|
+
this.batchTimer = null;
|
|
687
|
+
}
|
|
568
688
|
getDesiredBatchFlushDueAt(nowTs) {
|
|
569
689
|
const start = this.initTs || nowTs;
|
|
570
690
|
const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
|
|
@@ -575,7 +695,7 @@ class ClsLoggerCore {
|
|
|
575
695
|
}
|
|
576
696
|
return nowTs + this.batchIntervalMs;
|
|
577
697
|
}
|
|
578
|
-
info(message, data = {}) {
|
|
698
|
+
info(message, data = {}, options) {
|
|
579
699
|
let msg = '';
|
|
580
700
|
let extra = {};
|
|
581
701
|
if (message instanceof Error) {
|
|
@@ -591,9 +711,18 @@ class ClsLoggerCore {
|
|
|
591
711
|
extra = data;
|
|
592
712
|
}
|
|
593
713
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
|
|
594
|
-
|
|
714
|
+
const log = { type: 'info', data: payload, timestamp: Date.now() };
|
|
715
|
+
// info 默认走批量队列,支持 immediate 选项立即发送
|
|
716
|
+
if (options?.immediate) {
|
|
717
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
718
|
+
this.cacheFailedReportLogs([log]);
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
this.report(log);
|
|
723
|
+
}
|
|
595
724
|
}
|
|
596
|
-
warn(message, data = {}) {
|
|
725
|
+
warn(message, data = {}, options) {
|
|
597
726
|
let msg = '';
|
|
598
727
|
let extra = {};
|
|
599
728
|
if (message instanceof Error) {
|
|
@@ -609,9 +738,18 @@ class ClsLoggerCore {
|
|
|
609
738
|
extra = data;
|
|
610
739
|
}
|
|
611
740
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
|
|
612
|
-
|
|
741
|
+
const log = { type: 'warn', data: payload, timestamp: Date.now() };
|
|
742
|
+
// warn 默认走批量队列,支持 immediate 选项立即发送
|
|
743
|
+
if (options?.immediate) {
|
|
744
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
745
|
+
this.cacheFailedReportLogs([log]);
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
this.report(log);
|
|
750
|
+
}
|
|
613
751
|
}
|
|
614
|
-
error(message, data = {}) {
|
|
752
|
+
error(message, data = {}, options) {
|
|
615
753
|
let msg = '';
|
|
616
754
|
let extra = {};
|
|
617
755
|
if (message instanceof Error) {
|
|
@@ -627,7 +765,17 @@ class ClsLoggerCore {
|
|
|
627
765
|
extra = data;
|
|
628
766
|
}
|
|
629
767
|
const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
|
|
630
|
-
|
|
768
|
+
const log = { type: 'error', data: payload, timestamp: Date.now() };
|
|
769
|
+
// error 默认即时上报,除非显式指定 immediate: false
|
|
770
|
+
const immediate = options?.immediate ?? true;
|
|
771
|
+
if (immediate) {
|
|
772
|
+
void this.sendReportLogs([log]).catch(() => {
|
|
773
|
+
this.cacheFailedReportLogs([log]);
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
this.report(log);
|
|
778
|
+
}
|
|
631
779
|
}
|
|
632
780
|
track(trackType, data = {}) {
|
|
633
781
|
if (!trackType)
|
|
@@ -642,10 +790,7 @@ class ClsLoggerCore {
|
|
|
642
790
|
* 立即发送内存队列
|
|
643
791
|
*/
|
|
644
792
|
async flushBatch() {
|
|
645
|
-
|
|
646
|
-
clearTimeout(this.batchTimer);
|
|
647
|
-
this.batchTimer = null;
|
|
648
|
-
}
|
|
793
|
+
this.cancelScheduledFlush();
|
|
649
794
|
this.batchTimerDueAt = null;
|
|
650
795
|
if (this.memoryQueue.length === 0)
|
|
651
796
|
return;
|