【错误监控】Vue 中利用 errorhandler 错误上报二三事

Allan91

前言

在错误监控中,常用到的方法有:

  • 全局捕获 Promise 的未处理异常 unhandledrejection 事件
  • window.onerror 全局捕获错误事件

对于 Vue.js 的错误上报需要用其提供的 Vue.config.errorhandler 方法,但是错误一旦被这个方法捕获,就不会外抛到在控制台。如果需要自己手写一个错误监控,则理论上我们仅仅将错误捕获并上报,但最好不要阻止错误在控制台中展示,所以本文来分析下当中的二三事。

如有兴趣,或者您正在为自己的项目写错误上报的插件,可以一起来分析看看。本文我们将从错误监控的视角来分析和使用 errorhandler

vue 项目错误正常展示

示例代码如下:

export default {
    created() {
        let a = null;
        if(a.length > 1) {
            // ...
        }
    }
};

正常情况下,上述代码会报错:image.png

vue 项目错误捕获以及当中的缺陷

继上面后,我们尝试通过 Vue.config.errorHander 捕获:

Vue.config.errorHandler = (err, vm, info) => {
    console.log('进来啦~');
}

export default {
    created() {
        let a = null;
        if(a.length > 1) {
            // ...
        }
    }
};

然后控制台就不会对外抛错:
image.png

那么在错误监控系统中,理论上我们只去捕获报错而不去拦截报错,那么要怎么做才能把错误外抛到控制台呢?

简单点,就加个console.error(err)

Vue.config.errorHandler = (err, vm, info) => {
    console.log('进来啦~');
    console.error(err);~~~~
}

export default {
    created() {
        let a = null;
        if(a.length > 1) {
            // ...
        }
    }
};

控制台将会得到下面截图:
image.png

那么这样就可以了,错误的上报我们可以再Vue.config.errorHandler 中去做,完了再去把 Vue 中的这个“短板”补上,让错误继续正常抛出来。那么此时错误上报的函数(比如这函数是是captureError())应该这么去调用:

Vue.config.errorHandler = (err, vm, info) => {
    console.log('进来啦~');
    // 错误上报到收集报错的平台
    captureError(err);
}

export default {
    created() {
        let a = null;
        if(a.length > 1) {
            // ...
        }
    }
};

errorHandler 的源码分析

为了弄清楚 Vue 中对于报错是怎么处理的,我们来看看源码(注释也顺便写了):

function globalHandleError (err, vm, info) {
  // 获取全局配置,判断是否设置处理函数,默认undefined
  // 已配置
  if (config.errorHandler) {
    try {
      // 执行 errorHandler
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      // \# 如果开发者在errorHandler函数中手动抛出同样错误信息throw err  \# 判断err信息是否相等,避免log两次
      if (e !== err) {
        logError(e, null, 'config.errorHandler')
      }
    }
  }
  // 没有配置,常规输出
  logError(err, vm, info)
}

function logError (err, vm, info) {
  if (process.env.NODE_ENV !== 'production') {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }
  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console !== 'undefined') {
    console.error(err)
  } else {
    throw err
  }
}

源码中解读 Bugsnag-js 和 sentry 中如何处理的?

先来看 bugsnag-js 中的做法:

module.exports = {
  name: 'vue',
  init: (client, Vue = window.Vue) => {
    if (!Vue) throw new Error('cannot find Vue')
    
    // 思考为什么这样做
    const prev = Vue.config.errorHandler

    const handler = (err, vm, info) => {
      const handledState = { severity: 'error', unhandled: true, severityReason: { type: 'unhandledException' } }
      const report = new client.BugsnagReport(err.name, err.message, client.BugsnagReport.getStacktrace(err), handledState, err)

      report.updateMetaData('vue', {
        errorInfo: info,
        component: vm ? formatComponentName(vm, true) : undefined,
        props: vm ? vm.$options.propsData : undefined
      })

      client.notify(report)
      if (typeof console !== 'undefined' && typeof console.error === 'function') console.error(err)

      if (typeof prev === 'function') prev.call(this, err, vm, info)
    }
    
    // 思考为什么这样做
    Vue.config.errorHandler = handler
    return null
  }
}

再来看 sentry 中的做法:

function vuePlugin(Raven, Vue) {
  Vue = Vue || window.Vue;

  // quit if Vue isn't on the page
  if (!Vue || !Vue.config) return;
  
  // 为什么这么做?
  var _oldOnError = Vue.config.errorHandler;
  Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
    
    // ...

    // 上报
    Raven.captureException(error, {
      extra: metaData
    });

    if (typeof _oldOnError === 'function') {
      // 为什么这么做?
      _oldOnError.call(this, error, vm, info);
    }
  };
}

module.exports = vuePlugin;
上述 「为什么这样做」:AOP 切面编程,保留原始方法的纯净。

以上。

阅读 9.5k
2.1k 声望
2.6k 粉丝
0 条评论
2.1k 声望
2.6k 粉丝
文章目录
宣传栏