@be-link/cls-logger 1.0.1-beta.21 → 1.0.1-beta.23

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 CHANGED
@@ -144,6 +144,7 @@ clsLogger.init({
144
144
  maxSize: 50, // 队列达到此数量立即发送,默认 20
145
145
  intervalMs: 1000, // 定时批量发送间隔,默认 500ms
146
146
  startupDelayMs: 3000, // 启动合并窗口,默认 0(不启用)
147
+ startupMaxSize: 500, // 启动窗口内的队列阈值,默认 maxSize*10(至少200)
147
148
  useIdleCallback: true, // 使用浏览器空闲时间上报,默认 false
148
149
  idleTimeout: 3000, // 空闲回调超时时间,默认 3000ms
149
150
  },
@@ -224,13 +225,19 @@ clsLogger.init({
224
225
  clsLogger.init({
225
226
  batch: {
226
227
  startupDelayMs: 3000, // 启动 3 秒内的日志合并发送
227
- maxSize: 100, // 提高队列阈值
228
- intervalMs: 1000, // 延长批量间隔
229
- useIdleCallback: true, // 使用浏览器空闲时间上报
228
+ startupMaxSize: 500, // 启动窗口内允许更多日志积累(防止 maxSize 打断合并)
229
+ maxSize: 100, // 正常运行时的队列阈值
230
+ intervalMs: 2000, // 延长批量间隔
231
+ useIdleCallback: true, // 等待延迟后在浏览器空闲时上报
232
+ },
233
+ performanceMonitor: {
234
+ resourceTiming: false, // 关闭资源监控(首屏资源多时可大幅减少日志量)
230
235
  },
231
236
  });
232
237
  ```
233
238
 
239
+ > **说明**:`startupMaxSize` 确保启动窗口内不会因为 `maxSize` 被频繁触发而打断日志合并。
240
+
234
241
  ### 场景 2:减少资源监控日志
235
242
 
236
243
  开发环境资源过多,或生产环境不需要全量资源监控:
@@ -274,31 +281,174 @@ clsLogger.init({
274
281
  ```ts
275
282
  clsLogger.init({
276
283
  batch: {
277
- maxSize: 100, // 提高队列阈值
284
+ startupDelayMs: 3000, // 启动合并窗口
285
+ startupMaxSize: 500, // 启动窗口内的队列阈值
286
+ maxSize: 100, // 正常运行时的队列阈值
278
287
  intervalMs: 2000, // 延长批量间隔
279
- useIdleCallback: true, // 空闲时上报
288
+ useIdleCallback: true, // 空闲时上报,不阻塞主线程
280
289
  },
281
290
  performanceMonitor: {
282
291
  resourceTiming: false, // 关闭资源监控
283
292
  },
284
293
  requestMonitor: {
285
294
  sampleRate: 0.1, // 请求监控采样 10%
295
+ ignoreUrls: [
296
+ /heartbeat/i, // 忽略心跳请求
297
+ /polling/i, // 忽略轮询请求
298
+ /danmaku|barrage/i, // 忽略弹幕请求
299
+ ],
286
300
  },
287
301
  });
288
302
  ```
289
303
 
304
+ ## 最佳实践
305
+
306
+ ### 1. 批量配置的正确姿势
307
+
308
+ #### `useIdleCallback` 工作原理
309
+
310
+ 当 `useIdleCallback: true` 时,SDK 的上报流程为:
311
+
312
+ ```
313
+ 日志产生 → 等待 intervalMs/startupDelayMs → 等待浏览器空闲 → 发送
314
+ ↑ 最小等待时间 ↑ 在 idleTimeout 内空闲就发送
315
+ ```
316
+
317
+ **关键点**:
318
+
319
+ - `intervalMs`/`startupDelayMs` 是**最小等待时间**,保证不会过早发送
320
+ - `idleTimeout` 是空闲等待的**最大时间**,防止浏览器一直繁忙导致日志积压
321
+
322
+ #### `startupMaxSize` 的作用
323
+
324
+ 启动阶段(`startupDelayMs` 窗口内)日志量通常很大(性能指标、资源加载等),如果 `maxSize` 设置过小,会频繁触发队列满而打断合并:
325
+
326
+ ```ts
327
+ // ❌ 错误配置:startupDelayMs 会被 maxSize 打断
328
+ batch: {
329
+ startupDelayMs: 3000,
330
+ maxSize: 20, // 启动期间可能产生 200+ 条日志,会触发 10+ 次发送
331
+ }
332
+
333
+ // ✅ 正确配置:使用 startupMaxSize 保护启动窗口
334
+ batch: {
335
+ startupDelayMs: 3000,
336
+ maxSize: 20,
337
+ startupMaxSize: 500, // 启动期间允许更多日志积累
338
+ }
339
+ ```
340
+
341
+ ### 2. 推荐配置模板
342
+
343
+ #### 普通 H5 页面
344
+
345
+ ```ts
346
+ clsLogger.init({
347
+ batch: {
348
+ startupDelayMs: 2000,
349
+ startupMaxSize: 300,
350
+ maxSize: 50,
351
+ intervalMs: 1000,
352
+ useIdleCallback: true,
353
+ idleTimeout: 3000,
354
+ },
355
+ });
356
+ ```
357
+
358
+ #### 首屏性能敏感(电商、活动页)
359
+
360
+ ```ts
361
+ clsLogger.init({
362
+ batch: {
363
+ startupDelayMs: 5000, // 更长的启动窗口
364
+ startupMaxSize: 500,
365
+ maxSize: 100,
366
+ intervalMs: 2000,
367
+ useIdleCallback: true,
368
+ idleTimeout: 5000,
369
+ },
370
+ performanceMonitor: {
371
+ sampleRate: 0.3, // 性能监控采样
372
+ },
373
+ });
374
+ ```
375
+
376
+ #### 高频交互页面(直播间、游戏)
377
+
378
+ ```ts
379
+ clsLogger.init({
380
+ batch: {
381
+ startupDelayMs: 3000,
382
+ startupMaxSize: 500,
383
+ maxSize: 100,
384
+ intervalMs: 3000, // 更长的间隔
385
+ useIdleCallback: true,
386
+ idleTimeout: 5000,
387
+ },
388
+ performanceMonitor: {
389
+ resourceTiming: false, // 关闭资源监控
390
+ },
391
+ requestMonitor: {
392
+ sampleRate: 0.1, // 请求监控采样
393
+ ignoreUrls: [/heartbeat/, /polling/, /danmaku/],
394
+ },
395
+ });
396
+ ```
397
+
398
+ ### 3. 日志量优化技巧
399
+
400
+ | 优化项 | 配置 | 效果 |
401
+ | ------------ | ------------------------------------------ | ------------------------- |
402
+ | 关闭资源监控 | `performanceMonitor.resourceTiming: false` | 减少 80%+ 的 perf 日志 |
403
+ | 性能采样 | `performanceMonitor.sampleRate: 0.1` | 只采集 10% 用户的性能数据 |
404
+ | 请求采样 | `requestMonitor.sampleRate: 0.1` | 只采集 10% 的请求日志 |
405
+ | 忽略高频请求 | `requestMonitor.ignoreUrls: [/heartbeat/]` | 过滤心跳等高频请求 |
406
+ | 错误去重 | `errorMonitor.dedupeWindowMs: 5000` | 5秒内相同错误只上报一次 |
407
+
408
+ ### 4. 常见问题排查
409
+
410
+ #### 问题:首屏请求过多
411
+
412
+ **现象**:页面加载时发出 10+ 个日志请求
413
+
414
+ **排查步骤**:
415
+
416
+ 1. 检查 `startupDelayMs` 是否配置
417
+ 2. 检查 `startupMaxSize` 是否足够大(建议 300-500)
418
+ 3. 检查 `resourceTiming` 是否需要开启(这是日志量最大的来源)
419
+
420
+ #### 问题:日志延迟过高
421
+
422
+ **现象**:日志上报明显滞后
423
+
424
+ **排查步骤**:
425
+
426
+ 1. 检查 `useIdleCallback` + `idleTimeout` 配置
427
+ 2. 检查 `intervalMs` 是否设置过大
428
+ 3. 考虑关闭 `useIdleCallback` 或减小 `idleTimeout`
429
+
430
+ #### 问题:页面关闭丢日志
431
+
432
+ **现象**:用户快速关闭页面时日志丢失
433
+
434
+ **说明**:SDK 已通过 `sendBeacon` 处理此场景,如仍有丢失:
435
+
436
+ 1. 检查日志是否在 `memoryQueue` 中(可能还未触发 flush)
437
+ 2. 考虑对关键日志使用 `{ immediate: true }`
438
+
290
439
  ## 功能说明
291
440
 
292
441
  ### 1. 批量上报机制
293
442
 
294
443
  SDK 使用内存队列批量上报日志:
295
444
 
296
- | 触发条件 | 说明 |
297
- | -------- | ------------------------------------------------------ |
298
- | 队列满 | 达到 `maxSize` 立即发送 |
299
- | 定时器 | 每 `intervalMs` 毫秒发送一次 |
300
- | 页面关闭 | `visibilitychange` + `pagehide` 触发 `sendBeacon` 发送 |
301
- | 启动合并 | `startupDelayMs` 窗口内的日志尽量合并 |
445
+ | 触发条件 | 说明 |
446
+ | -------- | ------------------------------------------------------- |
447
+ | 队列满 | 达到 `maxSize`(启动窗口内为 `startupMaxSize`)立即发送 |
448
+ | 定时器 | 每 `intervalMs` 毫秒发送一次 |
449
+ | 页面关闭 | `visibilitychange` + `pagehide` 触发 `sendBeacon` 发送 |
450
+ | 启动合并 | `startupDelayMs` 窗口内的日志尽量合并 |
451
+ | 空闲上报 | `useIdleCallback` 开启时,等待浏览器空闲后发送 |
302
452
 
303
453
  ### 2. 即时上报 vs 批量上报
304
454
 
@@ -38,8 +38,10 @@ export declare abstract class ClsLoggerCore {
38
38
  protected batchTimerDueAt: number | null;
39
39
  protected initTs: number;
40
40
  protected startupDelayMs: number;
41
+ protected startupMaxSize: number;
41
42
  protected useIdleCallback: boolean;
42
43
  protected idleTimeout: number;
44
+ protected pendingIdleCallback: number | null;
43
45
  protected visibilityCleanup: (() => void) | null;
44
46
  protected failedCacheKey: string;
45
47
  protected failedCacheMax: number;
@@ -142,12 +144,13 @@ export declare abstract class ClsLoggerCore {
142
144
  report(log: ReportLog): void;
143
145
  /**
144
146
  * 调度批量发送
145
- * - 支持 requestIdleCallback(浏览器空闲时执行)
146
- * - 降级为 setTimeout
147
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
148
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
147
149
  */
148
150
  private scheduleFlush;
149
151
  /**
150
152
  * 取消已调度的批量发送
153
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
151
154
  */
152
155
  private cancelScheduledFlush;
153
156
  private getDesiredBatchFlushDueAt;
@@ -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
@@ -178,8 +178,10 @@ class ClsLoggerCore {
178
178
  this.batchTimerDueAt = null;
179
179
  this.initTs = 0;
180
180
  this.startupDelayMs = 0;
181
+ this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
181
182
  this.useIdleCallback = false;
182
183
  this.idleTimeout = 3000;
184
+ this.pendingIdleCallback = null; // requestIdleCallback 的 id
183
185
  this.visibilityCleanup = null;
184
186
  // 参考文档:失败缓存 + 重试
185
187
  this.failedCacheKey = 'cls_failed_logs';
@@ -252,6 +254,8 @@ class ClsLoggerCore {
252
254
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
253
255
  this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
254
256
  this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
257
+ // startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
258
+ this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
255
259
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
256
260
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
257
261
  // 预热(避免首条日志触发 import/初始化开销)
@@ -627,11 +631,15 @@ class ClsLoggerCore {
627
631
  return;
628
632
  }
629
633
  this.memoryQueue.push(log);
630
- if (this.memoryQueue.length >= this.batchMaxSize) {
634
+ const now = Date.now();
635
+ // 判断是否在启动合并窗口内
636
+ const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
637
+ // 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
638
+ const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
639
+ if (this.memoryQueue.length >= effectiveMaxSize) {
631
640
  void this.flushBatch();
632
641
  return;
633
642
  }
634
- const now = Date.now();
635
643
  const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
636
644
  const desiredDelay = Math.max(0, desiredDueAt - now);
637
645
  if (!this.batchTimer) {
@@ -648,17 +656,19 @@ class ClsLoggerCore {
648
656
  }
649
657
  /**
650
658
  * 调度批量发送
651
- * - 支持 requestIdleCallback(浏览器空闲时执行)
652
- * - 降级为 setTimeout
659
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
660
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
653
661
  */
654
662
  scheduleFlush(desiredDelay) {
655
663
  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;
664
+ // setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
665
+ this.batchTimer = setTimeout(() => {
666
+ const idleId = requestIdleCallback(() => {
667
+ this.pendingIdleCallback = null;
668
+ void this.flushBatch();
669
+ }, { timeout: this.idleTimeout });
670
+ this.pendingIdleCallback = idleId;
671
+ }, desiredDelay);
662
672
  }
663
673
  else {
664
674
  this.batchTimer = setTimeout(() => {
@@ -668,22 +678,29 @@ class ClsLoggerCore {
668
678
  }
669
679
  /**
670
680
  * 取消已调度的批量发送
681
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
671
682
  */
672
683
  cancelScheduledFlush() {
673
- if (!this.batchTimer)
674
- return;
675
- try {
676
- if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
677
- cancelIdleCallback(this.batchTimer);
678
- }
679
- else {
684
+ // 清理 setTimeout
685
+ if (this.batchTimer) {
686
+ try {
680
687
  clearTimeout(this.batchTimer);
681
688
  }
689
+ catch {
690
+ // ignore
691
+ }
692
+ this.batchTimer = null;
682
693
  }
683
- catch {
684
- // ignore
694
+ // 清理可能的 pendingIdleCallback
695
+ if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
696
+ try {
697
+ cancelIdleCallback(this.pendingIdleCallback);
698
+ }
699
+ catch {
700
+ // ignore
701
+ }
702
+ this.pendingIdleCallback = null;
685
703
  }
686
- this.batchTimer = null;
687
704
  }
688
705
  getDesiredBatchFlushDueAt(nowTs) {
689
706
  const start = this.initTs || nowTs;
@@ -896,7 +913,14 @@ class ClsLoggerCore {
896
913
  // 先清空,再尝试发送
897
914
  writeStringStorage(this.failedCacheKey, JSON.stringify([]));
898
915
  this.memoryQueue.unshift(...logs);
899
- void this.flushBatch();
916
+ // 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
917
+ if (!this.batchTimer) {
918
+ const now = Date.now();
919
+ const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
920
+ const desiredDelay = Math.max(0, desiredDueAt - now);
921
+ this.batchTimerDueAt = desiredDueAt;
922
+ this.scheduleFlush(desiredDelay);
923
+ }
900
924
  }
901
925
  /**
902
926
  * 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
package/dist/index.js CHANGED
@@ -182,8 +182,10 @@ class ClsLoggerCore {
182
182
  this.batchTimerDueAt = null;
183
183
  this.initTs = 0;
184
184
  this.startupDelayMs = 0;
185
+ this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
185
186
  this.useIdleCallback = false;
186
187
  this.idleTimeout = 3000;
188
+ this.pendingIdleCallback = null; // requestIdleCallback 的 id
187
189
  this.visibilityCleanup = null;
188
190
  // 参考文档:失败缓存 + 重试
189
191
  this.failedCacheKey = 'cls_failed_logs';
@@ -256,6 +258,8 @@ class ClsLoggerCore {
256
258
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
257
259
  this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
258
260
  this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
261
+ // startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
262
+ this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
259
263
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
260
264
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
261
265
  // 预热(避免首条日志触发 import/初始化开销)
@@ -631,11 +635,15 @@ class ClsLoggerCore {
631
635
  return;
632
636
  }
633
637
  this.memoryQueue.push(log);
634
- if (this.memoryQueue.length >= this.batchMaxSize) {
638
+ const now = Date.now();
639
+ // 判断是否在启动合并窗口内
640
+ const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
641
+ // 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
642
+ const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
643
+ if (this.memoryQueue.length >= effectiveMaxSize) {
635
644
  void this.flushBatch();
636
645
  return;
637
646
  }
638
- const now = Date.now();
639
647
  const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
640
648
  const desiredDelay = Math.max(0, desiredDueAt - now);
641
649
  if (!this.batchTimer) {
@@ -652,17 +660,19 @@ class ClsLoggerCore {
652
660
  }
653
661
  /**
654
662
  * 调度批量发送
655
- * - 支持 requestIdleCallback(浏览器空闲时执行)
656
- * - 降级为 setTimeout
663
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
664
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
657
665
  */
658
666
  scheduleFlush(desiredDelay) {
659
667
  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;
668
+ // setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
669
+ this.batchTimer = setTimeout(() => {
670
+ const idleId = requestIdleCallback(() => {
671
+ this.pendingIdleCallback = null;
672
+ void this.flushBatch();
673
+ }, { timeout: this.idleTimeout });
674
+ this.pendingIdleCallback = idleId;
675
+ }, desiredDelay);
666
676
  }
667
677
  else {
668
678
  this.batchTimer = setTimeout(() => {
@@ -672,22 +682,29 @@ class ClsLoggerCore {
672
682
  }
673
683
  /**
674
684
  * 取消已调度的批量发送
685
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
675
686
  */
676
687
  cancelScheduledFlush() {
677
- if (!this.batchTimer)
678
- return;
679
- try {
680
- if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
681
- cancelIdleCallback(this.batchTimer);
682
- }
683
- else {
688
+ // 清理 setTimeout
689
+ if (this.batchTimer) {
690
+ try {
684
691
  clearTimeout(this.batchTimer);
685
692
  }
693
+ catch {
694
+ // ignore
695
+ }
696
+ this.batchTimer = null;
686
697
  }
687
- catch {
688
- // ignore
698
+ // 清理可能的 pendingIdleCallback
699
+ if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
700
+ try {
701
+ cancelIdleCallback(this.pendingIdleCallback);
702
+ }
703
+ catch {
704
+ // ignore
705
+ }
706
+ this.pendingIdleCallback = null;
689
707
  }
690
- this.batchTimer = null;
691
708
  }
692
709
  getDesiredBatchFlushDueAt(nowTs) {
693
710
  const start = this.initTs || nowTs;
@@ -900,7 +917,14 @@ class ClsLoggerCore {
900
917
  // 先清空,再尝试发送
901
918
  writeStringStorage(this.failedCacheKey, JSON.stringify([]));
902
919
  this.memoryQueue.unshift(...logs);
903
- void this.flushBatch();
920
+ // 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
921
+ if (!this.batchTimer) {
922
+ const now = Date.now();
923
+ const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
924
+ const desiredDelay = Math.max(0, desiredDueAt - now);
925
+ this.batchTimerDueAt = desiredDueAt;
926
+ this.scheduleFlush(desiredDelay);
927
+ }
904
928
  }
905
929
  /**
906
930
  * 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
package/dist/index.umd.js CHANGED
@@ -182,8 +182,10 @@
182
182
  this.batchTimerDueAt = null;
183
183
  this.initTs = 0;
184
184
  this.startupDelayMs = 0;
185
+ this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
185
186
  this.useIdleCallback = false;
186
187
  this.idleTimeout = 3000;
188
+ this.pendingIdleCallback = null; // requestIdleCallback 的 id
187
189
  this.visibilityCleanup = null;
188
190
  // 参考文档:失败缓存 + 重试
189
191
  this.failedCacheKey = 'cls_failed_logs';
@@ -256,6 +258,8 @@
256
258
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
257
259
  this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
258
260
  this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
261
+ // startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
262
+ this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
259
263
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
260
264
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
261
265
  // 预热(避免首条日志触发 import/初始化开销)
@@ -631,11 +635,15 @@
631
635
  return;
632
636
  }
633
637
  this.memoryQueue.push(log);
634
- if (this.memoryQueue.length >= this.batchMaxSize) {
638
+ const now = Date.now();
639
+ // 判断是否在启动合并窗口内
640
+ const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
641
+ // 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
642
+ const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
643
+ if (this.memoryQueue.length >= effectiveMaxSize) {
635
644
  void this.flushBatch();
636
645
  return;
637
646
  }
638
- const now = Date.now();
639
647
  const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
640
648
  const desiredDelay = Math.max(0, desiredDueAt - now);
641
649
  if (!this.batchTimer) {
@@ -652,17 +660,19 @@
652
660
  }
653
661
  /**
654
662
  * 调度批量发送
655
- * - 支持 requestIdleCallback(浏览器空闲时执行)
656
- * - 降级为 setTimeout
663
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
664
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
657
665
  */
658
666
  scheduleFlush(desiredDelay) {
659
667
  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;
668
+ // setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
669
+ this.batchTimer = setTimeout(() => {
670
+ const idleId = requestIdleCallback(() => {
671
+ this.pendingIdleCallback = null;
672
+ void this.flushBatch();
673
+ }, { timeout: this.idleTimeout });
674
+ this.pendingIdleCallback = idleId;
675
+ }, desiredDelay);
666
676
  }
667
677
  else {
668
678
  this.batchTimer = setTimeout(() => {
@@ -672,22 +682,29 @@
672
682
  }
673
683
  /**
674
684
  * 取消已调度的批量发送
685
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
675
686
  */
676
687
  cancelScheduledFlush() {
677
- if (!this.batchTimer)
678
- return;
679
- try {
680
- if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
681
- cancelIdleCallback(this.batchTimer);
682
- }
683
- else {
688
+ // 清理 setTimeout
689
+ if (this.batchTimer) {
690
+ try {
684
691
  clearTimeout(this.batchTimer);
685
692
  }
693
+ catch {
694
+ // ignore
695
+ }
696
+ this.batchTimer = null;
686
697
  }
687
- catch {
688
- // ignore
698
+ // 清理可能的 pendingIdleCallback
699
+ if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
700
+ try {
701
+ cancelIdleCallback(this.pendingIdleCallback);
702
+ }
703
+ catch {
704
+ // ignore
705
+ }
706
+ this.pendingIdleCallback = null;
689
707
  }
690
- this.batchTimer = null;
691
708
  }
692
709
  getDesiredBatchFlushDueAt(nowTs) {
693
710
  const start = this.initTs || nowTs;
@@ -900,7 +917,14 @@
900
917
  // 先清空,再尝试发送
901
918
  writeStringStorage(this.failedCacheKey, JSON.stringify([]));
902
919
  this.memoryQueue.unshift(...logs);
903
- void this.flushBatch();
920
+ // 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
921
+ if (!this.batchTimer) {
922
+ const now = Date.now();
923
+ const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
924
+ const desiredDelay = Math.max(0, desiredDueAt - now);
925
+ this.batchTimerDueAt = desiredDueAt;
926
+ this.scheduleFlush(desiredDelay);
927
+ }
904
928
  }
905
929
  /**
906
930
  * 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
package/dist/mini.esm.js CHANGED
@@ -178,8 +178,10 @@ class ClsLoggerCore {
178
178
  this.batchTimerDueAt = null;
179
179
  this.initTs = 0;
180
180
  this.startupDelayMs = 0;
181
+ this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
181
182
  this.useIdleCallback = false;
182
183
  this.idleTimeout = 3000;
184
+ this.pendingIdleCallback = null; // requestIdleCallback 的 id
183
185
  this.visibilityCleanup = null;
184
186
  // 参考文档:失败缓存 + 重试
185
187
  this.failedCacheKey = 'cls_failed_logs';
@@ -252,6 +254,8 @@ class ClsLoggerCore {
252
254
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
253
255
  this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
254
256
  this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
257
+ // startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
258
+ this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
255
259
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
256
260
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
257
261
  // 预热(避免首条日志触发 import/初始化开销)
@@ -627,11 +631,15 @@ class ClsLoggerCore {
627
631
  return;
628
632
  }
629
633
  this.memoryQueue.push(log);
630
- if (this.memoryQueue.length >= this.batchMaxSize) {
634
+ const now = Date.now();
635
+ // 判断是否在启动合并窗口内
636
+ const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
637
+ // 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
638
+ const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
639
+ if (this.memoryQueue.length >= effectiveMaxSize) {
631
640
  void this.flushBatch();
632
641
  return;
633
642
  }
634
- const now = Date.now();
635
643
  const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
636
644
  const desiredDelay = Math.max(0, desiredDueAt - now);
637
645
  if (!this.batchTimer) {
@@ -648,17 +656,19 @@ class ClsLoggerCore {
648
656
  }
649
657
  /**
650
658
  * 调度批量发送
651
- * - 支持 requestIdleCallback(浏览器空闲时执行)
652
- * - 降级为 setTimeout
659
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
660
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
653
661
  */
654
662
  scheduleFlush(desiredDelay) {
655
663
  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;
664
+ // setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
665
+ this.batchTimer = setTimeout(() => {
666
+ const idleId = requestIdleCallback(() => {
667
+ this.pendingIdleCallback = null;
668
+ void this.flushBatch();
669
+ }, { timeout: this.idleTimeout });
670
+ this.pendingIdleCallback = idleId;
671
+ }, desiredDelay);
662
672
  }
663
673
  else {
664
674
  this.batchTimer = setTimeout(() => {
@@ -668,22 +678,29 @@ class ClsLoggerCore {
668
678
  }
669
679
  /**
670
680
  * 取消已调度的批量发送
681
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
671
682
  */
672
683
  cancelScheduledFlush() {
673
- if (!this.batchTimer)
674
- return;
675
- try {
676
- if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
677
- cancelIdleCallback(this.batchTimer);
678
- }
679
- else {
684
+ // 清理 setTimeout
685
+ if (this.batchTimer) {
686
+ try {
680
687
  clearTimeout(this.batchTimer);
681
688
  }
689
+ catch {
690
+ // ignore
691
+ }
692
+ this.batchTimer = null;
682
693
  }
683
- catch {
684
- // ignore
694
+ // 清理可能的 pendingIdleCallback
695
+ if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
696
+ try {
697
+ cancelIdleCallback(this.pendingIdleCallback);
698
+ }
699
+ catch {
700
+ // ignore
701
+ }
702
+ this.pendingIdleCallback = null;
685
703
  }
686
- this.batchTimer = null;
687
704
  }
688
705
  getDesiredBatchFlushDueAt(nowTs) {
689
706
  const start = this.initTs || nowTs;
@@ -896,7 +913,14 @@ class ClsLoggerCore {
896
913
  // 先清空,再尝试发送
897
914
  writeStringStorage(this.failedCacheKey, JSON.stringify([]));
898
915
  this.memoryQueue.unshift(...logs);
899
- void this.flushBatch();
916
+ // 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
917
+ if (!this.batchTimer) {
918
+ const now = Date.now();
919
+ const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
920
+ const desiredDelay = Math.max(0, desiredDueAt - now);
921
+ this.batchTimerDueAt = desiredDueAt;
922
+ this.scheduleFlush(desiredDelay);
923
+ }
900
924
  }
901
925
  /**
902
926
  * 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
package/dist/mini.js CHANGED
@@ -201,8 +201,10 @@ class ClsLoggerCore {
201
201
  this.batchTimerDueAt = null;
202
202
  this.initTs = 0;
203
203
  this.startupDelayMs = 0;
204
+ this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
204
205
  this.useIdleCallback = false;
205
206
  this.idleTimeout = 3000;
207
+ this.pendingIdleCallback = null; // requestIdleCallback 的 id
206
208
  this.visibilityCleanup = null;
207
209
  // 参考文档:失败缓存 + 重试
208
210
  this.failedCacheKey = 'cls_failed_logs';
@@ -275,6 +277,8 @@ class ClsLoggerCore {
275
277
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
276
278
  this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
277
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);
278
282
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
279
283
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
280
284
  // 预热(避免首条日志触发 import/初始化开销)
@@ -650,11 +654,15 @@ class ClsLoggerCore {
650
654
  return;
651
655
  }
652
656
  this.memoryQueue.push(log);
653
- if (this.memoryQueue.length >= this.batchMaxSize) {
657
+ const now = Date.now();
658
+ // 判断是否在启动合并窗口内
659
+ const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
660
+ // 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
661
+ const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
662
+ if (this.memoryQueue.length >= effectiveMaxSize) {
654
663
  void this.flushBatch();
655
664
  return;
656
665
  }
657
- const now = Date.now();
658
666
  const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
659
667
  const desiredDelay = Math.max(0, desiredDueAt - now);
660
668
  if (!this.batchTimer) {
@@ -671,17 +679,19 @@ class ClsLoggerCore {
671
679
  }
672
680
  /**
673
681
  * 调度批量发送
674
- * - 支持 requestIdleCallback(浏览器空闲时执行)
675
- * - 降级为 setTimeout
682
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
683
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
676
684
  */
677
685
  scheduleFlush(desiredDelay) {
678
686
  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;
687
+ // setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
688
+ this.batchTimer = setTimeout(() => {
689
+ const idleId = requestIdleCallback(() => {
690
+ this.pendingIdleCallback = null;
691
+ void this.flushBatch();
692
+ }, { timeout: this.idleTimeout });
693
+ this.pendingIdleCallback = idleId;
694
+ }, desiredDelay);
685
695
  }
686
696
  else {
687
697
  this.batchTimer = setTimeout(() => {
@@ -691,22 +701,29 @@ class ClsLoggerCore {
691
701
  }
692
702
  /**
693
703
  * 取消已调度的批量发送
704
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
694
705
  */
695
706
  cancelScheduledFlush() {
696
- if (!this.batchTimer)
697
- return;
698
- try {
699
- if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
700
- cancelIdleCallback(this.batchTimer);
701
- }
702
- else {
707
+ // 清理 setTimeout
708
+ if (this.batchTimer) {
709
+ try {
703
710
  clearTimeout(this.batchTimer);
704
711
  }
712
+ catch {
713
+ // ignore
714
+ }
715
+ this.batchTimer = null;
705
716
  }
706
- catch {
707
- // ignore
717
+ // 清理可能的 pendingIdleCallback
718
+ if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
719
+ try {
720
+ cancelIdleCallback(this.pendingIdleCallback);
721
+ }
722
+ catch {
723
+ // ignore
724
+ }
725
+ this.pendingIdleCallback = null;
708
726
  }
709
- this.batchTimer = null;
710
727
  }
711
728
  getDesiredBatchFlushDueAt(nowTs) {
712
729
  const start = this.initTs || nowTs;
@@ -919,7 +936,14 @@ class ClsLoggerCore {
919
936
  // 先清空,再尝试发送
920
937
  writeStringStorage(this.failedCacheKey, JSON.stringify([]));
921
938
  this.memoryQueue.unshift(...logs);
922
- void this.flushBatch();
939
+ // 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
940
+ if (!this.batchTimer) {
941
+ const now = Date.now();
942
+ const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
943
+ const desiredDelay = Math.max(0, desiredDueAt - now);
944
+ this.batchTimerDueAt = desiredDueAt;
945
+ this.scheduleFlush(desiredDelay);
946
+ }
923
947
  }
924
948
  /**
925
949
  * 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
package/dist/types.d.ts CHANGED
@@ -29,13 +29,20 @@ export interface BatchOptions {
29
29
  /**
30
30
  * 启动阶段合并窗口(ms)
31
31
  * - 目的:减少初始化/首屏阶段(perf/resource 等日志密集期)被 intervalMs 拆成多次上报
32
- * - 行为:在该窗口内尽量延迟 flush 到窗口结束(但仍受 maxSize 约束,达到阈值会立即发送)
32
+ * - 行为:在该窗口内尽量延迟 flush 到窗口结束
33
33
  * - 默认:0(不开启)
34
34
  */
35
35
  startupDelayMs?: number;
36
+ /**
37
+ * 启动阶段的 maxSize(启动窗口内生效)
38
+ * - 目的:防止启动期间日志密集导致 maxSize 被频繁触发而打断 startupDelayMs
39
+ * - 行为:在 startupDelayMs 窗口内使用此值作为队列阈值
40
+ * - 默认:maxSize * 10(若未设置则为 200)
41
+ */
42
+ startupMaxSize?: number;
36
43
  /**
37
44
  * 是否使用浏览器空闲时间上报
38
- * - 开启后会使用 requestIdleCallback 代替 setTimeout 调度批量发送
45
+ * - 开启后会先等待 intervalMs/startupDelayMs,然后在浏览器空闲时执行发送
39
46
  * - 配合 idleTimeout 使用,保证即使浏览器繁忙也能在超时后发送
40
47
  * - 默认:false
41
48
  */
@@ -43,7 +50,7 @@ export interface BatchOptions {
43
50
  /**
44
51
  * 空闲回调超时时间(ms)
45
52
  * - 当 useIdleCallback 为 true 时生效
46
- * - 即使浏览器繁忙,也会在此时间后强制执行
53
+ * - 在等待空闲期间,即使浏览器繁忙,也会在此时间后强制执行
47
54
  * - 默认:3000
48
55
  */
49
56
  idleTimeout?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/web.esm.js CHANGED
@@ -179,8 +179,10 @@ class ClsLoggerCore {
179
179
  this.batchTimerDueAt = null;
180
180
  this.initTs = 0;
181
181
  this.startupDelayMs = 0;
182
+ this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
182
183
  this.useIdleCallback = false;
183
184
  this.idleTimeout = 3000;
185
+ this.pendingIdleCallback = null; // requestIdleCallback 的 id
184
186
  this.visibilityCleanup = null;
185
187
  // 参考文档:失败缓存 + 重试
186
188
  this.failedCacheKey = 'cls_failed_logs';
@@ -253,6 +255,8 @@ class ClsLoggerCore {
253
255
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
254
256
  this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
255
257
  this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
258
+ // startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
259
+ this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
256
260
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
257
261
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
258
262
  // 预热(避免首条日志触发 import/初始化开销)
@@ -628,11 +632,15 @@ class ClsLoggerCore {
628
632
  return;
629
633
  }
630
634
  this.memoryQueue.push(log);
631
- if (this.memoryQueue.length >= this.batchMaxSize) {
635
+ const now = Date.now();
636
+ // 判断是否在启动合并窗口内
637
+ const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
638
+ // 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
639
+ const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
640
+ if (this.memoryQueue.length >= effectiveMaxSize) {
632
641
  void this.flushBatch();
633
642
  return;
634
643
  }
635
- const now = Date.now();
636
644
  const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
637
645
  const desiredDelay = Math.max(0, desiredDueAt - now);
638
646
  if (!this.batchTimer) {
@@ -649,17 +657,19 @@ class ClsLoggerCore {
649
657
  }
650
658
  /**
651
659
  * 调度批量发送
652
- * - 支持 requestIdleCallback(浏览器空闲时执行)
653
- * - 降级为 setTimeout
660
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
661
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
654
662
  */
655
663
  scheduleFlush(desiredDelay) {
656
664
  if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
657
- // 使用 requestIdleCallback,设置 timeout 保证最终执行
658
- const idleId = requestIdleCallback(() => {
659
- void this.flushBatch();
660
- }, { timeout: Math.max(desiredDelay, this.idleTimeout) });
661
- // 存储 idleId 以便清理(类型兼容处理)
662
- this.batchTimer = idleId;
665
+ // setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
666
+ this.batchTimer = setTimeout(() => {
667
+ const idleId = requestIdleCallback(() => {
668
+ this.pendingIdleCallback = null;
669
+ void this.flushBatch();
670
+ }, { timeout: this.idleTimeout });
671
+ this.pendingIdleCallback = idleId;
672
+ }, desiredDelay);
663
673
  }
664
674
  else {
665
675
  this.batchTimer = setTimeout(() => {
@@ -669,22 +679,29 @@ class ClsLoggerCore {
669
679
  }
670
680
  /**
671
681
  * 取消已调度的批量发送
682
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
672
683
  */
673
684
  cancelScheduledFlush() {
674
- if (!this.batchTimer)
675
- return;
676
- try {
677
- if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
678
- cancelIdleCallback(this.batchTimer);
679
- }
680
- else {
685
+ // 清理 setTimeout
686
+ if (this.batchTimer) {
687
+ try {
681
688
  clearTimeout(this.batchTimer);
682
689
  }
690
+ catch {
691
+ // ignore
692
+ }
693
+ this.batchTimer = null;
683
694
  }
684
- catch {
685
- // ignore
695
+ // 清理可能的 pendingIdleCallback
696
+ if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
697
+ try {
698
+ cancelIdleCallback(this.pendingIdleCallback);
699
+ }
700
+ catch {
701
+ // ignore
702
+ }
703
+ this.pendingIdleCallback = null;
686
704
  }
687
- this.batchTimer = null;
688
705
  }
689
706
  getDesiredBatchFlushDueAt(nowTs) {
690
707
  const start = this.initTs || nowTs;
@@ -897,7 +914,14 @@ class ClsLoggerCore {
897
914
  // 先清空,再尝试发送
898
915
  writeStringStorage(this.failedCacheKey, JSON.stringify([]));
899
916
  this.memoryQueue.unshift(...logs);
900
- void this.flushBatch();
917
+ // 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
918
+ if (!this.batchTimer) {
919
+ const now = Date.now();
920
+ const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
921
+ const desiredDelay = Math.max(0, desiredDueAt - now);
922
+ this.batchTimerDueAt = desiredDueAt;
923
+ this.scheduleFlush(desiredDelay);
924
+ }
901
925
  }
902
926
  /**
903
927
  * 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
package/dist/web.js CHANGED
@@ -202,8 +202,10 @@ class ClsLoggerCore {
202
202
  this.batchTimerDueAt = null;
203
203
  this.initTs = 0;
204
204
  this.startupDelayMs = 0;
205
+ this.startupMaxSize = 0; // 启动窗口内的 maxSize,0 表示使用默认计算值
205
206
  this.useIdleCallback = false;
206
207
  this.idleTimeout = 3000;
208
+ this.pendingIdleCallback = null; // requestIdleCallback 的 id
207
209
  this.visibilityCleanup = null;
208
210
  // 参考文档:失败缓存 + 重试
209
211
  this.failedCacheKey = 'cls_failed_logs';
@@ -276,6 +278,8 @@ class ClsLoggerCore {
276
278
  this.startupDelayMs = options.batch?.startupDelayMs ?? this.startupDelayMs;
277
279
  this.useIdleCallback = options.batch?.useIdleCallback ?? this.useIdleCallback;
278
280
  this.idleTimeout = options.batch?.idleTimeout ?? this.idleTimeout;
281
+ // startupMaxSize:启动窗口内的队列阈值,默认为 maxSize * 10(至少 200)
282
+ this.startupMaxSize = options.batch?.startupMaxSize ?? Math.max(this.batchMaxSize * 10, 200);
279
283
  this.failedCacheKey = options.failedCacheKey ?? this.failedCacheKey;
280
284
  this.failedCacheMax = options.failedCacheMax ?? this.failedCacheMax;
281
285
  // 预热(避免首条日志触发 import/初始化开销)
@@ -651,11 +655,15 @@ class ClsLoggerCore {
651
655
  return;
652
656
  }
653
657
  this.memoryQueue.push(log);
654
- if (this.memoryQueue.length >= this.batchMaxSize) {
658
+ const now = Date.now();
659
+ // 判断是否在启动合并窗口内
660
+ const inStartupWindow = this.startupDelayMs > 0 && now - this.initTs < this.startupDelayMs;
661
+ // 启动窗口内使用 startupMaxSize,正常情况使用 batchMaxSize
662
+ const effectiveMaxSize = inStartupWindow ? this.startupMaxSize : this.batchMaxSize;
663
+ if (this.memoryQueue.length >= effectiveMaxSize) {
655
664
  void this.flushBatch();
656
665
  return;
657
666
  }
658
- const now = Date.now();
659
667
  const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
660
668
  const desiredDelay = Math.max(0, desiredDueAt - now);
661
669
  if (!this.batchTimer) {
@@ -672,17 +680,19 @@ class ClsLoggerCore {
672
680
  }
673
681
  /**
674
682
  * 调度批量发送
675
- * - 支持 requestIdleCallback(浏览器空闲时执行)
676
- * - 降级为 setTimeout
683
+ * - 先使用 setTimeout 保证最小延迟(desiredDelay)
684
+ * - 若开启 useIdleCallback,在延迟结束后等待浏览器空闲再执行
677
685
  */
678
686
  scheduleFlush(desiredDelay) {
679
687
  if (this.useIdleCallback && typeof requestIdleCallback !== 'undefined') {
680
- // 使用 requestIdleCallback,设置 timeout 保证最终执行
681
- const idleId = requestIdleCallback(() => {
682
- void this.flushBatch();
683
- }, { timeout: Math.max(desiredDelay, this.idleTimeout) });
684
- // 存储 idleId 以便清理(类型兼容处理)
685
- this.batchTimer = idleId;
688
+ // setTimeout 保证最小延迟,再 requestIdleCallback 在空闲时执行
689
+ this.batchTimer = setTimeout(() => {
690
+ const idleId = requestIdleCallback(() => {
691
+ this.pendingIdleCallback = null;
692
+ void this.flushBatch();
693
+ }, { timeout: this.idleTimeout });
694
+ this.pendingIdleCallback = idleId;
695
+ }, desiredDelay);
686
696
  }
687
697
  else {
688
698
  this.batchTimer = setTimeout(() => {
@@ -692,22 +702,29 @@ class ClsLoggerCore {
692
702
  }
693
703
  /**
694
704
  * 取消已调度的批量发送
705
+ * - 同时清理 setTimeout 和可能的 requestIdleCallback
695
706
  */
696
707
  cancelScheduledFlush() {
697
- if (!this.batchTimer)
698
- return;
699
- try {
700
- if (this.useIdleCallback && typeof cancelIdleCallback !== 'undefined') {
701
- cancelIdleCallback(this.batchTimer);
702
- }
703
- else {
708
+ // 清理 setTimeout
709
+ if (this.batchTimer) {
710
+ try {
704
711
  clearTimeout(this.batchTimer);
705
712
  }
713
+ catch {
714
+ // ignore
715
+ }
716
+ this.batchTimer = null;
706
717
  }
707
- catch {
708
- // ignore
718
+ // 清理可能的 pendingIdleCallback
719
+ if (this.pendingIdleCallback !== null && typeof cancelIdleCallback !== 'undefined') {
720
+ try {
721
+ cancelIdleCallback(this.pendingIdleCallback);
722
+ }
723
+ catch {
724
+ // ignore
725
+ }
726
+ this.pendingIdleCallback = null;
709
727
  }
710
- this.batchTimer = null;
711
728
  }
712
729
  getDesiredBatchFlushDueAt(nowTs) {
713
730
  const start = this.initTs || nowTs;
@@ -920,7 +937,14 @@ class ClsLoggerCore {
920
937
  // 先清空,再尝试发送
921
938
  writeStringStorage(this.failedCacheKey, JSON.stringify([]));
922
939
  this.memoryQueue.unshift(...logs);
923
- void this.flushBatch();
940
+ // 触发定时器而非直接 flush,以尊重 startupDelayMs 配置
941
+ if (!this.batchTimer) {
942
+ const now = Date.now();
943
+ const desiredDueAt = this.getDesiredBatchFlushDueAt(now);
944
+ const desiredDelay = Math.max(0, desiredDueAt - now);
945
+ this.batchTimerDueAt = desiredDueAt;
946
+ this.scheduleFlush(desiredDelay);
947
+ }
924
948
  }
925
949
  /**
926
950
  * 统计/计数类日志:按字段展开上报(若 data 为空默认 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@be-link/cls-logger",
3
- "version": "1.0.1-beta.21",
3
+ "version": "1.0.1-beta.23",
4
4
  "description": "@be-link cls-logger - 腾讯云 CLS 日志上报封装",
5
5
  "homepage": "https://github.com/snowmountain-top/be-link",
6
6
  "author": "zhuiyi",