前言

我们做前端监控系统时,当我们通过各种各样的手段进行埋点统计、错误收集等等,终于,数据有了,那么我们怎么上传呢?

单独域名上报

一般像这些数据上报使用的域名都会和业务域名分开,或者说会单独使用一台服务器部署运行。

  • 从架构角度看,其实一般公司都会将业务和这些数据分开,比如有一个运营平台,数据平台什么的,比如你有很多个应用,你可以有一个数据中心统一收集这些数据。如果将这些和业务绑在一起,自然就变得很繁重了。
  • 从浏览器的角度上看,很多浏览器对同一域名的请求量有并发限制,单独域名能充分利用这些并发设置。

上报的时机

实时上报

适用于数据上报并发小的情况,实时性要求高

  1. 页面加载完成时:这是获取和上报页面加载性能数据的一个常见时机。例如,可以在 window 的 load 事件中获取页面加载所需的时间,然后上报。
  2. 用户交互时:如果你想了解用户在使用网站或应用时的性能体验,可以在用户的各种交互(如点击按钮、滚动页面等)后获取和上报相关的性能数据。
  3. 错误发生时:当前端代码运行出错时,可以立即获取错误信息并上报,以便尽快发现并修复问题。

定时上报

适用于数据上报并发量大的情况,如一个页面中,需要上报很多条数据,可合并上报,当然要考虑后端服务是否支持合并。

页面刷新和重新刷新

这里主要讨论单页面应用,如果是非单页面应用,可以通过页面关闭时上报。

hash

可以直接监听 hashchange

window.onhashchange = function (event) {
console.log(event);
};
//或者
window.addEventListener("hashchange", function (event) {
console.log(event);
});

history

history 模式主要通过 history.pushState 和 history.replaceState 方法,但浏览器不提供监听事件,那么我们可以手动添加,如下

const patchMethod = function (type) {
const _ori = hisoty[type];
return function () {
const result = _ori.apply(this, arguments);
const event = new Event(type);
event.arguments = arguments;
window.dispatchEvent(e);
return result;
};
};
history.pushState = patchMethod("pushState");
history.replaceState = patchMethod("replaceState");

window.addEventListener("pushState", (e) => {
// 上报
});
window.addEventListener("replaceState", (e) => {
// 上报
});

页面 Tab 显示状态改变

通过监听 visibilitychange 事件,

document.addEventListener("visibilitychange", (e) => {
const isHidden = document.hidden;
if (isHidden) {
// 隐藏
} else {
// 显示
}
});

页面关闭

通过 unload 事件

window.addEventListener("unload", (e) => {
// 上报
});

但需注意,如果使用 XHR,往往需要用同步代码,即阻塞当前页面卸载,可能会带来一些用户体验问题,因此推荐使用navigator.sendBeacon, 异步,不会被浏览器的关闭操作打断

移动端浏览器不可靠

在许多情况下(尤其是移动设备)浏览器不会产生 unloadbeforeunloadpagehide 事件。下面列出了一种不触发上述事件的情况:

  1. 用户加载了网页并与其交互。
  2. 完成浏览后,用户切换到了其他应用程序,而不是关闭选项卡。
  3. 随后,用户通过手机的应用管理器关闭了浏览器应用。

上报的方法

gif

  1. 不存在跨域,浏览器对照片资源加载没有跨域限制,这对单独域名上报很好解决。
  2. 兼容性好,基本所有浏览器都支持 gif 加载
  3. 相比于 png 等,体积更小

有人会觉得,跨域的话,那我服务器设置 CORS 就好啦,第一是 CORS 兼容性没有 gif 那么好,第二 CORS 可能会带来一些安全性问题,第三如果你有很多个应用公用同一个数据上报服务的话,CORS 配置维护麻烦

浏览器 URL 最大长度限制

使用 gif 上报需要考虑一个 url 长度问题,

浏览器 URL 最大长度限制参考:

浏览器 最大长度(字符数)
Internet Explorer 2048
Edge 4035
Firefox 65536
Chrome 8182
Safari 80000
Opera 190000

tip:

  1. URL 上存在中文字符会被浏览器编码,需根据浏览器编码类型将中文转换,因此,不能将中文字符长度作为 1

xhr

传统的接口上报

上述的所有方法都会迫使用户代理延迟卸载文档,并使得下一个导航出现的更晚。或者处理不好会丢失这个数据上报请求,下一个页面对于这种较差的载入表现无能为力。

这就是 sendBeacon() 方法存在的意义。使用 sendBeacon() 方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能,这意味着:

  • 数据发送是可靠的。
  • 数据异步传输。
  • 不影响下一导航的载入。

数据是通过 HTTP POST 请求发送的。

结合三种上报方式

  1. 如果长度小于 2048,使用 gif
  2. 如果大于 2048,并且浏览器支持 sendBeacon,使用 sendBeacon
  3. 使用 XHR
const reportData = (data) => {
if (urlLength < 2048) {
gifReport(data);
} else if (navigator.sendBeacon) {
sendBeaconReport(data);
} else {
xhrReport(data);
}
};

上传优化

上报采集率

如果网页访问量很大,那么因为一个错误上报的信息可能会很多,这时候可通过设置采集率,避免全部上报。

const reportDate = () => {
// 只采集30%
if (Math.randow() < 0.3) sendData();
};

识别流量高峰和低谷时期

我们可以通过一些手段识别当前是流量高峰还是低谷,动态设置上报采样率

短时间滤重

如果用户短时间内一直触发同一个错误,可考虑进行短时间滤重

监控报警

当我们监听到错误数据时,应该进行报警,如短信、群机器人等等。当然,要考虑是否会短时间内同时大量上报,可通过设置一些阀值,如 1 分钟最多报警 10 次,或者说错误达到了一定规模触发一次报警等类似

参考

  1. 侯策—《前端开发核心知识进阶》

  2. URL 长度限制url 参数长度限制皮蛋很白的博客-CSDN 博客