JavaScript错误监控

869 阅读4分钟

原文地址: segmentfault.com/a/119000001…

前端错误分类:js运行时错误、资源加载错误、接口错误

js运行时错误

js运行时的错误一般使用window.onerror捕获,但是有一种特殊情况就是promise被reject并且错误信息没有被处理的时候抛出的错误

一般情况的JS运行时错误

  1. SyntaxError——解析时发生语法错误

    window.onerror捕获不到SyntaxError,一般SyntaxError在构建阶段,甚至本地开发阶段就会被发现。

  2. TypeError——值不是所期待的类型

  3. ReferenceError——引用未声明的变量

  4. RangeError——当一个值不在其允许的范围或者集合中

  • 使用window.onerror和window.addEventListener('error')捕获。
        window.onerror = function(msg, url, lineNo, columnNo, error){
            //处理error信息
            console.log(msg, url, lineNo, columnNo, error)
        }
        
         window.addEventListener('error', function(event){
            console.log('addEventLister error:' , event)
        }, true)
        // true代表在捕获阶段调用,false代表在冒泡阶段捕获。使用true和false都可以
        const a = 1
        
        const a = 1
        a = 2
        console.log(a)

输出结果:

image.png

Uncaught (in promise)

  • 当promise被reject并且错误信息没有被处理的时候,会抛出一个unhandledrejection,并且这个错误不会被window.error以及window.addEventListener('error')捕获,需要专门的window.addEventListener('unhandledrejection')捕获处理
window.addEventListener('unhandledrejection', function(event){
            console.log("_________________________________________________")
            console.log( event)
        })

        new Promise((resolve, reject) => {
           throw new Error('123')
           resolve()
        })

        new Promise((resolve, reject) => {
            reject('321')
        })

        new Promise((resolve, reject) => {
            reject('456')
        }).then((value)=>{

        }, (reason)=> {
            console.log(reason)
        })

结果输出:

image.png

console.error

  • 一些特殊情况下,还需要捕获处理console.error,捕获方式就是重写window.console.error
       var consoleError = window.console.error
        window.console.error = function(){
            console.log(JSON.stringify(arguments));
            consoleError && consoleError.apply(window, arguments)
        }

        console.error('11111')

结果输出:

image.png

特别说明跨域日志

  • 什么是跨域脚本error? 当加载不同域的脚本中发生语法错误时,为了避免消息泄露,语法错误的细节将不会报告,而代之简单的“script error”。在某些浏览器中,通过<script>使用crossorigin属性并要求服务器发送适当的CORS HTTP响应头...

特别说明sourceMap

其他

  • sentry把所有的回调函数使用try catch封装一层
  • vue errorHandler 其原理也是使用try catch封装了nextTick, $emit, watch, data等

资源加载错误

使用window.addEventListener('error')捕获,window.onerror捕获不到资源加载错误。 window.addEventListener('error')捕获到的错误,可以通过target?.src || target?.href区分是资源加载错误还是js运行错误。

当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个Event接口的error事件,这些error事件不会向上冒泡到window,但能被捕获,而window.onerror不能检测捕获。

我们需要设置window.addEventListener()的第三个参数为true,才能捕获到资源加载错误。


 window.addEventListener('error', function(e){
     console.log('window.addEventListener error:', e)
 },true)

接口错误

www.yuque.com/xianjs/eg7d…

一般前端接口都是用jquery的Ajax请求,也有用fetch请求的,以及前端框架自己封装的请求等等。总之他们封装的方法各不相同,但是万变不离其宗,他们都是对浏览器的这个对象window.XMLHttpRequest进行封装,所以我们只要能够监听到这个对象的一些事件,就能够把请求的信息分离出来。

  1. 如何监听Ajax请求 通过监听XMLHttpRequest对象的两个事件loadstart、loadend。
       
       function ajaxEventTrigger(event){
            // 自定义事件
            var ajaxEvent = new CustomEvent(event, {detail: this})
            // dispatchEvent 向一个指定的事件目标派发一个事件;向window派发一个指定的事件
            window.dispatchEvent(ajaxEvent)
        }

        var oldXHR = window.XMLHttpRequest;
        function newXHR(){
            var realXHR = new oldXHR()
            realXHR.addEventListener('loadstart', function(){
                ajaxEventTrigger.call(this, 'ajaxLoadStart')
            }, false)
            realXHR.addEventListener('loadend', function(){
                ajaxEventTrigger.call(this, 'ajaxLoadEnd')
            }, false)
            return realXHR
        }
        

        const timeRecordArray = []
        window.XMLHttpRequest = newXHR
        window.addEventListener('ajaxLoadStart', function(e){
            var tempObj = {
                timeStamp : new Date().getTime(),
                event: e,
                simpleUrl: window.location.href.split('?')[0].replace('#',''),
                uploadFlag: false // uploadFlag=true代表这个请求已经被上传过
            }
            timeRecordArray.push(tempObj)
        })
        

        window.addEventListener("ajaxLoadEnd", function(){
            // ajaxLoadEnd时会修改ajaxLoadStart产生的e,两者影响的是同一个对象e
            for (var i = 0; i < timeRecordArray.length; i ++) {
                if(timeRecordArray[i].uploadFlag === true) continue
                console.log("window.XMLHttpRequest addEventListener loadend: ", timeRecordArray[i].event.detail)
                timeRecordArray[i].uploadFlag  = true
            }
        })
  1. 如何监听fetch请求 通过第一种方法,已经能够监听到大部分的ajax请求。然而,使用fetch请求的人越来越多,因为fetch的链式调用可以让我们摆脱ajax的嵌套地狱,被更多的人所青睐。奇怪的是,使用第一种方式,却无法监听到fetch的请求事件,这是为什么呢?

由于fetch代码是内置在浏览器中,它必然先于监控代码执行,所以我们在添加监听事件的时候,是无法监听fetch里边的XMLHttpRequest对象的。

那怎么办呢,我们需要重写一些fetch的代码,只要在监控代码执行之后,我们重写一些fetch,就可以正常监听使用fetch方式发送的请求了。

如何重写fetch呢? 未完待续