跟着营业的疾速生长,我们对临盆环境下的题目感知才能愈来愈关注。作为间隔用户近来的一层,前端的表现是不是牢靠、稳固、好用,很大程度上决议着用户对悉数产物的体验和感觉。因而,关于前端的
跟着营业的疾速生长,我们对临盆环境下的题目感知才能愈来愈关注。作为间隔用户近来的一层,前端的表现是不是牢靠、稳固、好用,很大程度上决议着用户对悉数产物的体验和感觉。因而,关于前端的监控不容忽视。
搭建一套前端监控平台须要斟酌的方面许多,比方数据网络、埋点情势、数据处置惩罚和剖析、报警以及监控平台在详细营业中的应用等等。在这一切环节中,正确、完整、周全的数据网络是一切的条件,也为后续的用户精细化运营供应基础。
前端手艺的一日千里给数据网络也带来了变化和应战,传统的手工办理情势已不能满足需求。如安在新的手艺背景下让前端数据网络事情越发完美、高效,是本文议论的重点。
前端监控数据网络
在网络数据之前,起首要斟酌网络什么样的数据。我们重点关注两类数据,一类是与用户体验相干的,如首屏时刻、文件加载时刻、页面机能等;别的是协助我们实时感知产物上线后是不是涌现异常的,比方资本毛病、API 相应时刻等。详细来讲,我们对前端的数据网络详细主要分为:
- 路由切换 (href、hashchange、pushState)
- JsError
- 机能 (performance)
- 资本毛病
- API
- 日记上报
路由切换
Vue、React、Angular 等前端手艺的疾速生长使单页面应用盛行。我们都晓得,传统的页面应用是用一些超链接来完成页面切换和跳转的,而单页面应用是应用各自的路由体系来治理前端的每个页面切换,比方 vue-router、react-router 等,跳转时仅革新部分资本 ,js、css 等公共资本只须要加载一次,这就使传统网页进入脱离的体式格局只要第一次翻开能被纪录。单页应用后续一切路由切换的体式格局有两种,一种是 Hash,一种是 HTML5 推出的 History API。
1. href
href 为页面初始化的第一次进入,这里只须要纯真上报「进入页面」事宜即可。
2. hashchange
Hash 路由一个显著的标志是带有「 # 」。Hash 的上风是兼容性更好,但题目在于 URL 中一向存在「 # 」并不雅观。我们主要经由过程监听 URL 中的 hashchange 来捕捉详细的 hash 值举行检测。
window.addEventListener('hashchange', function() {
// 上报【进入页面】事宜
}, true)
须要注重的是,在新版 vue-router 中假如浏览器支撑 history,纵然 mode 挑选 hash 也会优先挑选 history 情势,虽然表现情势临时照样 # 号,但现实上是模仿的,所以万万不要以为自身在 mode 挑选了hash 就肯定会是 hash。
3. History API
History 应用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 要领举行路由切换,是现在主流的无革新切换路由体式格局。与 hashchange 只能转变 # 背面的代码片断比拟,History API (pushState、replaceState) 给了前端完整的自在。
PopState 是浏览器返回事宜的回调,然则更新路由的 pushState、replaceState 并没有回调事宜,因而,还须要分别在 history.pushState() 和 history.replaceState() 要领里处置惩罚 URL 的变化。在这里,我们应用到了一种相似 Java 的 AOP 编程头脑,对 pushState 和 replaceState 举行革新。
AOP (Aspect-oriented programming)即面向切面编程,首倡针对一致类题目举行一致处置惩罚。AOP 的中心头脑是让某个模块能够重用,它采纳横向抽取机制,将功用代码从营业逻辑代码中分离出来,扩大功用而不修正源代码,比拟封装来讲断绝得越发完全。
下面引见我们的详细革新体式格局:
// 第一阶段:我们对原生要领举行包装,挪用前实行 dispatchEvent 了一个一样的事宜
function aop (type) {
var source = window.history[type];
return function () {
var event = new Event(type);
event.arguments = arguments;
window.dispatchEvent(event);
var rewrite = source.apply(this, arguments);
return rewrite;
};
}
// 第二阶段:将 pushState 和 replaceState 举行基于 AOP 头脑的代码注入
window.history.pushState = aop('pushState');
window.history.replaceState = aop('replaceState'); // 变动路由,不会留下历史纪录
// 第三阶段:捕捉pushState 和 replaceState
window.addEventListener('pushState', function() {
// 上报【进入页面】事宜
}, true)
window.addEventListener('replaceState', function() {
// 上报【进入页面】事宜
}, true)
window.history.pushState 现实挪用关联如图:
至此,我们对 pushState、replaceState 革新终了,完成了有效地捕捉路由切换。能够看到,我们在不侵入营业代码的状况下,对 window.history.pushState 举行了扩大,在挪用的同时会主动 dispatchEvent 一个 pushState。
但在这里我们也能看到一个弊病,就是假如 AOP 代办函数发作 JS 毛病,将会阻断后续的挪用关联,使现实的 window.history.pushState 没法被挪用。所以在应用此体式格局的时刻,要对 AOP 代办函数的内容做好完美的 try catch,来防备营业上涌现异常。
*__Tips:想自动捕捉页面停留时刻只须要在下一个进入页面事宜触发时,经由过程上一个页面的办理时刻和当前时刻做差值即可,这时候刻能够上报一个【脱离页面】事宜。
JsError
前端项目中,由于 Javascript 自身是一个弱范例言语,加上浏览器环境的复杂性、网络题目等,很轻易发作毛病。因而做好网页毛病监控,不停优化代码,进步代码健壮性是一项很主要的事情。
JsError 的捕捉能够协助我们剖析和监控线上题目,它与我们在 Chrome 浏览器的调试东西 Console 中看到的内容一致。
1. window.onerror
我们应用 window.onerror 捕捉平常状况下 JS 毛病的异常信息。捕捉 JS 毛病的体式格局有两种,window.onerror 和 window.addEventListener(‘error’)。平常状况下,捕捉 JS 异常不引荐应用 addEventListener(‘error’),主要是由于它没有客栈信息,而且还须要对捕捉到的信息做辨别,由于它会将一切异常信息捕捉到,包含资本加载毛病等。
window.onerror = function (msg, url, lineno, colno, stack) {
// 上报 【js毛病】事宜
}
2. Uncaught (in promise)
当 Promise 内发作 JS 毛病或许 reject 信息未被营业处置惩罚的状况时,会抛出一个 unhandledrejection,而且这个毛病不会被 window.onerror 以及 window.addEventListener(‘error’) 捕捉,这里须要用特地的 window.addEventListener(‘unhandledrejection’) 举行捕捉处置惩罚:
window.addEventListener('unhandledrejection', function (e) {
var reg_url = /\(([^)]*)\)/;
var fileMsg = e.reason.stack.split('\n')[1].match(reg_url)[1];
var fileArr = fileMsg.split(':');
var lineno = fileArr[fileArr.length - 2];
var colno = fileArr[fileArr.length - 1];
var url = fileMsg.slice(0, -lno.length - cno.length - 2);}, true);
var msg = e.reason.message;
// 上报 【js毛病】事宜
}
我们注重到 unhandledrejection 由于继续自 PromiseRejectionEvent,PromiseRejectionEvent 又继续自 Event,所以 msg、url、lineno、colno、stack 以字符串情势放到了 e.reason.stack 中,我们须要剖析出来上述参数来和 onerror 参数对齐,为后续监控平台的目标一致化打下基础。
3. 常见题目
- “Script error.”
假如涌现捕捉的 msg 悉数为 “Script error.” ,题目在于你的 JS 地点和当前网页不在一致个域下。由于我们要常常在线上的版本做静态资本 CDN 化,会致使常接见的页面跟脚本文件来自差别的域名。这时候假如没有举行分外的设置,浏览器出于平安方面的设想就轻易涌现 “Script error.”。我们能够应用现在盛行的 Webpack 打包东西来处置惩罚此类题目。
// webpack config 设置
// 处置惩罚 html 注入 js 增加跨域标识
plugins: [
new HtmlWebpackPlugin({
filename: 'html/index.html',
template: HTML_PATH,
attributes: {
crossorigin: 'anonymous'
}
}),
new HtmlWebpackPluginCrossorigin({
inject: true
})
]
// 处置惩罚按需加载的 js 增加跨域标识
output: {
crossOriginLoading: true
}
大部分场景下,临盆环境中的代码都是经由紧缩兼并的,这使得我们捕捉到的毛病很难映照到详细的源码,为我们处理题目带来很大搅扰,这里扼要提出 2 个处理方案的思绪。
临盆环境我们须要增加 sourceMap 设置,这会致使平安隐患,由于如许外网就能够经由过程 sourceMap 举行源码映照。为了下降风险,我们能够经由过程以下体式格局:
- 将 sourceMap 天生的 .map 文件设置公司内网接见,下降源码平安风险
- 在宣布代码到 CDN 的时刻,将 .map 文件存储到公司内网下
这时候我们已具有了 .map 文件,后续要做的就是经由过程捕捉到的 lineno、colno、url 挪用 mozilla/source-map 库举行源码映照,即可拿到实在的源码毛病信息。
机能
机能目标的猎取相对比较简单,在 onload 以后读取 window.performance 即可,内里包含了机能、内存等信息。这部分内容在许多现有的文章中都有引见,因篇幅所限不在本文做过量睁开,以后在相干主题文章中我们会有相干讨论,感兴趣的朋侪能够增加「马蜂窝手艺」民众号延续关注。
资本毛病
起首我们要明白下资本毛病捕捉的应用场景,更多的是感知 DNS 挟制 及 CDN 节点异常等,详细体式格局以下:
window.addEventListener('error', function (e) {
var target = e.target || e.srcElement;
if (target instanceof HTMLScriptElement) {
// 上报 【资本毛病】事宜
}
}, true)
这里只做基础演示,现实环境中我们会体贴更多的 Element 毛病,如 css、img、woff 等,人人能够依据差别的场景自行增加。
*资本毛病的应用场景更多依靠其他几个维度,如:__地区、运营商等,后续的篇幅中我们会详细解说。
API
市面上主流的框架(如 Axios、jQuery.ajax 等)中,基础上一切的 API 要求都是基于xmlHttpRequest 或许 fetch,所以捕捉全局接口毛病的体式格局就是封装 xmlHttpRequest 或许 fetch。这里,我们的 SDK 依然应用到上文说起的 AOP 头脑,对 API 举行阻拦。
1. XmlHttpRequest
var xhr = window.XMLHttpRequest;
var _open = xhr.prototype.open;
var _send = xhr.prototype.send;
var attr = {};
var openReplacement = function (method, url) {
// 能够存储method、url、时刻办理等信息
attr.duration = new Date().getTime();
_open.apply(this, arguments);
}
var sendReplacement = function () {
methods.addEvent(this, 'readystatechange', function (attr) {
// 能够存储response的status、盘算客户端现实相应时刻
attr.status = this.status;
attr.duration = new Date().getTime() - attr.duration;
// 上报【API】事宜
}.bind(this, , JSON.parse(JSON.stringify(attr))));
_send.apply(this, arguments);
}
xmlhttp.prototype.open = openReplacement;
xmlhttp.prototype.send = sendReplacement;
2. Fetch
须要注重的是,API 阻拦肯定要对 SDK 自身上报的 API 设置好疏忽,不然将会致使轮回上报题目。
var _fetch = window.fetch;
window.fetch = function () {
var attr = {
method: arguments[1].method,
url: arguments[0],
duration: new Date().getTime()
};
return _fetch.apply(this, arguments).then(res => {
attr.status = res.status;
attr.duration = new Date().getTime() - attr.duration;
// 上报【API】事宜
return res;
});
}
日记上报
为了监控前端应用是不是一般运转,一般会在前端网络毛病与机能等数据,最终将这些数据上报到服务端。由于日记上报并非应用的主要功用逻辑,优先级比较低,所以我们在确保日记数据上报更高效的同时,还应该斟酌怎样尽量地削减与其他症结操纵的资本争抢。
1. sendBeacon
navigator.sendBeacon() 要领主要用于满足统计和诊断代码的须要。这些代码一般会在卸载文档之前,尝试经由过程 HTTP 将少许数据异步传输到 Web 服务器。它处理了日记上报在 unload 时成功率很低的题目。我们在埋点时有许多对脱离页面时上报的需求,由于 SendBeacon 是异步的,不会影响当前页到下一个页面的跳转速率,能够更牢靠地保证事宜上报成功率,而且不影响路由切换。
window.navigator.sendBeacon('上报事宜的api', '数据参数')
2. img.src
当浏览器不支撑 navigator.sendBeacon 时,我们能够采纳模仿图片加载的体式格局发送日记上报事宜,且不会存在跨域题目。
var img = new Image();
img.src = API + '?' + '数据参数'
3. 关于 XmlHttpRequest
这里不引荐用 XmlHttpRequest。XHR 虽然支撑异步要求,直接发送数据到后端,然则会遭到跨域和同源的限定。而经由过程日记上报 API 跟营业是不在一个域下的,假如采纳这类情势须要设置 Access-Control-Allow-Origin:* 跨域,异常不方便,而且在 unload 状况下上报发作的丢包率最高。
总结来看,日记上报引荐采纳 sendBeacon -> img.src。在不影响用户路由切换和壅塞用户的状况下丢包率能够控制在 10%-30%,详细要看用户群体对应的环境。
小结
高效的前端数据网络关于搭建前端监控平台来讲异常症结。本文我们分享了马蜂窝在保证数据网络实时、正确、周全等方面的一些思绪和实践。须要提醒人人注重的是,文中涉及到的演示只做了中心代码的症结形貌,不具备临盆应用,我们在现实应用中须要做好兼容及容错。
本文也将作为马蜂窝前端监控平台系列文章的开篇,以后还将连续推出埋点情势、数据处置惩罚和剖析、报警以及监控平台在详细营业中的应用等内容,迎接人人延续关注。
本文作者:王峥,马蜂窝大数据平台前端手艺专家。
(马蜂窝手艺原创内容,转载务必说明出处保留文末二维码图片,感谢合营。)
关注马蜂窝手艺,找到更多你想要的内容