通过关键词采集文章采集api(搭建一套前端监控平台需要考虑的几个问题?|本文)
优采云 发布时间: 2022-04-17 12:32通过关键词采集文章采集api(搭建一套前端监控平台需要考虑的几个问题?|本文)
随着业务的快速发展,我们越来越重视对生产环境问题的感知能力。作为离用户最近的一层,前端性能是否可靠、稳定、好用在很大程度上决定了用户对整个产品的体验和感受。因此,前端的监控也不容忽视。
搭建前端监控平台需要考虑很多方面,比如数据采集、埋藏模式、数据处理分析、告警、监控平台在具体业务中的应用等等。在所有这些环节中、准确、完整、全面的数据采集是一切的前提,也为用户后续的精细化运营提供了依据。
前端技术的飞速发展给数据带来了变化和挑战采集,传统的人工管理模式已经不能满足需求。如何让前端数据采集在新的技术背景下工作更加完整高效是本文的重点。
前端监控数据采集
在采集数据之前,考虑一下采集什么样的数据。我们关注两类数据,一类与用户体验相关,如首屏时间、文件加载时间、页面性能等;另一个是帮助我们及时感知产品上线后是否出现异常,比如资源错误、API响应时间等。具体来说,我们的前端数据采集主要分为:
路由交换机
Vue、React、Angular等前端技术的快速发展,使得单页应用流行起来。我们都知道,传统的页面应用使用一些超链接来实现页面切换和跳转,而单页面应用使用自己的路由系统来管理各个前端页面切换,比如vue-router、react-router等,只有跳转时刷新本地资源,js、css等公共资源只需加载一次,使得传统的网页进出方式只有在第一次打开时才会被记录。在单页应用中切换所有后续路由有两种方式,一种是Hash,另一种是HTML5推出的History API。
1. 链接
href是页面初始化的第一个入口,这里只需要简单的报告“进入页面”事件。
2. 哈希变化
哈希路由的一个明显标志是带有“#”。Hash的优点是兼容性更好,但问题是URL中的“#”不美观。我们主要通过*敏*感*词* URL 中的 hashchange 来捕获具体的 hash 值进行检测。
window.addEventListener('hashchange', function() {
// 上报【进入页面】事件
}, true)
复制代码
需要注意的是,在新版本的vue-router中,如果浏览器支持history,即使模式选择hash,也会先选择history模式。虽然表达式暂时还是#,但实际上是模拟出来的,所以不要以为你在如果模式选择hash,那肯定是hash。
3. 历史 API
History利用HTML5 History接口中新增的pushState()和replaceState()方法进行路由切换,是目前主流的非刷新切换路由方式。与 hashchange 只能更改 # 后面的代码片段相比,History API(pushState、replaceState)给了前端完全的自由。
PopState 是浏览器返回事件的回调,但是更新路由的 pushState 和 replaceState 没有回调事件。因此,URL 更改需要分别在 history.pushState() 和 history.replaceState() 方法中处理。在这里,我们使用类似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:如果要自动捕获页面停留时间,只需要在触发下一页进入事件时,将上一页的时间与当前时间做个差值即可。这时候可以上报【离开页面】事件。
错误
在前端项目中,由于 JavaScript 本身是一种弱类型语言,再加上浏览器环境的复杂性、网络问题等,很容易出现错误。因此,做好网页错误监控,不断优化代码,提高代码的健壮性是非常重要的。
JsError的捕获可以帮助我们分析和监控在线问题,和我们在Chrome浏览器的调试工具Console中看到的一致。
1. 窗口.onerror
我们通常使用 window.onerror 来捕获 JS 错误的异常信息。有两种方法可以捕获 JS 错误,window.onerror 和 window.addEventListener('error')。一般不建议使用 addEventListener('error') 来捕获 JS 异常,主要是它没有堆栈信息,而且还需要区分捕获的信息,因为它会捕获所有的异常信息,包括资源加载错误. 等待。
window.onerror = function (msg, url, lineno, colno, stack) {
// 上报 【js错误】事件
}
复制代码
2. 未捕获(承诺)
当 Promise 发生 JS 错误或者拒绝信息没有被业务处理时,会抛出 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.常见问题
如果抓到的msg都是“Script error.”,问题是你的JS地址和当前网页不在同一个域下。因为我们经常需要对网络版的静态资源进行CDN,会导致经常访问的页面和脚本文件来自不同的域名。这时候如果不进行额外配置,浏览器很容易出现“脚本错误”。出于安全考虑。我们可以利用当前流行的 Webpack bundler 来处理此类问题。
// 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
}
复制代码
大多数场景下,生产环境中的代码都是压缩合并的,这使得我们抓到的错误很难映射到具体的源码中,给我们解决问题带来了很大的麻烦。这里有两个解决方案。想法。
在生产环境中,我们需要添加sourceMap的配置,这样会带来安全隐患,因为这样外网可以通过sourceMap进行source map的映射。为了降低风险,我们可以做到以下几点:
设置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等,大家可以根据不同的场景添加。
_*resource错误的使用场景更多地依赖于其他维度,比如:_region、operator等。我们将在后面的页面中详细讲解。
API
在市面上的主流框架(如axios、jQuery.ajax等)中,基本上所有的API请求都是基于xmlHttpRequest或者fetch,所以捕获全局接口错误的方式就是封装xmlHttpRequest或者fetch。在这里,我们的SDK还是使用了上面提到的AOP思想来拦截API。