前端监控笔记(三)

消息上报机制

常见方案

  • sendBeacon :常用于页面卸载阶段上报,接口简单,不阻塞页面卸载流程;通常存在单次请求体大小限制(常见约 64KB,不同浏览器可能有差异)。
  • fetch keepalive :写法更灵活,可自定义方法、请求头与内容类型;同样受卸载场景下请求体大小限制(常见约 64KB)。

发送时机

visibilitychange 事件中,当 document.visibilityState === 'hidden' 时触发上报。

  • 触发时机早于 beforeunload,标签页切后台、最小化、息屏等场景也能触发。
  • beforeunload 可能不会触发,visiblituchange 移动端可能存在兼容问题,beforeunload 可作为兼容兜底实现。
typescript 复制代码
window.addEventListener('visibilitychange', () => {
  if (document.visibilityState !== 'hidden') return;

  const payload = JSON.stringify(pendingEvents);

  // 优先使用 sendBeacon,避免页面卸载时请求被中断。
  const ok = navigator.sendBeacon('/collect', payload);
  if (ok) return;

  // sendBeacon 失败时降级为 keepalive fetch。
  fetch('/collect', {
    method: 'POST',
    keepalive: true, // 允许页面卸载时继续发送请求
    headers: { 'Content-Type': 'application/json' },
    body: payload
  }).catch(() => {
    // 失败后重传,离线缓存处理
  });
});

fetchLater

注册延迟请求,在浏览器在页面离开或 activateAfter 到时后触发发送理解成 fetch + activeAfter 构成的延迟版本)

解决 sendBeacon 和 fetch keepalive 发送时机选择的问题,由浏览器排队处理

但同样有请求体大小限制,兼容性较差。

typescript 复制代码
try {
  fetchLater('/collect', {
    method: 'POST',
    body: JSON.stringify(pendingEvents),
    activateAfter: 60_000 // 最晚约 60s 后触发发送(实际时机由浏览器决定,不指定可能是页面关闭时发送)
  });
} catch (error) {
  // 配额超限或浏览器不支持时,降级 sendBeacon / fetch keepalive
}

指数退避重传

服务端返回 429 Too Many Requests(可结合 Retry-After 响应头)或其他异常时,使用指数退避进行重试。

text 复制代码
delay = baseDelay * 2^attempt * (1 + random(0, 0.5)) // 加入随机抖动

批量合并

上传时,可以通过定时器/maxBatch 批量合并日志消息,在单个请求上传处理

离线缓存

当用户离线或临时网络抖动时,可使用 IndexedDB / 内存维护缓冲队列,待网络恢复后重传。

  1. offline 事件监听到用户网络离线 ,将数据存入缓冲队列
  2. 监听 online 事件,将数据取出并重新执行
typescript 复制代码
// 简化 indexedDB 离线队列
// 如果是内存缓存队列,可以直接在监听 offline 时暂停队列批量发送,同时把发送数据重新推入缓冲队列
class OfflineQueue {
  #dbName = 'sdk-queue'

  async enqueue(event) {
    const db = await this.#openDB()
    await db.add('events', { ...event, timestamp: Date.now() })
  }

  async flush() {
    if (!navigator.onLine) return
    const db = await this.#openDB()
    const events = await db.getAll('events')
    for (const event of events) {
      const ok = await this.#send(event)
      if (ok) await db.delete('events', event.id)
    }
  }
    
  //...
}

// 监听网络恢复,触发重传
window.addEventListener('online', () => queue.flush())

采样发送

原理:通过条件概率减少发送日志,降低存储和请求开销

  • 给定概率值,每次执行发送的时候 random 得到一个随机值
  • 如果随机值在采样概率之内,就将数据发送到后台否则不发送

采样类型

  • 固定采样:全局固定采样率,永远不变。
  • 确定性采样:通过固定 id 值,比如 userId 取 hash值,然后计算出同个用户/会话的采样率。
  • 动态采样:根据业务接口、用户身份、告警等级等业务规则实现采样,即针对业务进行不同频段的采样。
typescript 复制代码
// 固定采样值
function normalSample(rate: number) {
  return Math.random() < rate;
}

// 确定性采样示例:hash id, 采样值保持不变
function deterministicSample(id: string, rate: number) {
  const hash = simpleHash(id);
  return (hash % 10000) / 10000 < rate;
}
function simpleHash(input: string) {
  let hash = 0;
  for (let i = 0; i < input.length; i += 1) {
    hash = (hash * 31 + input.charCodeAt(i)) >>> 0;
  }
  return hash;
}



// 动态采样:根据业务、接口、以及下发采样配置实现采样
function dynamicSample(context: { url: string; isVip?: boolean }) {
  if (context.url.includes('/checkout')) return 1.0;
  if (context.isVip) return 0.5;
  return 0.1;
}
function dynamicSend(context: { url: string; isVip?: boolean }) {
  const rate = dynamicSample(context);
  return Math.random() < rate;
}

去重机制

通过为每个消息日志,错误信息生成独立指纹进行去重处理,减少重复推送,节省请求成本

消息事件去重

可组合以下字段生成指纹:

  • 消息类型。
  • 业务指纹(模块、接口、页面标识)。
  • 堆栈摘要(new Error().stack 归一化后摘要)。

异常类型去重:

  1. 判断异常类型和异常值,异常类型是 erroruncaughtrejection 这种独立类型,值是错误信息
  2. 比较指纹是否一致
  3. 比较堆栈跟踪信息是否一致

总结

  • 发送策略 :优先 visibilitychange + sendBeacon,失败时降级 fetch keepalivefetchLater 作为尝鲜使用。
  • 数据可靠处理:通过限流重试、采样、去重减少无效上报,离线队列保障弱网场景下的数据补偿。

参考内容

相关推荐
zzb15802 小时前
Kotlin 密封类与延迟初始化学习笔记
笔记·学习·kotlin
M ? A2 小时前
Vue Suspense 组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
im_AMBER2 小时前
Leetcode 159 无重复字符的最长子串 | 长度最小的子数组
javascript·数据结构·学习·算法·leetcode
天才熊猫君2 小时前
通用 Loading 状态管理器
前端·javascript·vue.js
胡志辉2 小时前
网络七层到底怎么落到一次前端请求上:从浏览器到网卡,再到远端服务器
前端·网络协议
三品吉他手会点灯2 小时前
C语言学习笔记 - 2.C概述 - HelloWorld程序举例
c语言·笔记·学习
怪兽同学2 小时前
统一管理Agent Skills
前端·agent
陆枫Larry2 小时前
微信小程序订阅消息完全指南:从原理到落地的全流程梳理
前端
Rousson2 小时前
硬件学习笔记-97 不同存储器件简单介绍
笔记·学习