JS前端错误监控捕获以及上报方法详解
let_code 人气:0前端错误捕获方法
前端捕获错误的方法:
try..catch:捕获的异常必须是线程执行进入到try...catch且try...catch未执行完的时候抛出来。
语法异常在语法检查阶段就报错了,线程尚未进入try...catch代码块,所以无法捕获到异常。
try { a. }catch(e) { console.log('catch error:', e) }
不能捕获setTimeout或者Promise中的错误。以下错误都不能捕获。如果想捕获,要将try...catch放入到异步代码内部。
try { new Promise((res, rej) => { rej('promise reject error') // throw new Error('promise throw error') }) } catch (e) { console.log('catch error:', e) } try { setTimeout(() => { throw new Error('setTimeout throw error') }, 0) } catch (e) { console.log('catch error:', e) }
能捕获async 异常
async function fn() { try { let res = await new Promise((res, rej) => { // rej('my reject err') // unhandledrejection 可以处理 throw Error('my throw error') // unhandledrejection 可以处理 }) } catch (err) { console.log('catch err', err) } } fn()
window.onerror:当资源加载失败或无法使用时,会在Window对象触发error
事件,无法捕获promise错误,可以捕获setTimeout错误。
当加载自不同域的脚本中发生语法错误时,浏览器为避免信息泄露的安全风险,语法错误的细节将不会报告给浏览器console中,而是使用"Script error."信息代替。解决办法是为 script 标签添加 crossOrigin 属性,并且服务端配置Access-Control-Alow-Origin:*
unhandledrejection:当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection
事件
不同场景错误处理方式
总结先行: addEventListener('error') + addEventListener('unhandledrejection') 的方式恰好能够覆盖5种异常错误(同步任务,普通异步任务,promise任务,async任务,资源加载)的捕获。
可以将unhandledrejection捕获到的错误throw出来让error进行捕获之后统一上报。
- 跨域资源加载问题:window.addEventListener('error',()=>{}),并且script 标签添加 crossOrigin 属性,并且服务端配置
Access-Control-Alow-Origin
- 定时器内部函数抛出错误:window.onerror或者window.addEventListener('error',()=>{})
- 静态资源加载的异常:window.addEventListener('error')可以捕获,但是window.onerror不能捕获
- 网络请求的异常:axios的响应拦截器
- 线上压缩代码:开启sourceMap
promise:常常配置catchhandler进行处理,没有处理的rejected的promise通过unhandledrejection
// 能触发 unhandledrejection ,因为未显式处理reason Promise.reject('error').then() Promise.reject('error').then(console.log) // 不能触发 unhandledrejection ,因为已处理reason Promise.reject('error').then(console.log, console.log) // 不能触发 unhandledrejection ,因为没处理reason,直接抛出异常 Promise.reject('error')
React捕获错误:错误边界(Error Boundaries)
部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。
错误边界无法捕获以下场景中产生的错误:
- 事件处理(了解更多)
- 异步代码(例如
setTimeout
或requestAnimationFrame
回调函数) - 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
Vue捕获错误:
全局-Vue.config.errorHandler:指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例。
- 从 2.2.0 起,这个钩子也会捕获组件生命周期钩子里的错误。同样的,当这个钩子是
undefined
时,被捕获的错误会通过console.error
输出而避免应用崩溃。 - 从 2.4.0 起,这个钩子也会捕获 Vue 自定义事件处理函数内部的错误了。
- 从 2.6.0 起,这个钩子也会捕获
v-on
DOM 监听器内部抛出的错误。另外,如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数),则来自其 Promise 链的错误也会被处理。 - 错误追踪服务 Sentry 和 Bugsnag 都通过此选项提供了官方支持。
生命周期钩子-errorCaptured:在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false
以阻止该错误继续向上传
传播规则:
- 默认情况下,如果全局的
config.errorHandler
被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报。 - 如果一个组件的 inheritance chain (继承链)或 parent chain (父链)中存在多个
errorCaptured
钩子,则它们将会被相同的错误逐个唤起。 - 如果此
errorCaptured
钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的config.errorHandler
。 - 一个
errorCaptured
钩子能够返回false
以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的errorCaptured
钩子和全局的config.errorHandler
。
错误信息上报
捕获到错误信息后进行上报,对于前端监控很重要。
上报的方式有三种:
ajax进行上报
发现错误的时候上传错误到接口进行存储。
但是存在一些问题:
- 有严格的跨域限制
- 上报请求可能会阻塞业务
- 请求容易丢失(被浏览器强制cancel)
image上报
由于图片天然可跨域,又能兼容所有的浏览器,而js和css等其他资源文件则可能出现安全拦截和跨域加载问题。
let img = new Image() img.src='请求的url'
但由于是一个get请求,上报的数据量在不同的浏览器下上限不一致(2kb-8kb),这就可能出现超出长度限制而无法上报完整数据的情况。因此,图片上报也是一个“不安全”的方式。
sendBeacon
navigator.sendBeacon()
方法可用于通过 HTTP POST 将少量数据 异步 传输到 Web 服务器。
它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术
这个方法主要用于满足统计和诊断代码的需要,这些代码通常尝试在卸载(unload)文档之前向 Web 服务器发送数据。过早的发送数据可能导致错过收集数据的机会。然而,对于开发者来说保证在文档卸载期间发送数据一直是一个困难。因为用户代理通常会忽略在 unload 事件处理器中产生的异步 XMLHttpRequest。
navigator.sendBeacon(url, data);
使用 sendBeacon()
方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能,这意味着:
- 数据发送是可靠的。
- 数据异步传输。
- 不影响下一导航的载入。
加载全部内容