一文搞定前端错误捕获和上报
背景
众所周知,几乎没有一个开发者能够做到开发时 100%没有 Bug,那么一旦我们的产品出了问题,快速定位问题是迫切需要做的事。好在我们在 Web 场景中 Js 运行出现异常不会导致 JS 引擎崩溃,最多只会终止当前执行的任务。然后逐级上抛错误,类似冒泡事件,在遇到最近的一层 catch 时停止上抛,如果中间都没有错误处理的 catch 时,直至 window 对象结束。那么今天就与大家一起探讨一下我们在 Web 场景中的异常错误数据如何收集以及如何上报。
错误类型
想要获取到相对完整的异常错误数据,先要了解在 Web 中常见的异常错误都有哪些。
Js 执行错误
日常执行中主要有同步错误、语法错误、普通异步任务错误、Promise 任务错误、async 任务错误 5 种常见的异常错误。
资源加载错误
主要有图片、script、css、font 等资源的加载错误问题。
错误捕获
try…catch
作为一个优秀的程序员,首先我们能想到的一定是 try…catch,那么我们直接上代码:
因为资源加载标签肯定不能在代码块中执行,因此资源加载错误肯定无法捕获。
基于上图结果,我们可以小结一下 try…catch 的处理能力:
能捕获包裹体内的同步执行错误。
不能捕获语法错误。
不能捕获异步任务错误。
不能捕获 Promise 任务错误。
不能捕获资源加载错误。
window.onerror
我们浏览器在 window 对象上还自带了一个 onerror 的方法
需要额外注意:跨域脚本加载错误只有一个“Script error”,并不能获取到错误信息。可以通过在<script>标签上添加“crossorigin”属性来解决这个问题。
基于上图结果,我们再小结一下 window.onerror 的处理能力:
能捕获所有同步执行错误。
不能捕获语法错误。
能捕获普通异步任务错误。
不能捕获 Promise 任务错误。
不能捕获 async 任务错误。
不能捕获资源加载错误。
window.addEventListener(‘error’)
在 Web 页面上我们可以监听绝大多数事件,当然也包括错误事件,我们从字面意思上浅理解我们可以认为与 onerror 差不多,但是实际上它们俩的表现还是有一点区别,这里我们给出 addEventListener 额外能捕获的错误,其他与 onerror 基本一致:
这里要额外注意的是:如果是在 js 代码中 new Image() 后加载出现的错误是无法捕获的。
相比 window.onerror,通过 window.addEventListener 的方式我们可以捕获资源加载的错误。
window.addEventListener(‘unhandledrejection’)
刚才我们介绍了 3 种常见的错误捕获方式,但都不能捕获 Promise 任务的错误,这里有人会说了,Promise 不是可以自己 catch 吗?是的,但是我相信大多数情况下我们的开发同学可能并不会为每一个 Promise 写一个 catch,或者可能出现漏写的情况。Js 为我们准备了一个“兜底方案”: unhandledrejection 事件监听。它会在 Promise 被 reject(抛错)且没有被 catch 的时候触发。下面上例子:
当然如果我们将没有 catch 的 Promise 放在 async 中去执行,unhandledrejection 事件监听也能捕获到。所以 async 任务错误 unhandledrejection 事件监听也是可以支持捕获的。
题外话:我们可以看到这个事件的名称叫做 unhandledrejection,作为一个英语词法敏锐的程序员,瞬间想到,有没有叫 handledrejection 的事件呢,如果有,我们是不是可以猜测作用刚好是相反呢?还真有!并且正如我们所猜测的,它是在 Promise 的 reject 做了处理(catch)后触发!这里我们就不展开谈论,有兴趣的同学可以研究一下。
回归正题,我们通过这么多例子测试了 4 种捕获错误的方式,总结得到下表:
那么我们观察这个表格,首先可以看到语法错误,4 种方式都不能捕获,但是我们一般认为语法错误不应该在执行阶段才发现,在我们的编译以及测试环节就可以检查出,所以我们不考虑将其捕获。那么其他的异常错误我们发现通过 addEventListener('onerror') + addEventListener('unhandledrejection') 的方式恰好能够覆盖 5 种异常错误的捕获,一起来实现一下:
把 Promise 及 async 任务中的错误捕获后用同步的逻辑抛出即可让 onerror 准确捕获到。
如此,我们就可以将我们 Web 中大部分的异常问题进行准确捕获。
接下来我们看看如何将错误问题上报至我们的服务器进行汇总。
数据上报
XMLHttpRequest
我们想要将数据传回服务器,最通用的方式当然就是 ajax 请求,通过浏览器的 XMLHttpRequest(这里我们不讨论 IE)的 send 方法,发送 post 请求数据给服务端,这里我们不再给出实现。
其缺点也很明显:
有严格的跨域限制、携带 cookie 问题。
上报请求可能会阻塞业务。
请求容易丢失(被浏览器强制 cancel)。
Image
由于浏览器对资源文件的区别对待,为了解决上面的几个问题,我们可以通过创建一个 1x1 大小的图片进行异步加载的方式来上报。图片天然可跨域,又能兼容所有的浏览器,而 js 和 css 等其他资源文件则可能出现安全拦截和跨域加载问题。
但由于是一个 get 请求,上报的数据量在不同的浏览器下上限不一致(2kb-8kb),这就可能出现超出长度限制而无法上报完整数据的情况。因此,图片上报也是一个“不安全”的方式。
SendBeacon
这个方法天生就是为了数据统计而设计的,它解决了 XMLHttpRequest 和图片上报的绝大部分弊端:没有跨域问题、不阻塞业务,甚至能在页面 unload 阶段继续发送数据,完美地解决了普通请求在 unload 阶段被 cancel 导致丢数据的问题,唯一的问题就是 IE 并不支持。
调用方式也非常简单,类似我们发送 post 请求:
这里需要注意的是,sendBeacon 并不像 XMLHttpRequest 一样可以直接指定 Content-Type,且不支持 application/json 等常见格式。data 的数据类型必须是 ArrayBufferView 或 Blob, DOMString 或者 FormData 类型的。这里给出 Blob 类型的示例。
小结
基于以上 3 种上报方式,我们可以基本总结出,上报数据建议优先使用 sendBeacon 的方式,不支持的浏览器(例如 IE)则降级使用图片上报,尽量避免直接使用 XMLHttpRequest 进行上报。
结语
目前你虽然 GrowingIO Web SDK 现在并没有对这些异常错误做完整的收集(因为我们的产品重点不在这),但是我们有需要的用户可以自己实现错误捕获的逻辑并使用 SDK 的埋点方法进行上报。
另外,我们正在 Web SDK 上进行架构演进且即将完成,创新性地提供了客户自定义插件的能力!后续您可以尝试通过 SDK 提供的插件能力,自行开发一个错误收集的插件(甚至是性能采集插件),配合 SDK 原有功能就能完成业务运营数据和开发所需的错误、性能数据的采集!敬请期待!
以上就是我们今天为大家分享的 Web 应用 Js 异常错误收集的内容。
参考文献:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/try...catch
https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalEventHandlers/onerror
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/unhandledrejection_event
https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/sendBeacon
版权声明: 本文为 InfoQ 作者【GrowingIO技术专栏】的原创文章。
原文链接:【http://xie.infoq.cn/article/f2e14a98f9c4124eefb7d29f1】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论