前端异常采集

为什么要做前端代码异常采集?好问题!
为了用户能安心用产品,不至于时不时“卡壳”崩溃。
为了能高效定位线上代码的异常并提供简单提示信息。
为了程序猿同胞们能睡个好觉。

本文完整示例请移步github:FEerrorLog

捕获异常的方法

js捕获异常的方法,两三个而已。

  1. try...catch 优缺点已有很多论述和解决方案,本文的异常采集并未建立在该方法之上,只是少量使用。
  2. window.onerror和方法3类似但不如方法3强大,因此未选用此方法。
  3. window.addEventListener('error',function(){},true),采用此方法。

前端异常包含两部分:
第一部分:window.onerror()能捕获到的异常,当然如果用addEventListener无论冒泡还是捕获阶段也能捕获到该异常。
第二部分:资源加载失败,即<img><script>标签上的onerror,这个异常无法通过冒泡到达window,但是可以在捕获阶段拿到,这就是为什么要将addEventListener第三个参数置成true了。

注意:为保证该异常采集脚本能执行到,不被先行执行的脚本里面的报错阻断,该脚本要放到最前面。

可能的异常及采集方案

  1. 资源加载失败,样式、图片、脚本文件的请求异常,比如js加载404了
  2. js脚本异常,即控制台常见的Error信息
  3. 检测HTML劫持,比如被运营商强行注入标签或脚本
  4. 页面样式丢失,CSS 展现异常

1. 资源加载失败

http异常用js几乎抓不到有用异常信息,但是404异常可以进行简单处理,此时是不会执行onerror的回调函数的。因为在addEventListener捕获到的异常信息中你可以发现,对应于onerror的五个回调参数根本不存在了,但是addEventListener中除这五个外,还有其他可以用的key,如果想获取加载失败的资源是哪个,可以去target中找些有用信息,我使用的是e.target.outerHTML

"HttpError at " + (e.target.baseURI || location.href) + " outerHTML:" + e.target.outerHTML

2. js异常

一般需要采集的信息:

  1. 异常的提示信息,会直接告诉你是什么异常。这是识别一个异常的最重要依据,即e.message中的信息。
  2. JS 文件名:异常发生在那个文件中。是堆栈信息中最顶层的那个文件。即e.filename。
  3. 异常所在行、列:异常的具体位置。行信息各浏览器基本还是一致的,列信息的差别较大,仅供参考。
  4. 堆栈信息:异常信息发生的堆栈,也是函数调用的堆栈信息,每下一层都是上一层的运行环境。即e.error.stack。每一层都包含类型、文件、行、列信息。但是注意堆栈信息可能会比较多,可以根据需要截取上报。safari和firfox的e.error.stack中不包含以上1,2,3的信息,只有堆栈信息,而chrome和IE中都包含,此处需要做兼容处理。
  5. 发生异常的设备信息,可以从window.navigator中选取自己需要的信息,或者直接使用window.navigator.userAgent
  6. 发生异常的时间点,不多说。

js主动抛出异常
js异常除了可以是系统抛出的几类异常,还可以是开发者利用throw关键字主动抛出异常。
值:可以是字符串、数字逻辑值或对象
使用方式:

//利用Error对象或其实例,采集到的异常系统自动为其添加了堆栈信息,和系统抛出异常基本类似
throw new Error('Problem description.')
throw Error('Problem description.') 
//直接利用关键字抛出内容,会完全复写event.error的内容,不推荐使用。
throw 'Problem description.' 
throw null 

另外:
console.error()和throw new Error()抛出的错误信息是有本质区别的。前者不会阻断js运行,也不会被error事件捕捉到,只是在控制台打印错误信息。

以下方式可以阻止异常信息在控制台中显示,线上可以自行收集异常信息后阻止外人看到控制台报错,开发环境不建议使用。

    window.addEventListener('error', (function(e) {
        console.log("-----errorEvent----", e)
        e.preventDefault()  //这里换成 return false或return true均不行!
    }), true);
    
    window.onerror = function(msg, url, line, col, error) {
        console.log("------errorInfo---",msg, url, line, col, error)
        return true;   //这里用return false不行!
    }

3. 检测html劫持

我选用的方案是保存真实环境中的html信息,并对比原html,检测是否有被篡改。
采集html文档用到的是document.documentElement.outerHTML。但是有一点需要注意,上面已提到,该文件需要放在最前面,所以直接用该方法拿到的可能只有<head>中的html。因此如果想拿到完整的页面信息,需要将采集时间点放到onload以后。

    //所有io操作最好都try...catch一下,这里是防止储存的信息超过localStorage的最大限制。关于最大限制是多少已经有不少人说过了,大家可以选择性看一些,如果有必要可以亲测一下。
    window.addEventListener("load", function() {
        try {
            localStorage.setItem("hawkeyeHtml", document.documentElement.outerHTML);
        } catch (e) {
            //超过限度时,chrome和safari的e.name为'QuotaExceededError',FF的e.name为'NS_ERROR_DOM_QUOTA_REACHED'
            console.error(e)
        }
    })

4. 页面样式丢失

尚未做此数据采集和监控,目前考虑大体思路和html劫持类似,保存截屏图片,用一定算法和正常样式下做对比,超过一定差异值即判定为样式异常。

数据处理方式:

  1. 上报监控服务器:便于统一监控异常数量类型等
    方案:我司仍选择img的属性src进行上报,一是考虑到解析性能,二是考虑到要为多站点服务,方便跨域上报。
  2. html页面打印前端异常:便于快速准确定位某使用环境崩溃原因
    方案:采集到信息储存在localstorage中,注意控制储存的条数。在需要查看异常信息的网站中增加该页面,取出进行简单处理即可。

缺陷处理

  1. 线上代码是混淆压缩的,无行号
    解决方案:线上也用sourceMap来解决,至于sourMap是什么,怎么使用,根据项目不同,可自行google。
  2. 跨域的js,异常信息只有一个"Script error"。虽然js拿不到任何其他异常信息,但是控制台能打印出全部异常信息。所以
    解决方案一:匹配到"Script error",引导开者去控制台查看或直接过滤掉。
    解决方案二:解决跨域问题,分两步。一:静态资源请求需要加多一个Access-Control-Allow-Origin头部。二:<scrip>标签加上crossorigin属性
  3. 跨域的js,获取不到js文件名,会拿到at <anonymous>:1:1酱紫的信息,暂时没找到解决方案

推荐文章:JavaScript Errors Handbook:里面有部分内容已不适用,具体实施请注意验证

posted @ 2017-05-31 11:43  冰凌哒雪花  阅读(1979)  评论(0编辑  收藏  举报