前端稳定性:监控 SDK 与故障闭环
设计覆盖错误、性能、资源、请求和用户行为的前端监控 SDK,并处理采样、隐私、上报与定位。
前端稳定性:监控 SDK 与故障闭环
本文聚焦监控系统的设计边界、数据质量和故障闭环。
1. 监控的目标
监控不是把所有事件都发到服务器,而是让团队能回答三个问题:发生了什么、影响了谁、如何复现和修复。SDK 采集层应尽量轻量,服务端聚合和告警层负责分析;采集失败不能影响主业务。
建议按四类信号建模:
- 错误:运行时异常、未处理 Promise、资源加载失败、接口失败和框架错误。
- 性能:导航、资源、长任务、交互延迟、核心 Web Vitals(如 LCP、INP、CLS)。
- 请求:URL 模板、方法、状态、耗时、traceId 和取消/超时原因。
- 行为:页面访问、关键交互和用户操作路径;默认只记录必要的事件名称,不采集输入框原文。
先定义指标口径:异常事件数/页面访问量反映事件密度,发生异常的会话数/总会话数反映影响面,两者不能都叫“错误率”后直接比较。请求失败要区分网络失败、HTTP 状态失败、业务失败与主动取消;明确分母是否包括取消和重试,并按版本、路由及时间窗口对齐。
白屏检测也不是“DOM 为空就算故障”:骨架屏、登录跳转和合法空状态都可能误报。可结合关键容器、路由就绪信号、资源错误和超时窗口,再用真实故障样本校验。页面崩溃或进程被杀时 SDK 可能来不及发送任何事件,客户端无上报不能证明没有故障,需要服务端业务指标和合成巡检补充。
2. 统一事件信封
所有信号使用统一字段,便于关联和采样:
type MonitorEvent = {
id: string
type: 'error' | 'performance' | 'request' | 'behavior'
occurredAt: number
sessionId: string
page: { url: string; route?: string }
release?: string
traceId?: string
payload: Record<string, unknown>
}
id 应在客户端生成并用于去重;release 用来匹配源码映射和部署版本。URL、错误消息和自定义属性在进入队列前就要脱敏,不能把清洗责任全部推给服务端。
3. 错误捕获边界
下面是浏览器端的接入示意,createEvent、getSafeUrl、normalizeError 由适配层实现;传入的 report 必须同步兜住内部异常,不能让采集失败逃逸到业务。normalizeError 需要接受任意拒绝值,包括 null、字符串和循环引用对象。
function installErrorCapture(report: (event: MonitorEvent) => void) {
window.addEventListener('error', (event) => {
const target = event.target
const isResource = target instanceof HTMLScriptElement ||
target instanceof HTMLLinkElement || target instanceof HTMLImageElement
report(isResource
? createEvent('error', { kind: 'resource', url: getSafeUrl(target) })
: createEvent('error', { kind: 'runtime', message: event.message, stack: event.error?.stack }))
}, true)
window.addEventListener('unhandledrejection', (event) => {
report(createEvent('error', {
kind: 'unhandledrejection',
reason: normalizeError(event.reason),
}))
})
}
框架错误边界负责捕获组件树中的渲染错误,但不能替代全局监听。跨域脚本常常只能得到“Script error.”;应通过正确的 CORS 响应和 crossorigin 配置改善堆栈,同时接受第三方脚本不可观测的边界。错误应按 fingerprint 聚合(类型、文件、行列、标准化消息),避免同一问题刷爆告警。
接入时还要补齐生命周期:重复初始化只能安装一次;卸载要移除同一函数引用的监听、断开 Observer、清理定时器,并按宿主销毁约定处理待发队列。不要为了采集而对错误调用 preventDefault(),也不要让 window.onerror 返回 true 去吞掉浏览器默认错误输出。
CSS 声明或选择器写错通常被解析器容错忽略,不会作为 JS 异常抛出。向 <head> 临时插入 <style> 再 try/catch 不能检测这类问题;观察 <head> 的 MutationObserver 若持续插入节点,还可能反复触发自身。静态规则交给样式检查,渲染结果交给关键元素断言和视觉回归;跨域样式表的 CSSOM 读取也有权限边界。
资源兜底需设终止条件。例如图片从原资源切一次备用资源,备用仍失败就显示最终占位;先保存失败时的 currentSrc || src,再改地址,否则上报会丢失原故障 URL。记录元素的兜底状态,处理 <picture> 的 <source> 和 srcset 候选,避免浏览器再次选回坏地址;占位不能继续走同一失败重试链。
4. 性能与请求采集
使用 PerformanceObserver 观察导航、资源、长任务和布局偏移,优先在空闲时间汇总,而不是每条资源都立刻上报。优先在 visibilitychange 进入 hidden 时刷新,也可在 pagehide 补充;不能假设卸载事件一定触发。sendBeacon() 返回 true 只表示浏览器接受排队,不代表服务端已收到;返回 false 表示未接受。跨启动补偿依赖提前持久化的 outbox,服务端不能重发从未收到的事件,也不能阻塞卸载等待确认。
请求拦截器应记录方法、脱敏后的 URL 模板、状态、耗时和 traceId,不记录完整请求体或 Authorization。对慢请求和错误请求提高采样率,对正常请求按会话或哈希稳定采样,避免同一用户的样本在页面间随机跳动。
4.1 请求拦截必须保持业务语义
| 接入点 | 正确契约 | 验证用例 |
|---|---|---|
| Fetch 输入 | 原样转交 input/init;方法按 init.method、Request 中的方法、默认 GET 的顺序确定,不额外构造会消费流的 Request |
Request 带方法,再由 init 覆盖;请求体为流 |
| Fetch 输出 | 返回原 Response,不读取或消费 body;原生拒绝时重新抛出同一个原因;采集自身异常被隔离 | 流式响应仍能读取;report 抛错不改变请求结果 |
| HTTP 与取消 | HTTP 4xx/5xx 不会自动使 Fetch reject;主动取消、超时、网络失败按调用契约分类 | 500 响应仍返回业务;用户取消不算后端不可用 |
| XHR | 从 send 开始计时;识别 load/error/timeout/abort,用统一结束逻辑只记一次;再次 open/send 时清理上一轮状态 |
open 后延迟 send;超时后 loadend 不重复上报;实例复用 |
| 自身隔离 | 上报端点和 SDK 内部请求在进入拦截器时排除;初始化/卸载不能反复叠加包装 | 上报失败不产生新一轮上报;重复安装不重复计数 |
包装原生方法时保留正确的接收者与参数;卸载只在当前全局方法仍是自己安装的包装器时还原,避免覆盖其他 SDK 后装的包装器。业务请求结束不应等待上报请求结束。navigator.onLine 和网络类型只能提供环境提示,不能证明某个业务接口可达。
指标实现复用指标采集与问题诊断:当前核心 Web Vitals 使用 LCP/INP/CLS;FCP 需过滤 paint 条目的名称,CLS 需按会话窗口聚合,长任务条目名为 longtask。responseStart 是首字节时刻而不是首次绘制,load 回调内的 loadEventEnd 也可能尚未写入。
5. 队列、上报与降级
采集 -> 脱敏/标准化 -> 采样 -> 内存队列 -> 批量压缩 -> Beacon/fetch 上报
| |
+-- 超限丢弃 +-- 失败退避/熔断
- 队列必须有事件条数和字节上限;超限时优先保留错误和关键性能事件。
- 上报接口使用独立域名或路径,并设置超时、重试上限和指数退避。
- 连续失败时熔断一段时间,避免网络异常放大请求压力。
- SDK 初始化失败、CSP 阻断或浏览器 API 缺失时静默降级,不抛出影响业务的异常。
- 采集 SDK 自身的错误要有内部计数器,但不能递归上报同一错误。
5.1 批量上报只确认发送的快照
队列可分为待发送区和发送中批次。开始发送时取独立快照,快照内的事件和聚合计数不再被后续采集修改;只允许一个批次发送,或显式跟踪每个并行批次的所有权。一个最小反例是:
t0: 待发送 [A, B] -> 固定批次 X=[A, B],待发送清空
t1: X 上报中,新事件 C -> 待发送 [C]
t2: X 被服务端确认 -> 删除 X,待发送仍是 [C]
X 失败/未确认 -> 保留 X 待重试,不能覆盖或清空 C
不能在 .then() 中直接 clear() 全局聚合 Map:Fetch 收到 500 也会 fulfilled,而且这时新事件可能已写入 Map。按上报协议检查 HTTP 状态和服务端确认;若协议只确认部分事件,只删除明确确认的 ID。事件 ID 在重试间保持不变,批次 ID 可关联尝试过程,服务端按事件去重。聚合 fingerprint 的数量、每组样本数、待发送与发送中批次的总字节都需设上限,避免快照绕过内存限制。
响应丢失时客户端无法知道服务端是否已接收,因此可实现“在保留期限内重试 + 服务端幂等”,不能宣称客户端绝对不丢或只发一次。需要跨启动补偿时先持久化脱敏事件,确认后再删除;存储失败、配额不足和过期丢弃都应有计数。Beacon 无服务端确认时不能把排队成功当作 outbox 已确认,后续重发要依赖事件 ID 去重。
6. 隐私与合规
默认采用数据最小化:不采集输入框内容、Cookie、令牌、完整 IP 或未经必要性评估的设备指纹。对 URL 查询参数、错误消息和自定义上下文做字段级脱敏;提供采集开关、用户同意状态、数据保留期限和删除机制。第三方脚本、跨境传输和小程序平台还需要遵守各自的权限与隐私要求。
7. Source Map 与定位
生产错误定位依赖“发布版本 + 构建产物 + 对应 Source Map”三者一致。Source Map 不应公开暴露源码;可以上传到受控的错误平台,客户端只携带 release 和文件名。部署时生成校验清单,防止旧版本错误匹配到新映射。错误详情应包含页面路由、浏览器版本、最近的请求 traceId 和用户可复现步骤,但仍要经过脱敏。
8. 告警和故障闭环
告警按影响面和趋势分级,而不是每个事件一条告警。常见维度包括错误率、受影响会话数、P75/P95 性能、资源失败率和接口超时率。告警应链接到聚合后的样本、发布版本和回滚入口;故障结束后记录根因、修复提交、回归测试和监控规则调整,形成闭环。
9. 大促高可用保障
9.1 稳定性是分层体系
稳定性不是单一的监控项目,可以用一组由底到顶的能力检查系统:
Monitoring
-> Incident Response
-> Postmortem / Root Cause Analysis
-> Testing and Release Procedures
-> Capacity Planning
-> Product
-> Development
监控让团队知道异常已经发生或即将发生,应急响应让问题被有序处理,复盘把一次故障转化为改进项,测试和发布管控限制变更风险,容量规划应对流量和业务规模变化,研发与产品设计则从源头降低系统复杂度。这个层级适合作为稳定性检查清单,不应被理解为只要接入前端 SDK 就能获得高可用。
9.2 先梳理入口、依赖和业务变量
大促保障通常在有限周期内完成,应该先找关键链路和薄弱节点,再决定投入点。入口盘点可按优先级覆盖:
- 核心重保入口:对服务等级、准确性和响应时间有明确承诺。
- 资损入口:故障可能直接影响公司或客户资金。
- 大流量入口:按 TPS/QPS 排名前列,即使业务有损较小,也可能把整体容量打满。
沿入口追踪 HTTP、RPC、消息和外部存储等节点,并分别判断:节点不可用是否会中断业务、返回错误或显著拖慢系统;日常是否经常超时或资源不足;最近是否有大版本改造、首次参加大促、历史高等级故障或资损风险。强依赖必须有明确的替代或切换策略,弱依赖则应提前设计降级边界。
容量模型不能只看本服务机器数,还要把上游流量、下游容量、业务量倍数、峰值日期和场景流量分配放在同一张链路图中,并用压测或历史数据验证峰值余量。特殊玩法和应急策略也应在活动前同步,否则技术预案无法正确评估影响。
9.3 监控和告警要覆盖三层信号
黑盒监控关注用户看到的结果,白盒监控关注系统内部状态和原因。大促梳理时可从核心和资损链路出发,按三层建立监控模型:
- Biz:成功率、业务延时、关键状态和资损指标,最接近用户感受。
- Application:运行时、线程/进程、中间件和数据库等应用依赖。
- System:CPU、内存、网络、磁盘和机器水位等基础资源。
每层都可以用四个黄金信号归类:Latency、Errors、Traffic、Saturation。业务层异常优先触发告警,应用和系统层更多用于定位原因,只有高风险指标才需要实时通知。每条告警至少明确级别、触发阈值、持续时间、通知人、通知方式,以及是否关联故障升级、资损和应急预案。阈值不能过迟,也不能因过敏造成告警疲劳;必要时结合多个指标和业务波动曲线。
专项梳理的产出应可被执行和审计:监控点清单、告警模型、业务大盘、应用/系统大盘,以及每项指标对应的日志和预案链接。已有 SDK 的错误、请求、性能和行为事件可作为 Biz 层信号的采集来源,详见错误捕获边界和性能与请求采集。
9.4 把临场决策变成预案和作战手册
值班人员在高并发故障现场应该做选择题,而不是临时发明处理方案。预案按执行时机和影响对象可分为四类:
- 技术应急:节点不可用时的限流、降级、摘除或切换。
- 技术前置:峰值前提前熔断弱依赖、暂停冲突的离线任务、校验容量和开关。
- 业务应急:业务数据错误、玩法变更或需要业务确认的有损处理。
- 业务前置:配合活动节奏提前调整配置和服务策略。
每份预案都应写清执行/关闭时间、触发和恢复阈值、系统与业务影响、决策/执行/验证人员,以及开启和关闭的验证方式。把入口、流量分支、依赖强弱、资损评估和预案汇总成全链路作战地图,才能从一个告警反查受影响范围,并反向检查容量与预案是否完整。
作战手册按事前、事中、事后组织:事前检查权限、限流、开关、数据库和中间件容量以及监控有效性;事中提供预案、排查脚本、日志源、大盘、上下游分组、值班通讯录和问题记录;事后恢复限流/扩容设置并完成复盘。每项检查都要有执行人、检查方法和结果,避免只列链接不留证据。
9.5 演练和事故指挥
沙盘演练应使用历史真实故障作为输入,重点验证止血、分工和定位,而不是只检查文档是否存在。现场原则是先恢复服务,同时保留机器、日志和水位等证据;依据白盒指标缩小范围;明确职责,避免多人同时修改同一开关;无法技术恢复时及时转为业务影响和资损评估。
常见止血动作包括入口限流、弱依赖降级、摘除异常单点,以及单元切流或备份切换。事故管理至少需要四个角色:事故总控负责全局协调和未分配事务,处理团队负责具体修复,发言人向内外同步并维护状态文档,规划负责人负责轮班和职责交接。配套的作战室、实时事故文档和公开交接记录,能让长时间故障保持信息一致。
来源:大促搭投实践&高可用性保障(简化版).pdf 第 9-17 页。
9.6 降级策略必须包含恢复路径
降级配置应描述完整目标状态,不能只记录“再关掉一项”。下面的等级是业务设计示例,不是固定标准:
| 状态 | 非核心推荐/动画 | 资源与缓存 | 核心交易 |
|---|---|---|---|
| NORMAL | 按产品配置开启 | 正常加载 | 实时校验 |
| LIGHT | 暂停非核心计算与预取 | 缩减图片质量和后台请求 | 实时校验 |
| HEAVY | 关闭非核心模块 | 允许带时效标识的展示缓存 | 依赖可用才允许继续 |
| EMERGENCY | 静态故障提示 | 仅保留必要资源 | 无法确认时停止提交并保留恢复入口 |
从 HEAVY 回到 LIGHT 也要恢复被重度降级关闭、但轻度状态允许的能力;回到 NORMAL 要恢复原产品配置与定时器策略,不能简单把所有开关设为 true。保留用户已输入内容,恢复时重新确认服务端状态,避免重复下单。
状态机同时规定触发阈值、恢复阈值、连续观测窗口和冷却期。例如进入降级使用较高错误率阈值,恢复使用较低阈值并持续一段时间,再逐级恢复;这些参数应由历史数据与演练确定。每次切换记录策略版本、触发指标、影响范围和验证结果,允许人工接管。按固定日期限流不能替代容量评估,客户端限流也不能保护服务端免于其他客户端的压力。
缓存与重试必须服从业务语义:展示缓存按用户/租户和版本隔离,设 TTL 和退出登录清理;库存、价格、权限和订单是否成功仍以服务端为准。localStorage 读写及 JSON 解析需要处理失败,IndexedDB 要在版本升级事务中创建 store,并等待事务完成才能认为写入已提交。注册 Service Worker 本身不等于已经实现离线能力。
只对可安全重试的操作重试;写请求依赖服务端幂等键与结果查询。总期限、次数上限、指数退避与抖动、Retry-After 和取消信号必须共同生效,切备用域名需重新评估凭据、Cookie 和 CSRF token 的发送范围。并发队列实现见请求客户端的错误归属与并发队列,在循环里逐项 await 不会获得预期并发。
9.7 AI 诊断与应急执行分开
模型可以异步解释聚合异常、辅助归因和草拟预案,但确定性规则仍独立完成告警和止血。先保存、发送原始脱敏事件,再把有限摘要交给模型;模型超时、不可用或格式错误时丢弃建议,不能阻断事件链,也不能用异步模型包装器改变原 emit 的同步语义。
建议进入执行层前需校验结构、动作白名单、目标环境、权限、审批要求、策略版本与有效期。模型不能直接取得任意修复或发布权限;高影响动作需业务授权,执行后验证指标并提供回滚。代码审查与研发规范复用AI 研发工作流,不能把模型生成的代码或“已审查”标记当作正确性证据。
周期分析采用单任务执行,完成后再调度下一次,或显式取消并丢弃过期结果。不要用无重叠保护的 setInterval(async ...) 并行生成多份策略;返回时仍要检查版本,防止旧建议覆盖人工更新。模型的收益以复盘命中率、误操作率、告警延迟和成本评估,资料中的 VibeCoding* 名称只是概念占位,不能当作现成 SDK 使用。
10. 测试清单
- 模拟运行时错误、Promise 拒绝、资源失败、网络超时和 Beacon 不可用。
- 检查事件去重、采样稳定性、队列上限、重试退避和熔断。
- 验证脱敏规则不会泄露 URL 参数、表单值和认证信息。
- 在 SSR、Web Worker、旧浏览器和小程序适配层分别测试能力检测与降级。
- 使用构建版本 fixture 验证 Source Map 关联和 release 不匹配时的安全失败。
- 上报 A/B 期间插入 C,分别模拟成功、500、响应丢失和部分确认,验证 C 不丢、A/B 重试 ID 不变。
- 对图片原地址与备用地址都注入失败,验证兜底有界且保留原 URL;验证 Fetch 流式响应与 XHR 实例复用。
- 演练 HEAVY -> LIGHT -> NORMAL,检查实际开关、定时器和用户输入;延迟模型结果,确认不会覆盖新策略或阻塞告警。
11. 来源与取舍
本轮增补来自 双十一特别专题:前端web稳定性保障.pdf:第 1-12 页用于细化错误与请求边界,第 14-17、22-36 页用于补充上报确认、缓存和可恢复降级,第 41-55 页用于澄清 AI 诊断与执行权限。课程宣传、未经验证的收益比例与职业预测不作为技术结论。
性能、懒加载与预取复用性能专题,完整稳定性项目表达复用稳定性监控与大促高可用。小程序环境、包身份、门禁与发布版本追踪见多环境与发布门禁。