前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

前端监控系统从 0 到 1 搭建全流程总结

首页2021-05-11 10:50:32Front-End
前端监控性能优化前端工程化

用户在群里甩来一张截图,说点了提交按钮没反应。你问他什么机型、什么网络、报了什么错,对面回一句「就是没反应啊」。然后你打开自己的 Chrome,一切正常。

这种对话我经历过太多次了。没有监控的时候,线上对前端来说就是一个黑盒,能不能复现全看运气。这篇是我把前端监控这套东西从指标定义、采集、上报、存储一直到告警和看板整个走了一遍之后的笔记合集,里面有能直接抄走的采集代码,也有踩过的坑。读完你至少能自己搭出一个跑得起来的最小监控系统,而不是只会说「我们接了 Sentry」。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • 一套完整前端监控系统的骨架长什么样,从采集到看板一共几层
  • 首屏时间(FMP)在 SPA 场景下怎么算,包含图片的页面为什么会算错
  • 白屏、卡顿、网络环境这三类指标各自的采集口径和代码
  • 性能 SDK 怎么设计才好接入,抽样、过滤、上报时机怎么定
  • 数据接入层为什么必须过一层 Kafka,清洗和计算分别做什么
  • 告警规则怎么配,什么样的问题值得叫醒人
  • JS 错误、接口异常、资源加载失败三条线的捕获方式
  • 用 Node.js + Logstash + Elasticsearch 手搭一套监控后端的完整流程
  • 首屏秒开的四重保障和白屏 300ms 的具体优化手段
  • 上线前该过一遍的 checklist

# 一、先把整套系统的骨架摆出来

很多人一上来就问「用什么 SDK」,这个问题问早了。前端监控不是一个 SDK 的事,它是一条从浏览器一直通到告警群的链路,任何一环断了,前面做得再精细都白搭。

先看这块,把整条链路画出来大概是这样:

                     浏览器 / WebView 里跑的那段 JS
 ┌────────────────────────────────────────────────────────────┐
 │ 采集层 SDK                                                  │
 │  ├─ 性能:FP / FMP / 瀑布流各阶段耗时 / FPS                  │
 │  ├─ 错误:onerror / unhandledrejection / resource error      │
 │  ├─ 接口:XMLHttpRequest 与 fetch 劫持                       │
 │  └─ 自定义:业务手动调用 report()                            │
 └───────────────┬────────────────────────────────────────────┘
                 │ 校验 → 过滤异常值 → 抽样 → 按网络状况决定时机
                 ▼
 ┌────────────────────────────────────────────────────────────┐
 │ 上报层    1x1 gif(GET) / POST / sendBeacon                 │
 └───────────────┬────────────────────────────────────────────┘
                 ▼
 ┌────────────────────────────────────────────────────────────┐
 │ 接入层    Node.js 服务,解参数、校验站点 id、写本地日志       │
 └───────────────┬────────────────────────────────────────────┘
                 ▼
 ┌──────────────────────────┐     ┌──────────────────────────┐
 │ 清洗计算                 │────▶│ 存储                      │
 │ Kafka / Logstash 接数据  │     │ ES(明细查询)            │
 │ Spark 离线 + Flink 实时  │     │ Hive / MongoDB(聚合结果)│
 └──────────────────────────┘     └──────────┬───────────────┘
                 ┌──────────────────────────┬─┘
                 ▼                          ▼
        ┌──────────────────┐      ┌────────────────────────┐
        │ 告警              │      │ 看板                    │
        │ 定时任务扫规则表  │      │ 大盘页 + 详情页         │
        │ 企微 / 邮件 / 短信│      │ React + AntdPro + AntV  │
        └──────────────────┘      └────────────────────────┘
@前端进阶之旅: 代码已经复制到剪贴板

这五层的落地顺序,我的建议是这样排:

  • Step 1:先定指标口径。首屏算到哪一刻、白屏用哪两个时间点相减、卡顿的阈值是多少,这些不定死,后面所有数据都没法横向比。
  • Step 2:写采集 SDK,先只做错误捕获这一条线。错误是最容易看到收益的,接上去当天就能捞到线上问题。
  • Step 3:搭接入层。一个 Node 服务 + 落本地日志就够了,先别想着上大数据。
  • Step 4:接存储和查询。日志用 Logstash 送进 ES,配个 Kibana 或者 es-head 就能开始查。
  • Step 5:补性能采集。等错误这条线跑顺了再加性能,因为性能指标的口径争议远比错误多。
  • Step 6:最后做告警和看板。有了稳定的数据才谈得上定阈值。

我一开始也是反过来做的,先花两周做了一个很漂亮的大盘,结果数据口径改了三次,图表全部返工。回到这个顺序上,先有数据,再有展示,返工成本会低很多。

后面的章节就按这条链路一层一层往下拆。

# 二、性能优化方法论

在动手写采集代码之前,得先想清楚一件事:我们到底在优化什么。性能不是一个数,它是一串可以拆开的时间段,只有拆开了才知道该在哪一段下手。

前端性能优化方法论整体框架

首屏时间可以拆分为白屏时间、数据接口响应时间、图片加载资源等

这句话是整篇的地基。用户抱怨「慢」的时候,慢在哪一段是完全不同的问题:白屏长通常是 DNS、连接和首字节的问题;接口响应慢要找后端;图片加载慢那是资源体积和 CDN 的事。拿不到分段数据,优化就只能靠猜。

性能指标与优化手段的对应关系

性能优化方法论的落地路径

这两张图把「指标 → 归因 → 手段」串了起来。看图的时候重点不是记住每个格子,而是记住这个链条本身:任何一个优化动作,都应该能回答「它改善的是哪个指标的哪一段」。答不上来的优化,多半是在做无用功。

顺着上面聊,指标从哪儿来?这就到采集环节了。

# 三、指标采集:首屏时间指标采集具体办法

首屏时间(FMP,First Meaningful Paint)是所有性能指标里最难算准的一个,因为「有意义的内容出现了」这句话本身就没有统一定义。业界的做法分两派,手动打点和自动化采集,各有各的问题。

# 手动采集办法及优缺点

所谓手动采集,一般是通过埋点的方式进行,比如在页面开始位置打上 FMP.Start(),在首屏结束位置打上 FMP.End(),利用 FMP.End()-FMP.Start() 获取到首屏时间。

听起来很直观,做起来全是麻烦。手动采集的统计结果并不精确,因为它依赖于人,每个人对首屏的理解有偏差,经常打错或者忘记打点。更麻烦的是页面改版之后,那个 FMP.End() 常常还留在原来的组件里,数据看着挺稳定,其实早就量错了地方。

这个我踩过,一个列表页改成了骨架屏方案,结算点还挂在旧的 loading 隐藏逻辑上,数据一夜之间「优化」了 400ms,实际用户体验一点没变。

# 自动化采集优势及办法

所谓自动化采集,即引入一段通用的代码来做首屏时间自动化采集,引入过程中,除了必要的配置不需要做其他事情。

自动化采集的好处是独立性更强,接入过程更自动化。具体的自动化采集代码,可以由一个公共团队来开发,试点后,推广到各个业务团队。而且统计结果更标准化,同一段统计代码,标准更统一,业务侧同学也更认可这个统计结果。

标准统一这一点特别重要。监控数据的价值不在绝对值,而在可比较:A 页面 1.2s、B 页面 2.8s,这个对比成立的前提是两边用同一把尺子。手动埋点做不到这件事。

当然,它也有缺点,最明显的是,有些个性化需求无法满足,毕竟在工作中,总会有一些特殊业务场景。所以,采用自动化采集方案必须做一些取舍。我的取舍是:主指标一律走自动化,个别页面确实需要精确到某个业务节点的,再额外开一个自定义埋点,两套数据分开存,不要混在一起算平均。

# 单页面(SPA)应用业务下的采集办法

SPA 页面因为无法基于 DOMContentLoaded 做首屏指标采集,可以使用 MutationObserver 采集首屏时间。

MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。

简单来说, 使用 MutationObserver 能监控页面信息的变化,当页面 body 变化最剧烈的时候,我们拿到的时间数据,就是首屏时间。

首先,在用户进入页面时,我们可以使用 MutationObserver 监控 DOM 元素 (Document Object Model,文档对象模型)。当 DOM 元素发生变化时,程序会标记变化的元素,记录时间点和分数,存储到数组中。数据的格式类似于 [200ms,18.5]。

为了提升计算的效率,我们认为首屏指标采集到某些条件时,首屏渲染已经结束,我们需要考虑首屏采集终止的条件,即计算时间超过 30 秒还没有结束;计算了 4 轮且 1 秒内分数不再变化;计算了 9 次且分数不再变化。

接下来,设定元素权重计算分数。

递归遍历 DOM 元素及其子元素,根据子元素所在层数设定元素权重,比如第一层元素权重是 1,当它被渲染时得 1 分,每增加一层权重增加 0.5,比如第五层元素权重是 3.5,渲染时给出对应分数。

为什么需要权重呢?

因为页面中每个 DOM 元素对于首屏的意义是不同的,越往内层越接近真实的首屏内容,如图片和文字,越往外层越接近 body 等框架层。

最后,根据前面的得分,计算元素的分数变化率,获取变化率最大点对应的分数。然后找到该分数对应的时间,即为首屏时间。

这套打分法的思路,说到底是用 DOM 结构的复杂度变化来近似「内容出现了」这件事。它不完美,但比人肉打点稳定得多。

下面这段是分数部分的核心计算逻辑,做三件事:把 SCRIPT、STYLE、META、HEAD 这类不产生视觉的标签排除掉;用 getBoundingClientRect().top < WH 判断元素是否在首屏可视范围内,超出的直接返回 0 分;每往下一层,权重加 0.5。

function calculateScore(el, tiers, parentScore) {
    let score = 0;
    const tagName = el.tagName;
    if ("SCRIPT" !== tagName && "STYLE" !== tagName && "META" !== tagName && "HEAD" !== tagName) {
      const childrenLen = el.children ? el.children.length : 0;
      if (childrenLen > 0) for (let childs = el.children, len = childrenLen - 1; len >= 0; len--) {
        score += calculateScore(childs[len], tiers + 1, score > 0);
      }
      if (score <= 0 && !parentScore) {
        if (!(el.getBoundingClientRect && el.getBoundingClientRect().top < WH)) return 0;
      }
      score += 1 + .5 * tiers;
    }
    return score;
  }
@前端进阶之旅: 代码已经复制到剪贴板

这里有个细节容易被忽略:parentScore 这个参数是用来做剪枝的。如果父节点已经拿到了分数,子节点即便超出可视区也不再单独判断,避免整棵子树被误判成 0 分。

fe
  • 一、先把整套系统的骨架摆出来
  • 二、性能优化方法论
  • 三、指标采集:首屏时间指标采集具体办法
    • 手动采集办法及优缺点
    • 自动化采集优势及办法
    • 单页面(SPA)应用业务下的采集办法
  • 四、指标采集:白屏、卡顿、网络环境指标采集方法
    • 白屏指标采集
    • 卡顿指标采集
  • 五、工具实践:性能 SDK 及上报策略设计
    • SDK 接入设计
    • SDK 运行设计
    • 日志数据过滤
    • 数据抽样策略
    • 上报机制选择
  • 六、平台实践:如何从 0 到 1 搭建前端性能平台
    • 性能数据处理后台
    • 前端数据可视化展示前台
  • 七、诊断清单:如何实现监控预警并进行问题诊断
    • 监控预警
    • 问题诊断
  • 八、优化手段:首屏秒开的 4 重保障
    • 懒加载
    • 缓存
    • 接口缓存
    • 静态资源缓存
    • 离线化
    • 并行化
  • 九、优化手段:白屏 300ms 和界面流畅优化技巧
    • 白屏优化
    • DNS 查询优化
    • 首字符展示优化
    • 卡顿治理
    • 浏览器的主线程与合成线程调度不合理
    • 计算耗时操作
  • 十、JS SDK 设计
    • Performance
    • 错误监控
    • 数据处理和展示
  • 十一、前端监控系统实战
    • 没有前端监控下的痛点有哪些
    • 手动搭建一个前端监控系统
    • JS SDK 功能设计
    • 服务端接收数据,并记录日志
    • 数据收集与过滤
    • 数据存储(ES)提供分析和查询能力
    • 告警配置
  • 十二、上线前的 checklist
  • 总结
  • 参考

← 微前端实战总结,从 single-spa 到 qiankun 的原理拆解小程序插件总结 从创建到发布的完整流程 →