前端监控数据怎样做采样、去重、聚合和告警?
性能和行为数据按会话做一致性采样,错误数据尽量全量或单独提高采样率;客户端限流去重,服务端按错误指纹聚合成问题;告警基于错误率和影响用户数并与发布版本关联,避免按单条错误报警。
参考答案
简答
- 采样按会话或用户 ID 做一致性采样:同一会话要么全采要么全不采,事件链才完整;性能数据可以低采样,错误数据应全量或单独设置更高采样率。
- 客户端先去重和限流:同一错误短时间内只报一次,单页面设上报条数上限,防止死循环报错打爆接口。
- 服务端按错误指纹聚合:用错误类型、规范化后的消息和栈顶帧生成指纹,同指纹的事件归为一个问题。
- 告警看比率和影响面:错误率、影响用户数、相对基线的突增,并标注发布版本;按单条错误或绝对数量告警会被流量波动淹没。
采样
JavaScript
function hash(text) {
let h = 0;
for (const char of text) h = (h * 31 + char.charCodeAt(0)) >>> 0;
return h;
}
const isSampled = (sessionId, rate) => hash(sessionId) % 10000 < rate * 10000;
console.log(isSampled('session-a', 0.1) === isSampled('session-a', 0.1)); // true
用 Math.random() 逐条决定会让同一会话的事件散落,无法回放用户路径。按 ID 哈希后得到稳定结果,这与 OpenTelemetry 中按 trace ID 一致性采样的思路相同。
决定在采集开始时做出(头部采样),实现简单,但事后无法"把出错的会话补采回来"。需要"出错的会话全保留"时,可以在客户端缓存最近事件,出错时连同上下文一起上报。
上报时带上采样率,服务端统计总量时除以采样率还原估算值。
客户端去重与限流
JavaScript
const seen = new Map();
const MAX_PER_PAGE = 50;
let sent = 0;
function shouldReport(fingerprint, now = Date.now()) {
if (sent >= MAX_PER_PAGE) return false;
const last = seen.get(fingerprint);
if (last && now - last < 60_000) return false; // 一分钟内同指纹只报一次
seen.set(fingerprint, now);
sent += 1;
return true;
}
数据先放入队列批量发送,页面隐藏时再刷出,发送方式见《页面关闭时怎样可靠上报?sendBeacon 和 keepalive 怎么选?》。
指纹与聚合
JavaScript
const fingerprint = (e) => `${e.name}|${e.message.replace(/\d+/g, '{n}')}|${e.frame}`;
console.log(fingerprint({ name: 'TypeError', message: 'id 123 not found', frame: 'app.js:10:5' }));
// TypeError|id {n} not found|app.js:10:5
消息里的 ID、时间戳、URL 参数要先规范化,否则同一 bug 会被拆成成千上万个问题。栈顶帧最好用 Source Map 还原后的源码位置,这样文件哈希变化不会让同一个问题在每次发布后都"重新出现",还原方式见《线上报错怎样用 Source Map 还原,又不把 map 文件公开?》。
聚合后每个问题记录首次出现版本、最近出现时间、事件数和影响用户数,作为排期依据。
告警
- 以比率为主:JS 错误率 = 出错会话数 / 总会话数,避免流量高峰误报、低谷漏报。
- 以基线对比:与上周同时段或上一版本比较,突增超过阈值才触发。
- 新问题告警:新版本首次出现且影响用户数超过阈值的指纹。
- 设置持续时间和静默期:连续几个统计窗口超标才发,同一问题告警后静默一段时间,防止刷屏。
- 告警中附带版本、浏览器分布和示例堆栈,方便判断是否需要回滚。
常见追问
- 为什么不能全量采集?流量大时存储、带宽和用户端开销都不可接受,且大部分重复数据没有额外价值。
- 浏览器扩展、爬虫的错误怎么处理?按来源文件和 User-Agent 过滤,不计入错误率。
- 采样会不会漏掉罕见错误?会,这是错误数据要单独提高采样率的原因。