Skip to content

Latest commit

 

History

History
208 lines (133 loc) · 8.95 KB

前端监控.md

File metadata and controls

208 lines (133 loc) · 8.95 KB

数据监控

Web 数据监控可以分为两大类:

  • 合成监控(非侵入):合成监控是采用 Web 浏览器模拟器来加载网页,通过模拟终端用户可能的操作来采集对应的性能指标,最后输出一个网站性能报告。
  • 真是用户监控(侵入式):真实用户监控是一种被动监控技术,是一种应用服务,被监控的 Web 应用通过 SDK 等方式接入该服务,将真实的用户访问、交互等性能指标数据收集上报、通过数据清洗加工后形成性能分析报表。
类型 优点 缺点 示例
非侵入式 指标齐全、客户端主动监测、竞品监控 无法知道性能影响用户数、采样少容易失真、无法监控复杂应用与细分功能,例如:没法考虑到登录的情况,对于需要登录的页面就无法监控到 Lighthouse、WebPageTest
侵入式 真实海量用户数据、能监控复杂应用与业务功能、用户点击与区域渲染 需插入脚本统计、网络指标不全、无法监控竞品 OneAPM、Datadog

打造自己的监控系统

虽然已经有了很多优秀的第三方监控系统,但是有时候我们也会面对打造自己监控系统的需求,可能是因为成本、安全、灵活性等等。

数据监控主要有以下几步:

  1. 数据收集与上报
  2. 数据存储与处理
  3. 数据聚合和分析

收集与上报

数据收集

数据收集主要分为基本性能数据收集、程序异常数据收集、以及用户自定义埋点数据收集。

基本性能数据

将 performance.navagation 和 performance.timing 中的所有点都上报。

例如:白屏时间,以及加载总时长等。

关于性能指标的详细介绍可以查看这篇文章

程序异常数据

前端异常主要分为以下几类:

  1. 运行时错误
  2. 加载时错误
  3. 网络请求错误
  4. Script error

一、先说说运行时错误

  • SyntaxError(语法错误)
  • ReferenceError(引用错误)
  • TypeError(类型错误)
  • RangeError(范围越界错误)
  • URIError(URI不正确)

以上错误中,SyntaxError 无法捕获,一旦发生整个程序将无法运行,不过一般编译时就会发现。其他几种均可通过 window.onerror 或 try...catch 捕获。

  • setTimeout
  • setInterval
  • requestAnimationFrame

以上异步调用,使用 try...catch 捕获无法捕获,可以通过 window.onerror 进行捕获。

  • Promise
  • async...await

ES6 中的 Promise 无法使用 window.onerror 和 try...catch 捕获,但是 async...await 可以。

Promise 的错误我们可以通过监听全局的 unhandledrejection 事件进行捕获。

二、再说说加载时错误

资源加载不止 js,还有 css、img、font 等等,监听这类错误,方法有多种。

  • 监听资源本身的 error 事件,比如 image 对象的 onerror 事件。
  • 监听 window 的 error 事件,这里需要注意需要使用 addEventListener 方式,并且设置为捕获模式。

三、接着说说网络请求错误

  • window.XMLHttpRequest
  • window.fetch
  • window.WebSocket

处理方式就是重写这些方法,进行一层包装。

四、最后说说 Script error 错误

如果是刚开始关注异常监控的同学,一定会遇到 Script error 这种错误。

这类错误一般就是说明发生错误的脚本跨域了,出于安全问题浏览器自动屏蔽了错误信息,只给出 Script error 提示。

解决方式有两种:

  1. 为页面上 script 标签添加 crossorigin 属性,同时服务端响应头设置 Access-Control-Allow-Origin,如果存在缓存问题,响应头可以加上 Vary: Origin
  2. 通过 try...catch 捕获,然后上报。

何时上报

数据上报有一个比较值得考虑的事情,那就是如何上报数据。

主要分为两种:

  • 实时上报
  • 离线上报

实时上报

实时上报的意思是页面发生了异常,立即调用接口将数据发送到服务端。

如果应用的用户量较大,我们可以进行抽样上报。可以从后端动态获取一个上报比率,然后进行抽样,这样可以减轻服务器压力。

离线上报

离线上报的意思是页面发生了异常,则将异常信息缓存在本地,等到用户主动反馈异常问题时,再将数据上报。

这种方式对于网速较差或者日志量较大的情况,很有作用。

如何上报

一般会想到用 ajax 或者 fetch 来做,但是它们有两个主要的问题:

  1. 跨域问题:一般日志服务器和业务服务器是分开的,所以不符合同域条件
  2. 请求中断:浏览器通常会忽略在 unload 事件处理器中产生的异步请求

有一些其它方法可以帮助我们避免上述问题,但是由于兼容性问题,我们需要封装一个方法。当不满足条件时,我们需要做降级处理。

除了 ajax 以外我们还可以采用:

  1. 动态创建 image 标签
  2. 使用 navigator.sendBeacon 方法

Google 开发者推荐的上报方式,见下图。

log report

存储与处理

一个用户访问,可能会上报几十条数据,每条数据都是多维度的。即:当前访问时间、平台、网络、ip 等。

这些一条条的数据都会被存储到数据库中,然后通过数据分析与聚合,提炼出有意义的数据。

例如:某日所有用户的平均访问时长、pv 等。

聚合和分析

数据统计分析的方法:平均值统计法、百分位数统计法、样本分布统计法。

数据最终可以使用 Echarts 这类图表来展示,便于观察与分析。

source map

如今js文件一般经过工具进行了编译、压缩处理,线上的js错误行和列不是源文件的行和列。如果做错误日志的分析与呈现,不得不面对一个问题,编译后的代码的错误位置如何映射到源文件的位置。

其实,在进行编译和压缩处理时,我们可以同时生成一份 source-map 文件,来映射源文件和压缩文件的位置对应关系。

如果直接没有接触过 source-map,可以先阅读阮一峰的一篇文章《JavaScript Source Map 详解》

借助一款工具 source-map,我们可以根据压缩后文件的行与列找到源文件的位置。

简单示例:

var fs = require("fs");
var path = require("path");
var sourceMap = require("source-map");

// 要解析的map文件路径./test/vendor.8b1e40e47e1cc4a3533b.js.map
var GENERATED_FILE = path.join(
  ".",
  "test",
  "vendor.8b1e40e47e1cc4a3533b.js.map"
);
// 读取map文件,实际就是一个json文件
var rawSourceMap = fs.readFileSync(GENERATED_FILE).toString();
// 通过sourceMap库转换为sourceMapConsumer对象
var consumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
// 传入要查找的行列数,查找到压缩前的源文件及行列数
var sm = consumer.originalPositionFor({
  line: 2, // 压缩后的行数
  column: 100086, // 压缩后的列数
});
// 压缩前的所有源文件列表
var sources = consumer.sources;
// 根据查到的source,到源文件列表中查找索引位置
var smIndex = sources.indexOf(sm.source);
// 到源码列表中查到源代码
var smContent = consumer.sourcesContent[smIndex];
// 将源代码串按"行结束标记"拆分为数组形式
const rawLines = smContent.split(/\r?\n/g);
// 输出源码行,因为数组索引从0开始,故行数需要-1
console.log(rawLines[sm.line - 1]);

最后输出行时,可以把错误行前后的几行代码也保留,便于定位。

rrweb

rrweb 全称 record and replay the web,简单来讲就是记录和回放用户操作。

有时候,我们仅仅靠错误日志很难重现错误。我们还需要知道用户是如何操作的,因为不同的操作,可能会产生不同的效果。

借助 rrweb 工具,我们可以轻松的记录用户的每一步操作,包括鼠标的移动,按钮点击的顺序等等。


参考: