详解js前端代码异常监控


Posted in Javascript onJanuary 11, 2017

阅读目录

  • 什么是前端代码异常 
  • window.onerror
  • 写一个js报错的上报库
  • 注意点:
  • 缺点:

在平时的工作,js报错是比较常见的一个情景,尤其是有一些错误可能我们在本地测试的时候测试不出来,当发布到线上之后才可以发现,如果抢救及时,那还好,假如很晚才发

现,那就可能造成很大的损失了。如果我们前端可以监控到这种报错,并及时上报的话,那我们的问题就比较好解决了。所以我们今天来聊聊前端代码的异常监控

什么是前端代码异常 

一般语法错误以及运行时错误,浏览器都会在console里边体现出错误信息,以及出错的文件,行号,堆栈信息。

我们先来说手前端代码异常是什么意思。前端代码异常指的是以下两种情况:

1、JS脚本里边存着语法错误;

2、JS脚本在运行时发生错误。

类似于这种:

for(var i=0;i<l;i++){
 console.log(i);
}

详解js前端代码异常监控

那么我们如何来捕获这种异常呢,有两种方法,

第一种是try..catch

第二种是 window.onerror

由于try.catch 没法捕捉到全局的错误事件,也即是说 只有try,catch的块里边运行出错才会被你捕捉到。所以我们这里排除它的这种方案,

来采用第二种方法,也就是window.onerror方法。

window.onerror

打开浏览器自带的开发者工具,当一个错误发生时,我们可以立刻得到提示,并且知道错误发生的位置以及调用的堆栈信息。

我们可以通过 window.onerror 来捕获页面上的各种脚本执行异常,它能帮助我们获取有用的信息。但是这个方法存在兼容性问题,在不同的浏览器上提供的数据不完全一致,

部分过时的浏览器只能提供部分数据。它的用法如下:

window.onerror = function (message, url, lineNo, columnNo, error)

五个参数的含义如下:

1、message {String} 错误信息。直观的错误描述信息,不过有时候你确实无法从这里面看出端倪,特别是压缩后脚本的报错信息,可能让你更加疑惑。

2、url {String} 发生错误对应的脚本路径,比如是你的http://a.js报错了还是http://b.js报错了。

3、lineNo {Number} 错误发生的行号。

4、columnNo {Number} 错误发生的列号。

5、error {Object} 具体的 error 对象,包含更加详细的错误调用堆栈信息,这对于定位错误非常有帮助。

兼容性问题

不同浏览器对同一个错误的 message 是不一样的。

IE10以下浏览器只能获取到 message,url 和 lineNo这三个参数,获取不到columnNo 和 error

不过 window.event 对象提供了 errorLine errorCharacter,以此来对应相应的行列号信息。

在使用onerror的时候,我们可以使用arguments.callee.caller 来递归出调用堆栈,这一类信息是最直接的错误信息信息,所以是必须要捕获并上报的。后面我们会用js去示范。

不同浏览器默认可获取的参数值:

详解js前端代码异常监控

写一个js报错的上报库

既然知道了window.onerror的用法,为啥我们不来写一个js库来监控我们的前端js,废话少说,写之。

实现思路:

1、收集window.onerror的五个参数

2、除了那五个参数,可以增加自定义参数

3、发送到后台服务器

我们暂且给我们的库起名为 badJsReport

原理比较简单,代码如下:

/**
 * Name: badJsReport.js
 * Version 1.1.0
 * Author xianyulaodi
 * Address: https://github.com/xianyulaodi/badJsReport
 * Released on: December 22, 2016
 */
;(function(){
 'use strict';
 if (window.badJsReport){ 
 return window.badJsReport 
 };
 /*
 * 默认上报的错误信息
 */ 
 var defaults = {
 msg:'', //错误的具体信息
 url:'', //错误所在的url
 line:'', //错误所在的行
 col:'', //错误所在的列
 error:'', //具体的error对象
 };
 /*
 *ajax封装
 */
 function ajax(options) {
 options = options || {};
 options.type = (options.type || "GET").toUpperCase();
 options.dataType = options.dataType || "json";
 var params = formatParams(options.data);
 if (window.XMLHttpRequest) {
 var xhr = new XMLHttpRequest();
 } else { 
 var xhr = new ActiveXObject('Microsoft.XMLHTTP');
 }
 xhr.onreadystatechange = function () {
 if (xhr.readyState == 4) {
 var status = xhr.status;
 if (status >= 200 && status < 300) {
  options.success && options.success(xhr.responseText, xhr.responseXML);
 } else {
  options.fail && options.fail(status);
 }
 }
 }
 if (options.type == "GET") {
 xhr.open("GET", options.url + "?" + params, true);
 xhr.send(null);
 } else if (options.type == "POST") {
 xhr.open("POST", options.url, true);
 //设置表单提交时的内容类型
 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
 xhr.send(params);
 }
 }
 /*
 *格式化参数
 */
 function formatParams(data) {
 var arr = [];
 for (var name in data) {
 arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
 }
 arr.push(("v=" + Math.random()).replace(".",""));
 return arr.join("&");
 }
 /*
 * 合并对象,将配置的参数也一并上报
 */
 function cloneObj(oldObj) { //复制对象方法
 if (typeof(oldObj) != 'object') return oldObj;
 if (oldObj == null) return oldObj;
 var newObj = new Object();
 for (var prop in oldObj)
 newObj[prop] = oldObj[prop];
 return newObj;
 };
 function extendObj() { //扩展对象
 var args = arguments;
 if (args.length < 2) {return;}
 var temp = cloneObj(args[0]); //调用复制对象方法
 for (var n = 1,len=args.length; n <len; n++){
 for (var index in args[n]) {
 temp[index] = args[n][index];
 }
 }
 return temp;
 }
 /**
 * 核心代码区
 **/
 var badJsReport=function(params){
 if(!params.url){return}
 window.onerror = function(msg,url,line,col,error){
 //采用异步的方式,避免阻塞
 setTimeout(function(){
 //不一定所有浏览器都支持col参数,如果不支持就用window.event来兼容
 col = col || (window.event && window.event.errorCharacter) || 0;
 defaults.url = url;
 defaults.line = line;
 defaults.col = col;
 if (error && error.stack){
  //如果浏览器有堆栈信息,直接使用
  defaults.msg = error.stack.toString();
 }else if (arguments.callee){
  //尝试通过callee拿堆栈信息
  var ext = [];
  var fn = arguments.callee.caller;
  var floor = 3; //这里只拿三层堆栈信息
  while (fn && (--floor>0)) {
  ext.push(fn.toString());
  if (fn === fn.caller) {
  break;//如果有环
  }
  fn = fn.caller;
  }
  ext = ext.join(",");
  defaults.msg = error.stack.toString();
 }
 // 合并上报的数据,包括默认上报的数据和自定义上报的数据
 var reportData=extendObj(params.data || {},defaults);
 // 把错误信息发送给后台
 ajax({
  url: params.url, //请求地址
  type: "POST", //请求方式
  data: reportData, //请求参数
  dataType: "json",
  success: function (response, xml) {
  // 此处放成功后执行的代码
params.successCallBack&¶ms.successCallBack(response, xml);
  },
  fail: function (status) {
  // 此处放失败后执行的代码
  params.failCallBack&¶ms.failCallBack(status);
  }
  });
 },0);
 return true; //错误不会console浏览器上,如需要,可将这样注释
 };
 }
 window.badJsReport=badJsReport;
})();
/*===========================
badJsReport AMD Export
===========================*/
if (typeof(module) !== 'undefined'){
 module.exports = window.badJsReport;
}
else if (typeof define === 'function' && define.amd) {
 define([], function () {
 'use strict';
 return window.badJsReport;
 });
}

我们封装了原生ajax,还有将上报的参数对象合并。并暴露了一个全局方法 badJsReport

使用方法:

1、将badJsReport.js加载到其他的js之前

2、简单的使用方法:(这个执行方法要放在其他代码执行之前)

badJsReport({
 url:'http://www.baidu.com', //发送到后台的url *必须
})

3、如果需要新增上报参数,或者要知道发送给后台的回调。可以用下面的方法

badJsReport({
 url:'http://www.baidu.com', //发送到后台的url *必须
 data:{}, //自定义添加上报参数,比如app版本,浏览器版本 -可省略
 successCallBack:function(response, xml){
 // 发送给后台成功的回调,-可省略
 },
 failCallBack:function(error){
 // 发送给后台失败的回调,-可省略
 }
})

注意点:

1、对于跨域的JS资源,window.onerror拿不到详细的信息,需要往资源的请求添加额外的头部。

静态资源请求需要加多一个Access-Control-Allow-Origin头部,也就是需要后台加一个Access-Control-Allow-Origin,同时script引入外链的标签需要加多一个crossorigin的属性。这样就可以获取准确的出错信息。

2、因为代码的最后return true,所以如果有错误信息,浏览器不会console出来,如果需要浏览器console,可以注释掉最后的return true

缺点:

对于压缩之后的代码,我们得到错误的信息,但是我们却无法定位到错误的行数,比如jquery的源码压缩,总共才3行。这样就很难定位到具体的地方了,因为一行有很多很多的代码。

代码我放到了github上:https://github.com/xianyulaodi/badJsReport

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
了解了这些才能开始发挥jQuery的威力
Oct 10 Javascript
js获取或设置当前窗口url参数的小例子
Oct 14 Javascript
js setTimeout opener的用法示例详解
Oct 23 Javascript
浅析javascript中的DOM
Mar 01 Javascript
js获取隐藏元素宽高的实现方法
May 19 Javascript
JS 日期与时间戮相互转化的简单实例
Jun 22 Javascript
简单实现jQuery级联菜单
Jan 09 Javascript
EasyUI中的dataGrid的行内编辑
Jun 22 Javascript
vue init失败简单解决方法(终极版)
Dec 22 Javascript
深入理解node.js http模块
Jan 24 Javascript
6行代码实现微信小程序页面返回顶部效果
Dec 28 Javascript
JavaScript&quot;模拟事件&quot;的注意要点详解
Feb 13 Javascript
Vue数据驱动模拟实现3
Jan 11 #Javascript
jQuery实现判断控件是否显示的方法
Jan 11 #Javascript
jQuery Form表单取值的方法
Jan 11 #Javascript
vue实现ajax滚动下拉加载,同时具有loading效果(推荐)
Jan 11 #Javascript
浅谈JavaScript中promise的使用
Jan 11 #Javascript
JS多文件上传的实例代码
Jan 11 #Javascript
微信小程序开发(一) 微信登录流程详解
Jan 11 #Javascript
You might like
php抓即时股票信息
2006/10/09 PHP
PHP判断FORM表单或URL参数来的数据是否为整数的方法
2016/03/25 PHP
PHP XML和数组互相转换详解
2016/10/26 PHP
PHP递归统计系统中代码行数
2019/09/19 PHP
javascript实现二分查找法实现代码
2007/11/12 Javascript
跨域表单提交状态的变相判断代码
2009/11/12 Javascript
使用jQuery模板来展现json数据的代码
2010/10/22 Javascript
jquery tab插件精简版分享
2011/09/10 Javascript
JavaScript高级程序设计 阅读笔记(十三) js定义类或对象
2012/08/14 Javascript
动态的改变IFrame的高度实现IFrame自动伸展适应高度
2012/12/28 Javascript
JS实现标签页效果(配合css)
2013/04/03 Javascript
jQuery及JS实现循环中暂停的方法
2015/02/02 Javascript
js 轮播效果实例分享
2016/12/28 Javascript
react-native ListView下拉刷新上拉加载实现代码
2017/08/03 Javascript
JavaScript ES6中的简写语法总结与使用技巧
2018/12/30 Javascript
js图片无缝滚动插件使用详解
2020/05/26 Javascript
EasyUI 数据表格datagrid列自适应内容宽度的实现
2019/07/18 Javascript
微信小程序登录时如何获取input框中的内容
2019/12/04 Javascript
[03:48]大碗DOTA
2019/07/25 DOTA
Python创建xml文件示例
2017/03/22 Python
使用pandas对两个dataframe进行join的实例
2018/06/08 Python
Python实现string字符串连接的方法总结【8种方式】
2018/07/06 Python
Python爬虫之网页图片抓取的方法
2018/07/16 Python
详解Python计算机视觉 图像扭曲(仿射扭曲)
2019/03/27 Python
基于Python的PIL库学习详解
2019/05/10 Python
python的sorted用法详解
2019/06/25 Python
python导包的几种方法(自定义包的生成以及导入详解)
2019/07/15 Python
python nmap实现端口扫描器教程
2020/05/28 Python
TensorFlow Saver:保存和读取模型参数.ckpt实例
2020/02/10 Python
使用CSS3的::selection改变选中文本颜色的方法
2015/09/29 HTML / CSS
Contém1g官网:巴西彩妆品牌
2020/01/17 全球购物
DBA数据库管理员JAVA程序员架构师必看
2016/02/07 面试题
岗位职责怎么写
2014/03/14 职场文书
优质服务活动实施方案
2014/05/02 职场文书
我们的节日端午节活动总结
2015/02/11 职场文书
python如何将mat文件转为png
2022/07/15 Python