前言
在错误监控中,常用到的方法有:
- 全局捕获
Promise
的未处理异常unhandledrejection
事件 - window.onerror 全局捕获错误事件
对于 Vue.js 的错误上报需要用其提供的 Vue.config.errorhandler
方法,但是错误一旦被这个方法捕获,就不会外抛到在控制台。如果需要自己手写一个错误监控,则理论上我们仅仅将错误捕获并上报,但最好不要阻止错误在控制台中展示,所以本文来分析下当中的二三事。
如有兴趣,或者您正在为自己的项目写错误上报的插件,可以一起来分析看看。本文我们将从错误监控的视角来分析和使用 errorhandler
。
vue 项目错误正常展示
示例代码如下:
export default {
created() {
let a = null;
if(a.length > 1) {
// ...
}
}
};
正常情况下,上述代码会报错:
vue 项目错误捕获以及当中的缺陷
继上面后,我们尝试通过 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) {
// ...
}
}
};
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 切面编程,保留原始方法的纯净。
以上。