Giter Site home page Giter Site logo

blog's People

Contributors

silencesnow avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

给项目加上错误上报

起因

项目测试程度有限,就会有测不出的问题出现,上一篇我写了一个performance兼容性的问题,使我决定马上把全局错误上报加入到项目中,以下就是过程啦。

写法

全局错误上报可以利用window的error事件,搜到的例子都是写成window.onerror,这是dom0级事件的写法,为了与项目其他事件监听统一,我想写成dom2级的,于是我写成这样:

window.addEventListener("error",function(e){
  /* 上报内容 */
  setTimeout(function(){
    var msg={
      errorMsg:e.error.stack,
      position:'url:'+document.URL+'l:'+e.lineno+',c:'+e.colno,
      name:'{{postReportMsg}}'||'{{postReporInnerMsg}}'||'{{postReportMsgOut}}'||'OUTPAGE',
      us:navigator.userAgent
    };
    var xhr=new XMLHttpRequest();
    xhr.open('POST','/ajax/errorReport',true);
    xhr.setRequestHeader('Content-Type','application/json');
    xhr.send(JSON.stringify(msg));
    xhr.onload=function(e){
      if(this.status==200){
        console.log('error report success',this.responseText)
      }
    }
    xhr.onerror = function(){
      console.log('error report fail');
    };
  },0);
},true);

调试时发现window.onerror和addEventListener二者传入的参数不太一致,不过不影响关键数据,于是我愉快的部署了。

第二天早上一上班,打开后台的统计列表,我惊呆了,一夜间上万条的错误上报是什么鬼!

分析上报内容

值得关注的奇怪报错——TypeError: Cannot read property 'stack' of undefined at 某位置——这个位置指向错误上报的方法,看起来某些报错的error对象没有stack属性,此时可以注意到,window.onerror和window.addEventListener('error')并不相同,引用mdn上的一段话:

  • When a JavaScript runtime error (including syntax errors and exceptions thrown within handlers) occurs, an error event using interface ErrorEvent is fired at window and window.onerror() is invoked (as well as handlers attached by window.addEventListener (not only capturing)).

  • When a resource (such as an img or script) fails to load, an error event using interface Event is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but (at least in Firefox) can be handled with a single capturing window.addEventListener.

原文地址在这里

总结一下是:js报错产生一个ErrorEvent,使用接口'ErrorEvent',触发对象是window,window.onerror和window.addEventListener都会被触发。
而图片或脚本资源加载失败,会产生一个error Event,这个使用接口'Event',触发对象是加载失败的元素,这个事件不冒泡,但是可以捕获。

图片或脚本资源加载失败,最常见的情况就是懒加载图片时,我们写的src="#",这就会触发一个图片加载失败的error Event

由于我部署的window.addEventListener('error',function(e){},true)设为了捕获阶段触发事件监听函数,导致图片加载失败等事件也被搜集了进来,而图片加载失败触发的接口不同于脚本错误,一个是error Event,使用Event接口,一个是ErrorEvent,使用ErrorEvent接口,前者没有error属性,就更不会在error属性上有stack属性了,所以才会报错。

修正和兼容性

是时候看一下完整的参数和兼容性了:原文地址
img

历史原因,window.onerror有5个参数;window.addEventListener('error',function(e){})有一个参数error,该参数有5个属性,对应于onerror的5个参数。

这5个参数兼容性参差不齐,现代浏览器都支持的是前三个,报错信息,报错位置,行号,后两个列号,error对象主要是safari系不支持,然而由于现在js普遍都是打包过的,你会发现,报错的位置行号经常都是1,列号是1万几,丢失列号很难忍受,error对象中也有很有价值的堆栈信息,应该做到尽量多的搜集。参考这张兼容性表格出处的文章,建议的最完善的办法是加try-catch,可以catch到很全面的报错信息,不过如果考虑简单的错误搜集,并且你主要指向移动端的话,也可以只使用window.onerror。

需要注意的是,错误上报方法需要放在头部,js脚本按顺序执行,未执行该方法就出现报错,错误无法被收集到

还有一个tips,普遍推荐的方式都是加一层setTimeout(function(){},0)以实现不阻塞上报。

以下是简单error上报的正确打开方式:

/* 第一种 */
/* 我是头部 */
window.onerror=function(e,url,l,c,error){
  setTimeout(function(){
      var msg={
        errorMsg:e,
        position:'url:'+document.URL+',file:'+url+',l:'+l+',c:'+c+',error:'+error,
        name:页面标识,
        us:navigator.userAgent
      };
      var xhr=new XMLHttpRequest();
      xhr.open('POST','错误上报接口',true);
      xhr.setRequestHeader('Content-Type','application/json');
      xhr.send(JSON.stringify(msg));
      xhr.onload=function(e){
        if(this.status==200){
          console.log('error report success',this.responseText)
        }
      }
      xhr.onerror = function(){
        console.log('error report fail');
      };
    },0)
}
/* 第二种 */
window.addEventListener('error',function(e){
  /* setTimeout方法同上,只不过参数为e.message,e.filename,e.lineno,e.colno,e.error */
  })

遇到的常见错误

  • Uncaught ReferenceError: WeixinJSBridge is not defined

    微信环境经典报错, 微信内置浏览器会有WeixinJSBridge,但是需要一定的加载时间,如果在它加载完成之前进行微信接口操作,就会出现这个bug。避免方法是监听它的加载事件:

    if (typeof window.WeixinJSBridge == "undefined"){
      document.addEventListener("WeixinJSBridgeReady", function () {
        /* 操作 */
      }
    }else {
      /* 操作 */
    }
    
  • Script error

    外部脚本报错,最常见就是http页面被运营商劫持插入广告。

    此外,想要获取外部脚本报错详情信息,需要在 <script> 标签处加一个crossorigin属性,并将拉取外部脚本的服务器资源设为'Access-Control-Allow-Origin:*'这里有个具体参考操作

  • Uncaught ReferenceError: FastClick is not defined

    Uncaught ReferenceError: jQuery is not defined

    ReferenceError: Can't find variable: wx

    都是由于资源未加载完成就使用导致的。

从一次用户报bug说起

  • 起因

公司APP注册页面有两页是h5做的,某天,体验群里一个用户截图提出,注册填生日页面点击无反应,这是大事啊,收到截图的我马上开始debug排查原因。

  • 第一轮排查

由于测试的安卓机和iphone都没出现这个问题,只能考虑生产环境捕获报错。
我在出问题的页面加了try-catch,把报错信息上报到redis上搜集:

try{
  /* 业务代码 */  
  }catch(error){
    $.ajax({
      url:"/* 上报接口 */",
      type:"post",
      dataType:"json",
      data:{
        /* 需要关注的有:报错信息,发生时间,用户设备情况 */
        errorMsg:error.message,
        errorStack:error.stack,
        time:Date.now(),
        page:'REGISTER',
        us:navigator.userAgent
      },
  }

部署了一个晚上以后,第二天果然捕获两个报错,长这样:

{
    "errorMsg": "Cannot read property 'version' of undefined",
    "errorStack": "TypeError: Cannot read property 'version' of undefined\n    at /* 该页面某行 */",
    "time": "1515089782262",
    "page": "REGISTER",
    "us": "Mozilla/5.0 (Linux; U; Android 4.2.2; zh-cn; N1T Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.3010colour-app-android&v2.1.5"
},
{
    "errorMsg": "undefined is not an object (evaluating 'platform_version.version')",
    "errorStack": "global code@/* 该页面某行 */",
    "time": "1515143791093",
    "page": "REGISTER",
    "us": "Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12H32110colour-app-iOS&v2.1.2"
}

查找报错位置,长这样:

if(platform_version.version&&platform_version.platform=='android'){
  /* 安卓平台的处理 */
}else{
  /* ios平台的处理 */
}

根据报错信息,应该是使用了一个未被定义过的变量的属性引起的,也就是说platform_version这个变量未定义,但是!platform_version是一个全局变量,是在头部定义的,此处介绍下项目模版结构:

<!doctype html>
<html>
  <head>
    <script type="text/javascript">
    /* 全局变量、版本控制、行为上报 */
    /* platform_version定义位置 */
    </script>
  </head>
  <body>
  /* 模版部分 */
  /* css */
  /* html */
  /* js——报错位置 */
  </body>
</html>  

由于头部的script为内联脚本,执行顺序是可以保证从上到下依次进行的,排除执行底部报错脚本时头部未执行的情况,而头部定义的位置中规中矩,长这样:

...
console.log(performance.now())
var us=navigator.userAgent;
us = us.toLowerCase();
var platform_version={};
var info = us.match(/10colour-app-(android|ios)&?v?(\d{1,2}\.\d{1,2}\.\d{1,2})?/);
if (info) {
  platform_version.platform=info[1];
  platform_version.version=info[2];
}
...

让我费解的是,明明我已经定义了一个初始空对象给platform_version,为什么底部js会报错说它不存在呢?卡了半天后,灵光一闪————是不是头部的公共js报错,导致platform_version并未成功赋值,所以底部才索引不到它。于是进入第二轮排查。

  • 第二轮排查

故技重施,我在头部加了try-catch并上报,又部署了一个晚上后,如愿捕获到报错:

{
    "errorMsg": "Uncaught TypeError: Object #<Performance> has no method 'now'"
    /* 其他省略 */
    "us": "Mozilla/5.0 (Linux; U; Android 4.3; zh-cn; R2017 Build/JLS36C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.3010colour-app-android&v2.1.5""
}

比较明确了,Performance对象没有now这个方法,问题来自头部console.log,吓得我赶快打开caniuse看了一眼:
Image of Yaktocat

明明是一片绿的嘛!

但是等等,为什么只统计到android 4.4和ios safari 10.2?

我还记得报错的机型,android 4.3,iphone 8_4_1什么的。

此时才意识到一个坑爹的问题,caniuse统计的版本信息默认情况下并不能完全作为兼容性参考,想要更完整的参考,需要手动设一下:

  • 搜索右侧的setting
    Image of Yaktocat

  • min-browser useges的比例,默认为0.5%,然而还不足以涵盖**市场,我试了下,0.05%比较合适,包含了android 4.1和ios 7.1。——此时为2018.2.10

    什么?你的老板要你兼容更低版本的?那就把进度条拉到最左,0.01%吧……
    Image of Yaktocat

改完设置以后,再看:
Image of Yaktocat
果然,android 4.3及以下,ios 8.4及以下,performance.now()这个方法都是不支持的,问题被找到了。解决方法就是去掉这句console,再观测两天,没有再收到报错,可以把try-catch去掉了,问题被解决。

  • 穿越到问题发生前

    某天,我吃着火锅唱着歌敲代码时,打印出:console.log(new Date().getTime())时,想起看过的另一种写法,Date.now(),两种有什么不同呢?研究一下,发现原理不同,结果相同:

    Date为一个时间相关对象

new Date().getTime() Date.now()
new Date()创建一个新时间对象,接着调用该对象的getTime()方法,获取时间戳 now()是Date的一个静态方法,获取时间戳
更快

附链接:stackoverflow上关于这个比较的讨论

既然如此,那么以后我都用Date.now()好了。然后,我又喵到一个同样是统计时间的方式——performance.now();

performance

performance是一个很有趣的接口,可以获取当前页面与性能相关的信息,包含三个只读对象:navigation/timing/memory,若干方法:比如now();

附链接:Performance--MDN,推荐去看一下

从统计浏览器加载性能来说,performance.now()比Date.now()好很多,前者精确度更高(千分之五秒),更可靠(不会受浏览器时间影响),然而目前支持率不太够(android 4.3及以下,ios 8.4及以下)。

  • 穿越回问题发生后

为什么出问题的版本是android 4.3及以下,ios 8.4及以下呢?

有时候有些问题没有太好的答案,比如可能就是那时候这个属性开始被支持啦,不过我研究了一下,还是找到一个更有一些意义的解释。

首先是版本和h5的关系,app内使用webview来加载页面,系统版本影响了webview的版本,进而影响h5页面兼容性。

  • ios 的webview有两种选择,UIwebview和WKwebview,后者是ios 8之后新推出的一个类,据说更强大,WKwebview兼容低版本系统的方式是切换为UIwebview,假设我们公司用的就是WKwebview那么performance的兼容问题就很好解释了。

但是我们公司ios使用的是UIwebview,我不太理解8.4之前和之后它发生了什么导致支持度的变化,如果有人知道求指出

  • android 的webview在4.4左右也发生了一件大事,它换内核了!在android 4.4(不包含4.4)以下,webview底层采用Webkit内核,在android 4.4及以上换成了chromium内核,后者对新特性的支持明显提升。

在这里又出现了新问题,我对chrome-chromium-webkit傻傻分不清……于是研究了一下,附上简书上一篇解释的比较好的文章链接:

附链接:常见的浏览器有哪些,什么内核?

总结一下重点:
- KHTML和KJS——Linux 桌面系统的引擎
- 2001年——苹果公司使用了修改自KHTML的Webkit内核作为safari的引擎
- 2008年——google推出chrome,使用了webkit内核,但是主要使用源自KHTML的webcore,不太用苹果开发的部分,使用自己的多进程浏览器架构,被称为Chromium引擎
- 2010年——苹果宣布了Webkit2,实现自己的多进程架构
- 2013年——chrome放弃chromium引擎转而使用Blink引擎(克隆自webkit,但是不再和webkit的发展相关)

  • 有关项目的总结

通过这次错误排查,我觉得,是时候给项目加上全局的错误上报统计了!
错误上报的问题计划单独写一篇总结,点击这里查看

  • 写在最后

2017年结束了,而我还没写几篇博客,之前在简书上写了一些,现在准备都挪到github的博客上,希望自己可以坚持下去。喜欢就给个星星吧:star2::star2::star2:

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.