在错误监控中,常用到的方法有:
Promise
的未处理异常 unhandledrejection
事件对于 Vue.js 的错误上报需要用其提供的 Vue.config.errorhandler
方法,但是错误一旦被这个方法捕获,就不会外抛到在控制台。如果需要自己手写一个错误监控,则理论上我们仅仅将错误捕获并上报,但最好不要阻止错误在控制台中展示,所以本文来分析下当中的二三事。
如有兴趣,或者您正在为自己的项目写错误上报的插件,可以一起来分析看看。本文我们将从错误监控的视角来分析和使用 errorhandler
。
示例代码如下:
export default { created() { let a = null; if(a.length > 1) { // ... } } };
正常情况下,上述代码会报错:
继上面后,我们尝试通过 Vue.config.errorHander
捕获:
Vue.config.errorHandler = (err, vm, info) => { console.log('进来啦~'); } export default { created() { let a = null; if(a.length > 1) { // ... } } };
然后控制台就不会对外抛错:
那么在错误监控系统中,理论上我们只去捕获报错而不去拦截报错,那么要怎么做才能把错误外抛到控制台呢?
简单点,就加个console.error(err)
:
Vue.config.errorHandler = (err, vm, info) => { console.log('进来啦~'); console.error(err);~~~~ } export default { created() { let a = null; if(a.length > 1) { // ... } } };
控制台将会得到下面截图:
那么这样就可以了,错误的上报我们可以再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) { // ... } } };
为了弄清楚 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 中的做法:
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;
以上。