关于前端异常监控,你需要知道这些🔥🔥🔥

502 阅读6分钟

由于我比较擅长监控方面的建设,面试的时候也刚好被问到了,自然对于我来说不是问题,现重新整理分享出来,你按照这个能回答上80%左右,面试官印象分有了!

一、为什么要做监控

  1. 前端异常是指在用户使用系统过程中无法得到符合预期结果的情况,不同的异常带来的后果程度不同,轻则引起用户使用不悦,重则导致产品无法使用,使用户丧失对产品的认可。
  2. 为了能够让开发者可以快速的感知系统稳定性结果,异常监控是目前为止最有效的系统反馈机制。

二、异常分类

  • JS运行异常

    • EvalError eval错误
    • RangeError 范围错误
    • ReferenceError 引用错误
    • TypeError 类型错误
    • URIError URI错误
    • SyntaxError 语法错误
    • Error 通用错误
  • 资源加载异常

    • 图片加载异常
    • script加载异常
    • link加载异常
  • promise异常

  • Ajax异常/fetch异常

    • Ajax失败
    • Ajax超时
  • 浏览器奔溃异常

    • 浏览器无法操作,进程卡死
    • 内存泄露
  • 浏览器卡顿异常

    • FPS异常

三、异常采集方式

异常采集方式一般分为两种:全局接口捕获单点异常捕获

JS运行异常

//方法一
window.onerror = function() {}
//方法二
重写console.error();
//方法三
try {
  ...
} catch (e) {
  handleError(e);
}

资源加载异常

window.addEventListener('error', (event)=> {
  // 通过tagName区分不同资源
  let tagName = event.tagName.toLocaleLowerCase();
  if (tagName !== 'img' && tagName !== 'script' && tagName !== 'link') return;
  if (tagName === 'link' && event.target.rel !== 'stylesheet') return;
  // 上报异常信息
  ...
  reportData('resourceError', event)
})

Promise异常

window.addEventListener('unhandledrejection', (event)=> {
  let reason = err.reason;
  // 过滤掉谷歌插件的报错
  if (reason.stack.indexOf('chrome-extension') !== -1) return;
  ...
  reportData(type, {msg: reason})
})

Ajax异常/fetch异常

if (window.XMLHttpRequest) {
  const oldXmlHttpRequestSend = XMLHttpRequest.prototype.send;
  const oldXmlHttpRequestOpen = XMLHttpRequest.prototype.open;
  
  XMLHttpRequest.prototype.open = function (method, url, bool) {
    // 拦截URL
    // 记录请求开始时间 startTime,用于计算Ajax超时
  }
  XMLHttpRequest.prototype.send = funciton (data) {
    // 记录状态码
    if (status < 200 || status > 299) {
      reportData(type, {...})
    }
    // 统计Ajax耗时 endTime - startTime
    ...
  }
}
if ('fetch' in window) {
  const oldFetch = window.fetch;
  window.fetch = function() {
    let args = Array.from(arguments);
    return oldFetch.apply(this, args).then(res => {
      ...
    })
  }
}
  

浏览器奔溃异常

// 浏览器必须支持service worker,sw具有在浏览器奔溃的情况下依旧可以工作的特性
if (navigator.serviceWorker && window.addEventListener && window.postMessage) {
  
  // 步骤
  // 1.页面初始化时创建iframe标签
  // 2.监听iframe的onload事件,初始化监听器
  // 3.监听器中通过postMessage发送事件,心跳检测
  // 4.sw监听postMessage事件,上报异常
  
        var crashHost = 'https://aaa.bbb.cn';
        var crashSrc = crashHost + '/front-end-look/crash';
​
        var connect = function () {
          var iframe = this;
          var HEARTBEAT_INTERVAL = 5 * 1000; // 每五秒发一次心跳
          const base_data = {}
          // 记录上报类型
          base_data['REPORT_TYPE'] = 'crash';
​
          iframe.contentWindow.postMessage({
            type: 'baseData',
            id: '',
            base_data: base_data,
            report_url: 'xxxx'
          }, crashSrc);
​
          (function heartbeat() {
            // 附加信息,如果页面 crash,上报的附加数据
            var memoryData = {};
            if (performance) {
              var memory = performance.memory;
              if (memory) {
                memoryData.memory = {
                  totalJSHeapSize: memory.totalJSHeapSize,
                  usedJSHeapSize: memory.usedJSHeapSize,
                  jsHeapSizeLimit: memory.jsHeapSizeLimit,
                  usedRatio: (memory.usedJSHeapSize / memory.totalJSHeapSize * 100).toFixed(4) + "%"
                }
              }
            }
​
            var exp_data = {};
            exp_data['_EXCEPTION._MESSAGE'] = JSON.stringify(memoryData);
            // 记录上报时间
            exp_data['_BASIC._CLI_TIME'] = new Date().getTime();
​
            iframe.contentWindow.postMessage({
              type: 'heartbeat',
              id: '',
              exp_data: exp_data,
            }, crashSrc);
​
            setTimeout(heartbeat, HEARTBEAT_INTERVAL);
          })();
​
          window.addEventListener('beforeunload', function () {
            iframe.contentWindow.postMessage({
              type: 'unload',
              id: sessionId
            }, crashSrc);
          });
        }
​
        // 由于 sw 只能检测当前域名,所以这里检测当前的资源域名其实是没有意义的。
        // 所以用 iframe 作中转。起到两个作用:
        // 1、可以监听各个业务的崩溃异常,并通过真正的 crash 接口 去上报异常信息。
        // 2、一定程度上,可以避免 sw 重复注册带来的性能损失。
        var appendIframe = function () {
          try {
            if (document.body.querySelector('#crashIframe')) return;
​
            var iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            iframe.src = crashSrc;
            iframe.id = 'crashIframe';
            iframe.addEventListener('load', connect, false);
            document.body.appendChild(iframe);
          } catch (e) {
            console.log(e);
          }
        }
​
        if (document.body) {
          appendIframe();
        } else {
          win.addEventListener('load', appendIframe, false);
        }
}

浏览器卡顿异常

// fps收集器const fpsList = [];
  
function fpsCollect() {
  // 收集1s之内的fps
}
window.requestAnimationFrame(fpsCollect);

四、异常日志缓存

如果要收集用户的行为日志,又要采取一定的技巧,而不能用户每一个操作后,就立即将该行为日志传到服务器,对于具有大量用户同时在线的应用,如果用户一操作就立即上传日志,无异于对日志服务器进行DDOS攻击。因此,我们先将这些日志存储在用户客户端本地,达到一定条件之后,再同时打包上传一组日志。

那么,如何进行前端日志存储呢?我们不可能直接将这些日志用一个变量保存起来,这样会挤爆内存,而且一旦用户进行刷新操作,这些日志就丢失了,因此,我们自然而然想到前端数据持久化方案。

目前,可用的持久化方案可选项也比较多了,主要有:Cookie、localStorage、sessionStorage、IndexedDB、webSQL 等等

通过对比,IndexedDB是最好的选择,它具有容量大、异步的优势,异步的特性保证它不会对界面的渲染产生阻塞。

五、异常上报时机、方式

1、上报时机

上报原则:所有的数据上报不能阻断或者影响用户的正常操作流程!

系统自动上报

  • 发生错误即时上报

    • 通过页面beforeunloadonVisbilityChange60s一次 、创建<img /> 进行上报
    • unload 时机触发,但要配合navigator.sendBeacon

使用 sendBeacon() 方法会使用户代理(浏览器)在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载。

用户手动上报

  • 通过bug反馈入口进行上报反馈

2、上报方式

  • 异步请求上报, 后端提供接口,或者直接发到日志服务器

  • img(gif)请求上报,URL参数带异常信息

(new Image()).src = 'http://yc.xxx.com/report?exp={xxx}'

六、异常告警和通知

对异常进行统计和分析只是基础,而在发现异常时可以推送和告警,甚至做到自动处理,才是一个异常监控系统应该具备的能力。

监控实现

当日志信息进入接入层时,就可以触发监控逻辑。当日志信息中存在较为高级别的异常时,也可以立即出发告警。

触发条件

除了系统开发时配置的默认告警条件,还应该提供给日志管理员可配置的自定义触发条件。

  • 日志内含有什么内容时
  • 日志统计达到什么度、量时
  • 向符合什么条件的用户告警

推送渠道

可选择的途径有很多,例如邮件、短信、微信、电话。

推送频率

针对不同级别的告警,推送的频率也可以进行设定。低风险告警可以以报告的形式一天推送一次,高风险告警10分钟循环推送,直到处理人手动关闭告警开关。

自动报表

对于日志统计信息的推送,可以做到自动生成日报、周报、月报、年报并邮件发送给相关群组。

自动产生bug工单

当异常发生时,系统可以调用工单系统API实现自动生成bug单,工单关闭后反馈给监控系统,形成对异常处理的追踪信息进行记录,在报告中予以展示。

总结

本文介绍了关于前端异常板块常见的异常分类,并且给出了针对不同的异常采用不同的采集方式,为了避免上报数据太过于频繁可选择合适的日志进行缓存。

接着在一定的时机进行上报统计,而不恰当的上报方式可能会直接影响到正常的业务操作,选择的标准是无感知、不影响业务逻辑。

最后,需要及时响应或通知相关人员进行异常处理和反馈,形成监控闭环