前端异常监控
对于前端应用来说, 对异常的监控是很有必要的。异常也有很多种,比如 js 运行时异常,加载静态资源异常,接口请求异常等等。今天就来大概了解一下如何进行异常监控。
JavaScript 运行时异常
try…catch
一般我们写代码时,会使用 try...catch 来捕获异常:
try { |
在 try 代码块中,我们可以通过 throw 手动抛出异常,异常我们可以自定义,catch 代码块的参数是就是我们抛出来的异常。
当我们代码运行时发生错误时,会自动抛出来一个异常,这个异常是一个 Error 对象,会带有 name 和 message 两个标准属性。
现在大部分浏览器都支持了
stack属性,包含错误的完整信息,包括了name、message和发生错误的文件和行号等。
try { |
但是我们并不能使用 try...catch 来做整个 Web 应用的异常监控,因为我们不可能给所有的 js 代码都加上 try...catch。
window.onerror
当 JavaScript 运行时错误(包括语法错误)发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。
因此我们可以使用 window.onerror 来监控运行时的代码异常:
/** |
需要注意的是,当加载自不同域的脚本中发生语法错误时,为避免信息泄露,语法错误的细节将不会报告,而代之简单的”Script error.”。onerror 捕获的错误信息为:
message: "Script error." |
如果需要解决这个问题的话,我们需要做两件事:
- 引用跨域 js 文件时加上
crossorigin属性
<script type="text/javascript" src="http://b.com/b.js" crossorigin></script> |
- 服务器发送相关跨域 js 文件时需要发送适当的 CORS HTTP 响应头(
Access-Control-Allow-Origin)
静态资源异常
当一项资源(如
<img>或<script>)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror() 处理函数。这些 error 事件不会向上冒泡到 window,不过能被单一的 window.addEventListener 捕获。
因此只用 window.onerror 来监测前端项目异常的话,功能还不够完善。但我们可以使用 window.addEventListener('error', function(event) {}, true) 来捕获静态资源异常
window.addEventListener( |
由于 window.addEventListener('error', function(event) {}, true) 也能捕获 js 运行时异常,因此我们使用了 window.onerror 后,为了避免重复上报,在监听静态资源异常时,需要做一些处理:
window.addEventListener( |
未处理的 promise 异常
当 Promise 被 reject 且没有 reject 处理器的时候,会触发
unhandledrejection事件。
也就是说当 promise 被 reject 但是没有进行 catch 处理时,会抛出异常。 但是这个异常不会被 window.onerror 和 window.addEventListener('error', function(event) {}, true) 捕获。
function foo() { |
我们可以通过监听 unhandledrejection 事件来捕获未处理的 promise 异常:
window.addEventListener("unhandledrejection", (event) => { |
网络请求异常
XMLHttpRequest
一般情况下,我们都是基于 XMLHttpRequest 发送网络请求的:
let xhr = new XMLHttpRequest(); |
我们可以通过劫持 XMLHttpRequest 的 open 和 send 方法来进行异常上报:
(function handleXMLHttpRequestException() { |
fetch
当我们使用 fetch 发送网络请求时,可以这样做:
(function handleFetchException() { |
Vue 内部发生的错误
Vue 内部发生的错误会被 Vue 拦截,官方提供了一个处理函数,我们可以在这个函数内部进行异常上报:
Vue.config.errorHandler = function (err, vm, info) { |
React 内部发生的错误
我们可以通过 React16+ 中的 Error Boundaries 特性来处理组件内部的错误:
class ErrorBoundary extends React.Component { |
Error Boundaries 即错误边界,其可以拦截子组件生命周期内的错误,我们可以使用它来保证页面可以正常渲染UI,而不是一片空白。我们可以使用ErrorBoundary 包裹我们的子组件,在 componentDidCatch 函数中进行异常上报。
上报异常
当我们上报异常时,为了更准确的分析定位,我们需要不仅仅要上报捕获到的异常基本信息,还需要上报用户信息、环境信息(操作系统信息、浏览器信息、网络环境、应用版本等)等。
上报异常时我们还需要考虑上报的量和频率问题。有需要的情况下,我们可以先把异常信息存储到 IndexedDB 中,在 Web Worker 线程中控制异常上报。
上报异常时,Ajax 请求本身也有可能会发生异常,一般情况下更推荐使用动态创建 img 标签的形式进行上报。
现在前端代码大部分情况都是经过压缩后发布的,上报的 stack 信息需要还原为源码信息,才能快速定位源码进行修改。因此我们可以发部署时,将 sourcemap 文件上传到监控系统,在监控系统中展示 stack 信息时,利用 sourcemap 文件对 stack 信息进行解码,得到源码中的具体信息。
小结
现在我们算是基本了解了如何进行异常监控,但要实现完整的异常监控功能,还任重而道远。Sentry 是一项非常出色的服务,可以帮助我们进行异常监控,并且是开源的。后面会研究下,如何使用 Sentry 进行异常监控。