文章采集api(搭建一套前端监控平台需要考虑的几个问题?|本文)

优采云 发布时间: 2021-11-29 02:05

  文章采集api(搭建一套前端监控平台需要考虑的几个问题?|本文)

  随着业务的快速发展,我们越来越重视生产环境中的问题感知能力。作为离用户最近的一层,前端性能是否可靠、稳定、易用,在很大程度上决定了用户对整个产品的体验和感受。因此,前端的监控不容忽视。

  搭建前端监控平台需要考虑的方面有很多,比如数据采集、埋点模式、数据处理分析、告警、监控平台在具体业务中的应用等。在所有这些环节中,准确、完整、全面的数据采集是一切的前提,也为用户后续精细化操作提供了基础。

  前端技术的飞速发展也给数据带来了变化和挑战采集。传统的人工管理模式已不能满足需求。如何让前端数据采集在新的技术背景下工作更完整、更高效,是本文的重点。

  前端监控数据采集

  在采集数据之前,我们首先要考虑采集是什么样的数据。我们关注两类数据,一类是与用户体验相关的数据,比如首屏时间、文件加载时间、页面性能等;另一个是帮助我们及时感知产品上线后是否有异常,比如资源错误、API响应时间等。具体来说,我们的前端数据采集具体分为:

  路由交换机

  Vue、React、Angular 等前端技术的快速发展,使得单页应用大行其道。我们都知道传统的页面应用使用一些超链接来实现页面切换和跳转,而单页面应用则使用自己的路由系统来管理前端的各个页面切换,比如vue-router、react-router等,跳转时只刷新部分资源,js、css等公共资源只需要加载一次,这就使得传统网页的进出方式只能在第一次打开时记录。单页应用所有后续路由的切换有两种方式,一种是Hash,一种是HTML5推出的History API。

  1. href

  href是页面初始化的第一个入口,这里只需要上报“页面入口”事件即可。

  2. 哈希变化

  哈希路由的一个明显标志是带有“#”。Hash 的优点是兼容性比较好,但问题是 URL 中总有一个“#”,不美观。我们主要是监控URL中的hashchange,捕获具体的hash值进行检测。

  window.addEventListener('hashchange', function() {

    // 上报【进入页面】事件

}, true)

  需要注意的是,在新版本的vue-router中,如果浏览器支持history,即使选择了hash模式,也会先选择history模式。虽然表达式暂时还是#,但实际上是模拟的,所以不要以为你在模式选择hash的时候就会是hash。

  3. 历史 API

  History使用HTML5 History Interface中新增的pushState()和replaceState()方法进行路由切换,是目前主流的非刷新切换路由方式。相比hashchange后面的代码片段只能改#,History API(pushState、replaceState)给了前端完全的自由。

  PopState是浏览器返回事件的回调,但是update路由的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:如果要自动捕捉页面停留时间,只需要计算下一页进入事件触发时上一页的tick时间与当前时间的差值即可。这时候可以举报【离开页面】事件。

  错误

  在前端项目中,由于JavaScript本身是弱类型语言,加上浏览器环境的复杂、网络问题等,容易出现错误。因此,做好网页错误的监控,不断优化代码,提高代码的健壮性是非常重要的。

  JsError的捕获可以帮助我们分析和监控在线问题,与我们在Chrome浏览器的调试工具Console中看到的一致。

  1. window.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错误或者业务没有处理reject信息时,会抛出unhandledrejection,window.onerror和window.addEventListener('error')不会捕捉到这个错误。这里需要一个特殊的窗口。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打包工具来处理此类问题。

  // 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 文件。后面我们要做的就是调用mozilla/source-map库,通过抓到的lineno、colno、url来映射源码,然后我们就可以得到真正的源码错误信息了。

  表现

  性能指标的获取比较简单,只需要在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等,可以根据不同的场景添加。

  _*资源错误的使用场景更多地依赖于其他几个维度,例如:_region、operator等,我们将在后面的页面中详细说明。

  应用程序接口

  在市面上的主流框架(如Axios、jQuery.ajax等)中,基本上所有的API请求都是基于xmlHttpRequest或者fetch,所以捕获全局接口错误的方式是封装xmlHttpRequest或者fetch。在这里,我们的SDK还是采用了上面提到的AOP思想来拦截API。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线