@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.
@@ -1,4 +1,4 @@
1
- import type { ClsLoggerInitOptions, ErrorMonitorOptions, EnvType, FlatFields, PerformanceMonitorOptions, PutOptions, QueueItem, ReportLog, RequestMonitorOptions, BehaviorMonitorOptions, DeviceInfoOptions } from './types';
1
+ import type { ClsLoggerInitOptions, ErrorMonitorOptions, EnvType, FlatFields, PerformanceMonitorOptions, PutOptions, QueueItem, ReportLog, ReportOptions, RequestMonitorOptions, BehaviorMonitorOptions, DeviceInfoOptions } from './types';
2
2
  import type { ClsSdkModule } from './clsSdkTypes';
3
3
  /**
4
4
  * CLS Logger 核心基类
@@ -38,6 +38,9 @@ export declare abstract class ClsLoggerCore {
38
38
  protected batchTimerDueAt: number | null;
39
39
  protected initTs: number;
40
40
  protected startupDelayMs: number;
41
+ protected useIdleCallback: boolean;
42
+ protected idleTimeout: number;
43
+ protected visibilityCleanup: (() => void) | null;
41
44
  protected failedCacheKey: string;
42
45
  protected failedCacheMax: number;
43
46
  protected requestMonitorStarted: boolean;
@@ -77,6 +80,22 @@ export declare abstract class ClsLoggerCore {
77
80
  protected detectEnvType(): EnvType;
78
81
  init(options: ClsLoggerInitOptions): void;
79
82
  private getBaseFields;
83
+ /**
84
+ * 设置页面可见性监听
85
+ * - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
86
+ * - pagehide: 作为移动端 fallback
87
+ */
88
+ private setupVisibilityListener;
89
+ /**
90
+ * 同步发送内存队列(使用 sendBeacon)
91
+ * - 用于页面关闭时确保数据发送
92
+ * - sendBeacon 不可用时降级为缓存到 localStorage
93
+ */
94
+ private flushBatchSync;
95
+ /**
96
+ * 构建 sendBeacon 的 payload
97
+ */
98
+ private buildSendBeaconPayload;
80
99
  private startRequestMonitor;
81
100
  private startErrorMonitor;
82
101
  private startPerformanceMonitor;
@@ -121,10 +140,20 @@ export declare abstract class ClsLoggerCore {
121
140
  * 参考《一、概述》:统一上报入口(内存队列 + 批量发送)
122
141
  */
123
142
  report(log: ReportLog): void;
143
+ /**
144
+ * 调度批量发送
145
+ * - 支持 requestIdleCallback(浏览器空闲时执行)
146
+ * - 降级为 setTimeout
147
+ */
148
+ private scheduleFlush;
149
+ /**
150
+ * 取消已调度的批量发送
151
+ */
152
+ private cancelScheduledFlush;
124
153
  private getDesiredBatchFlushDueAt;
125
- info(message: string | Error, data?: FlatFields): void;
126
- warn(message: string | Error, data?: FlatFields): void;
127
- error(message: string | Error, data?: FlatFields): void;
154
+ info(message: string | Error, data?: FlatFields, options?: ReportOptions): void;
155
+ warn(message: string | Error, data?: FlatFields, options?: ReportOptions): void;
156
+ error(message: string | Error, data?: FlatFields, options?: ReportOptions): void;
128
157
  track(trackType: string, data?: FlatFields): void;
129
158
  /**
130
159
  * 立即发送内存队列
@@ -1 +1 @@
1
- {"version":3,"file":"ClsLoggerCore.d.ts","sourceRoot":"","sources":["../src/ClsLoggerCore.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClsLoggerCore.d.ts","sourceRoot":"","sources":["../src/ClsLoggerCore.ts"],"names":[],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -176,6 +176,9 @@ class ClsLoggerCore {
176
176
  this.batchTimerDueAt = null;
177
177
  this.initTs = 0;
178
178
  this.startupDelayMs = 0;
179
+ this.useIdleCallback = false;
180
+ this.idleTimeout = 3000;
181
+ this.visibilityCleanup = null;
179
182
  // 参考文档:失败缓存 + 重试
180
183
  this.failedCacheKey = 'cls_failed_logs';
181
184
  this.failedCacheMax = 200;
@@ -245,6 +248,8 @@ class ClsLoggerCore {
245
248
  this.batchMaxSize = options.batch?.maxSize ?? this.batchMaxSize;
246
249
  this.batchIntervalMs = options.batch?.intervalMs ?? this.batchIntervalMs;
247
250
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
251
+ this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
252
+ this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
248
253
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
249
254
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
250
255
  // 预热(避免首条日志触发 import/初始化开销)
@@ -254,6 +259,8 @@ class ClsLoggerCore {
254
259
  if (this.enabled) {
255
260
  // 启动时尝试发送失败缓存
256
261
  this.flushFailed();
262
+ // 添加页面可见性监听(确保页面关闭时数据不丢失)
263
+ this.setupVisibilityListener();
257
264
  // 初始化后立即启动请求监听
258
265
  this.startRequestMonitor(options.requestMonitor);
259
266
  // 初始化后立即启动错误监控/性能监控
@@ -290,6 +297,89 @@ class ClsLoggerCore {
290
297
  return auto;
291
298
  return undefined;
292
299
  }
300
+ /**
301
+ * 设置页面可见性监听
302
+ * - visibilitychange: 页面隐藏时使用 sendBeacon 发送队列
303
+ * - pagehide: 作为移动端 fallback
304
+ */
305
+ setupVisibilityListener() {
306
+ if (typeof document === 'undefined' || typeof window === 'undefined')
307
+ return;
308
+ // 避免重复监听
309
+ if (this.visibilityCleanup)
310
+ return;
311
+ const handleVisibilityChange = () => {
312
+ if (document.visibilityState === 'hidden') {
313
+ this.flushBatchSync();
314
+ }
315
+ };
316
+ const handlePageHide = () => {
317
+ this.flushBatchSync();
318
+ };
319
+ document.addEventListener('visibilitychange', handleVisibilityChange);
320
+ window.addEventListener('pagehide', handlePageHide);
321
+ this.visibilityCleanup = () => {
322
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
323
+ window.removeEventListener('pagehide', handlePageHide);
324
+ };
325
+ }
326
+ /**
327
+ * 同步发送内存队列(使用 sendBeacon)
328
+ * - 用于页面关闭时确保数据发送
329
+ * - sendBeacon 不可用时降级为缓存到 localStorage
330
+ */
331
+ flushBatchSync() {
332
+ if (this.memoryQueue.length === 0)
333
+ return;
334
+ // 清除定时器
335
+ if (this.batchTimer) {
336
+ try {
337
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
338
+ cancelIdleCallback(this.batchTimer);
339
+ }
340
+ else {
341
+ clearTimeout(this.batchTimer);
342
+ }
343
+ }
344
+ catch {
345
+ // ignore
346
+ }
347
+ this.batchTimer = null;
348
+ }
349
+ this.batchTimerDueAt = null;
350
+ const logs = [...this.memoryQueue];
351
+ this.memoryQueue = [];
352
+ // 优先使用 sendBeacon(页面关闭时可靠发送)
353
+ if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
354
+ try {
355
+ const payload = this.buildSendBeaconPayload(logs);
356
+ const blob = new Blob([payload], { type: 'application/json' });
357
+ const url = `${this.endpoint}/structuredlog?topic_id=${this.topicId}`;
358
+ const success = navigator.sendBeacon(url, blob);
359
+ if (!success) {
360
+ // sendBeacon 返回 false 时,降级缓存
361
+ this.cacheFailedReportLogs(logs);
362
+ }
363
+ }
364
+ catch {
365
+ this.cacheFailedReportLogs(logs);
366
+ }
367
+ }
368
+ else {
369
+ // 不支持 sendBeacon,降级缓存到 localStorage
370
+ this.cacheFailedReportLogs(logs);
371
+ }
372
+ }
373
+ /**
374
+ * 构建 sendBeacon 的 payload
375
+ */
376
+ buildSendBeaconPayload(logs) {
377
+ const logList = logs.map((log) => this.buildReportFields(log));
378
+ return JSON.stringify({
379
+ source: this.source,
380
+ logs: logList,
381
+ });
382
+ }
293
383
  startRequestMonitor(requestMonitor) {
294
384
  if (this.requestMonitorStarted)
295
385
  return;
@@ -544,25 +634,55 @@ class ClsLoggerCore {
544
634
  const desiredDelay = Math.max(0, desiredDueAt - now);
545
635
  if (!this.batchTimer) {
546
636
  this.batchTimerDueAt = desiredDueAt;
547
- this.batchTimer = setTimeout(() => {
548
- void this.flushBatch();
549
- }, desiredDelay);
637
+ this.scheduleFlush(desiredDelay);
550
638
  return;
551
639
  }
552
- // 启动合并窗口内:如果当前 timer 会“更早”触发,则延后到窗口结束,尽量减少多次发送
640
+ // 启动合并窗口内:如果当前 timer 会"更早"触发,则延后到窗口结束,尽量减少多次发送
553
641
  if (this.batchTimerDueAt !== null && this.batchTimerDueAt < desiredDueAt) {
554
- try {
555
- clearTimeout(this.batchTimer);
556
- }
557
- catch {
558
- // ignore
559
- }
642
+ this.cancelScheduledFlush();
560
643
  this.batchTimerDueAt = desiredDueAt;
644
+ this.scheduleFlush(desiredDelay);
645
+ }
646
+ }
647
+ /**
648
+ * 调度批量发送
649
+ * - 支持 requestIdleCallback(浏览器空闲时执行)
650
+ * - 降级为 setTimeout
651
+ */
652
+ scheduleFlush(desiredDelay) {
653
+ if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
654
+ // 使用 requestIdleCallback,设置 timeout 保证最终执行
655
+ const idleId = requestIdleCallback(() => {
656
+ void this.flushBatch();
657
+ }, { timeout: Math.max(desiredDelay, this.idleTimeout) });
658
+ // 存储 idleId 以便清理(类型兼容处理)
659
+ this.batchTimer = idleId;
660
+ }
661
+ else {
561
662
  this.batchTimer = setTimeout(() => {
562
663
  void this.flushBatch();
563
664
  }, desiredDelay);
564
665
  }
565
666
  }
667
+ /**
668
+ * 取消已调度的批量发送
669
+ */
670
+ cancelScheduledFlush() {
671
+ if (!this.batchTimer)
672
+ return;
673
+ try {
674
+ if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
675
+ cancelIdleCallback(this.batchTimer);
676
+ }
677
+ else {
678
+ clearTimeout(this.batchTimer);
679
+ }
680
+ }
681
+ catch {
682
+ // ignore
683
+ }
684
+ this.batchTimer = null;
685
+ }
566
686
  getDesiredBatchFlushDueAt(nowTs) {
567
687
  const start = this.initTs || nowTs;
568
688
  const startupDelay = Number.isFinite(this.startupDelayMs) ? Math.max(0, this.startupDelayMs) : 0;
@@ -573,7 +693,7 @@ class ClsLoggerCore {
573
693
  }
574
694
  return nowTs + this.batchIntervalMs;
575
695
  }
576
- info(message, data = {}) {
696
+ info(message, data = {}, options) {
577
697
  let msg = '';
578
698
  let extra = {};
579
699
  if (message instanceof Error) {
@@ -589,9 +709,18 @@ class ClsLoggerCore {
589
709
  extra = data;
590
710
  }
591
711
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'info');
592
- this.report({ type: 'info', data: payload, timestamp: Date.now() });
712
+ const log = { type: 'info', data: payload, timestamp: Date.now() };
713
+ // info 默认走批量队列,支持 immediate 选项立即发送
714
+ if (options?.immediate) {
715
+ void this.sendReportLogs([log]).catch(() => {
716
+ this.cacheFailedReportLogs([log]);
717
+ });
718
+ }
719
+ else {
720
+ this.report(log);
721
+ }
593
722
  }
594
- warn(message, data = {}) {
723
+ warn(message, data = {}, options) {
595
724
  let msg = '';
596
725
  let extra = {};
597
726
  if (message instanceof Error) {
@@ -607,9 +736,18 @@ class ClsLoggerCore {
607
736
  extra = data;
608
737
  }
609
738
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'warn');
610
- this.report({ type: 'warn', data: payload, timestamp: Date.now() });
739
+ const log = { type: 'warn', data: payload, timestamp: Date.now() };
740
+ // warn 默认走批量队列,支持 immediate 选项立即发送
741
+ if (options?.immediate) {
742
+ void this.sendReportLogs([log]).catch(() => {
743
+ this.cacheFailedReportLogs([log]);
744
+ });
745
+ }
746
+ else {
747
+ this.report(log);
748
+ }
611
749
  }
612
- error(message, data = {}) {
750
+ error(message, data = {}, options) {
613
751
  let msg = '';
614
752
  let extra = {};
615
753
  if (message instanceof Error) {
@@ -625,7 +763,17 @@ class ClsLoggerCore {
625
763
  extra = data;
626
764
  }
627
765
  const payload = normalizeFlatFields({ message: msg, ...extra }, 'error');
628
- this.report({ type: 'error', data: payload, timestamp: Date.now() });
766
+ const log = { type: 'error', data: payload, timestamp: Date.now() };
767
+ // error 默认即时上报,除非显式指定 immediate: false
768
+ const immediate = options?.immediate ?? true;
769
+ if (immediate) {
770
+ void this.sendReportLogs([log]).catch(() => {
771
+ this.cacheFailedReportLogs([log]);
772
+ });
773
+ }
774
+ else {
775
+ this.report(log);
776
+ }
629
777
  }
630
778
  track(trackType, data = {}) {
631
779
  if (!trackType)
@@ -640,10 +788,7 @@ class ClsLoggerCore {
640
788
  * 立即发送内存队列
641
789
  */
642
790
  async flushBatch() {
643
- if (this.batchTimer) {
644
- clearTimeout(this.batchTimer);
645
- this.batchTimer = null;
646
- }
791
+ this.cancelScheduledFlush();
647
792
  this.batchTimerDueAt = null;
648
793
  if (this.memoryQueue.length === 0)
649
794
  return;
package/dist/index.js CHANGED
@@ -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;
@@ -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.batchTimer = setTimeout(() => {
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
- try {
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
- this.report({ type: 'info', data: payload, timestamp: Date.now() });
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
- this.report({ type: 'warn', data: payload, timestamp: Date.now() });
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
- this.report({ type: 'error', data: payload, timestamp: Date.now() });
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
- if (this.batchTimer) {
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;