前端监控之异常采集

623 阅读5分钟

决策源于数据,数据源于采集。 一个项目上线后线上运行情况,用户使用情况,pv/uv,设备分布情况,用户城市分布,线上异常情况等等信息都需要知晓,便于产品及运营同学进行下一步决策,同时也便于开发同学及时发现线上代码异常等问题。对于开发来讲,异常监控便是其中最重要的,今天就先来聊聊前端代码异常的数据采集。

image.png

如何捕获异常

JS异常的异常的特点是即使出现异常,一般不会导致浏览器崩溃,最多只是当前任务被迫终止。比如一个页面多个按钮,其中一个按钮报错了,其他按钮还可以正常使用。又或者一部分代码报错,其他代码还能依旧运行。如下:

image.png

image.png

如果用户不打开控制台,就根本不会知道第一部分的代码报错了。那么错误该如何采集呢。

try-catch

最容易想到的便是try-catch语句:

image.png

image.png

由上可以看见  被try-catch语句包裹的错误会被捕获,并打印出来。但是异步代码块(如setTimeout)内部的错误不会被捕获。其实这里对于一个插件或者第三方包而言,try-catche内部自己捕获的 就是自己捕获了,第三方包无法获得,因为从代码运行角度来看,这个错误已经被内部处理了,不算是代码异常了。 不过,对于异步的那个错误,我们是可以处理的,请看下面。

window.onerror

window.onerror 最大的好处就是可以同步任务还是异步任务都可捕获。

image.png

image.png

这里会发现 虽然异常被捕获了,但是错误还是会出现在控制台。这是因为window.onerror需要一个返回值,添加上 return true即可。

image.png

image.png 如图,错误消失。

window.addEventListener

window.onerror固好,也有其办不到的事,如网络异常。

<img src="https://ctrip.com/zhangxuefeng.png">

上面这张图片地址为404,控制台报错如下:

image.png

这个时候就需要请出window.addEventListener了。

image.png

image.png

对于promise以及async/await的异常捕获,window.addEventListener也可以处理,只不过要注意的是,这个时候监听的不是error了,而是unhandledrejection,如下:

window.addEventListener('unhandledrejection',function(){...},true)

React项目异常采集

react官方给我们提供了错误处理的方案:

image.png

  1. error —— 抛出的错误。
  2. info —— 带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息

image.png

需要注意的是,React 的开发和生产构建版本在 componentDidCatch() 的方式上有轻微差别。

  • 在开发模式下,错误会冒泡至 window,这意味着任何 window.onerror 或 window.addEventListener('error', callback) 会中断这些已经被 componentDidCatch() 捕获的错误。

  • 相反,在生产模式下,错误不会冒泡,这意味着任何根错误处理器只会接受那些没有显式地被 componentDidCatch() 捕获的错误。

为了对错误做出相应的降级处理,static getDerivedStateFromError()生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  // 更新 state 使下一次渲染可以显降级 UI
  static getDerivedStateFromError(error) {    
      return { hasError: true };
  }
  render() {
    // 渲染任何自定义的降级 UI
    if (this.state.hasError) {            
        return   <h1>Something went wrong.</h1>;    
    }
    return this.props.children;
  }
}

getDerivedStateFromError 会在渲染阶段调用,因此不允许出现副作用。如遇此类情况,请改用 componentDidCatch()

小程序错误采集(以微信小程序为例)

小程序相对PC H5页面等相对封闭些,也没有window对象,其基于App()Page()方法来注册页面,发送请求也是封装了一个wx.request。我们要实现全局的错误采集,可以扩展Pagewx.request方法,在自己的小程序里试验了一下,项目结构如图:

image.png

ZPage代替Pagezwx代替wx对象,代码如下:

// ZPage.js

import util from './utils/util';
import zwx from './zwx'

const ZPage = (options)=>{
  const pageData = {
    onLoad: (onloadOptions)=>{
      console.log('在这里统计pv  uv  cid 等信息')
      console.log(zwx)
      options.onLoad.call(this, onloadOptions)
    }
  }

  if(options.data) {
    pageData.data = util.deepCopy(options.data)
    delete options.data
  }

  if(options.onShareAppMessage) {
    try {
      let _onShareAppMessage = options.onShareAppMessage
      pageData.onShareAppMessage = (res) =>{
        let shareData = _onShareAppMessage.call(this, res)
        console.log(shareData)
      }

      delete options.onShareAppMessage
    } catch(err) {

    }
  }

  for(let key in options) {
    if(typeof options[key] == 'function' && 
      pageData[key] == undefined) {
      pageData[key] =  function() {
        try{
          return options[key].call(this, ...arguments)
        }catch(e){
          const errInfo = '发生了错误!页面:' + this.__route__ + ',
          方法:' + key + ',错误信息:' + e.message
          console.log(errInfo)
          // 将信息上报
          //...
        }
      }
    }
  }
  Page(pageData)

}

module.exports = {
  ZPage
}

// zwx.js
let zwx=Object.assign({},wx);

zwx.request = (cfg) =>{
  let _header = cfg.header || {}
  // 处理header
  _header.cookie = 'name=zxf;' + (_header.cookie || '')
  let head = {
    cid: 666,
    auth: 'sss',
    sence: 1024
  }
  let zcfg = JSON.parse(JSON.stringify(cfg))
  zcfg.header = _header
  if(!zcfg.data) {
    zcfg.data = {}
  }
  if(typeof zcfg.data == 'string') {
    // 处理传入参数为字符串的情况
  }

  Object.assign(zcfg.data, {
    head: head
  })
  zcfg.success = (res) =>{
    console.log('请求成功了',res)
    return cfg.success(res)
  }
  zcfg.fail = (err) =>{
    console.log('请求失败了',err)
    console.log('此处记录下失败的请求内容 及返回内容')
    console.log(cfg)
    return cfg.fail(err)
  }
  wx.request(zcfg)
}

module.exports = zwx

使用如下:


// index.js
import {ZPage} from '../../zwx/ZPage'
import zwx from '../../zwx/zwx'
const app = getApp()

ZPage({
  data: {
    name:'zxf'
  },
  onLoad: function(options) {
    console.log(options)
    zwx.request({
      url:'https://baidu.com',
      success: res =>{
        
      },
      fail: err =>{
        
      }
    })
  },
  test() {
    console.log(123)
  },
  buttonTest(e) {
    this.setData({
      name:'zxf222'
    })
    err
  }
})

对页面内所有方法进行了错误监听,对请求进行了错误监听及对请求参数可以进行统一的处理。

image.png

错误上报

错误上报一般采用新建一个图片,src属性上赋值需要上报的数据进行上报。因为这种方式无跨域问题,兼容性,适用性较好。如下:

image.png

image.png

tips

如果当前页面需要采集信息还没上报完毕,页面以及跳转走,比如用户点击按钮,需要上报一些信息(不一定是错误),但是这个按钮是跳转性质的,该如何处理?

  • onunload: 但是可能导致页面卡住或者卡顿
  • navigator.sendBeacon(url, data):使用sendBeacon()方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载。(目前大部分浏览器都成功实现了该功能)