前端问题常被描述成“偶尔白屏”“有时点不动”。在开发环境里一切正常,日志也只存在用户的浏览器中。没有现场信息时,排查只能依赖猜测和反复询问。

服务端早已习惯记录请求与错误,前端同样需要建立可观测性。不同之处在于,浏览器环境更碎片化,采集本身也不能成为新的性能负担。

记录能帮助行动的信息

一个错误栈如果没有版本、路由、用户操作和接口状态,价值非常有限。我会为每次发布保留构建标识,把未捕获异常、资源加载失败和关键请求错误统一上报,并限制相同错误的频率。

客户端上报的最小事件大致是这样:

type FrontendErrorEvent = {
  name: string;
  message: string;
  stack?: string;
  buildId: string;
  route: string;
  sessionId: string;
  actionTrail: Array<{ name: string; at: number }>;
  request?: { traceId?: string; urlPattern: string; status?: number };
  occurredAt: number;
};

这里故意保存 urlPattern,而不是完整 URL;操作轨迹只记录“点击提交”“切换标签”这类事件名,不记录输入内容。排障需要上下文,不等于可以无限采集用户数据。

buildId 解决了另一个常见问题。静态资源发布后,用户可能仍开着旧页面。没有版本信息时,同一条堆栈到底属于哪份源码都无法确认,Source Map 也就失去意义。

并非数据越多越好。输入内容、身份信息和业务数据不应被随意采集。可观测性首先是工程能力,也必须有明确的隐私边界。

从错误数量走向用户影响

错误发生一百次,可能来自一个用户的循环刷新;只发生一次,也可能阻断关键流程。监控应该同时关注影响用户数、页面路径和功能阶段,而不是只看总量。

性能也一样。平均加载时间会掩盖长尾,真实用户环境比实验室分数更能说明问题。先建立稳定基线,优化才有方向。

层级 示例 用途
用户结果 查询成功率、提交完成率 判断功能是否真的可用
页面体验 LCP、INP、首屏接口耗时 定位等待发生在哪一段
工程诊断 JS 错误、资源失败、接口错误 找到具体版本和调用链

只监控第三层,团队会得到很多红色数字,却不知道用户是否受影响。只看第一层,又无法定位原因。三层通过 sessionId 和后端 traceId 关联,才形成从用户结果到具体请求的证据链。

采样与去重保护系统

监控本身也可能制造流量。相同错误在循环中每秒出现数百次,如果全部上报,会挤占网络并淹没真正变化。客户端先按错误指纹去重,对高频事件采样,并在单次会话设置总量上限。

错误指纹不能只使用完整消息,因为动态参数会让同一问题变成无数条记录。更稳定的做法是组合错误类型、规范化堆栈和构建版本。服务端再聚合影响用户与页面,既保留趋势,也能回到具体样本。

可观测性的意义,是把“我觉得没问题”变成“我们知道哪里出了问题”。一旦系统能够诚实报告自身状态,团队才可能持续改善它。

浏览器动作、服务端请求和错误平台汇合的故障证据时序

图:版本、动作与 requestId 关联后,错误才从堆栈变成可调查事件。

一条前端错误事件的最小合同

我们最后固定保留 releaseId、routeTemplate、errorFingerprint、requestId、最近业务动作摘要和采样原因;用户输入、token 与完整响应默认不采集。事件超过大小上限就丢弃明细而不是阻塞页面。

告警验收也写成一句话:值班人能否从事件跳到对应版本、服务端请求和用户动作,并在十分钟内确定影响面。做不到时,增加更多日志通常不如补齐这几条关联字段。