详解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的固定表格头部的代码(IE6,7,8测试通过)
May 18 Javascript
限制textbox或textarea输入字符长度的JS代码
Oct 16 Javascript
js模拟hashtable的简单实例
Mar 06 Javascript
JS应用正则表达式转换大小写示例
Sep 18 Javascript
Backbone中View之间传值的学习心得
Aug 09 Javascript
微信小程序  modal详解及实例代码
Nov 09 Javascript
轻松理解JavaScript之AJAX
Mar 15 Javascript
vue2 中如何实现动态表单增删改查实例
Jun 09 Javascript
JS设计模式之命令模式概念与用法分析
Feb 06 Javascript
Vue用v-for给src属性赋值的方法
Mar 03 Javascript
vue中引入第三方字体文件的方法示例
Dec 17 Javascript
AngularJS实现的鼠标拖动画矩形框示例【可兼容IE8】
May 17 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
屏蔽浏览器缓存另类方法
2006/10/09 PHP
php实现的mongodb操作类实例
2015/04/03 PHP
JavaScript 格式字符串的应用
2010/03/29 Javascript
各浏览器对click方法的支持差异小结
2011/07/31 Javascript
javascript中的parseInt和parseFloat区别
2013/07/12 Javascript
jquery动态添加删除一行数据示例
2014/06/12 Javascript
jQuery html()方法使用不了无法显示内容的问题
2014/08/06 Javascript
JS中this上下文对象使用方式
2016/10/09 Javascript
jquery dialog获取焦点的方法
2017/02/09 Javascript
Require.js的基本用法详解
2017/07/03 Javascript
为输入框加入数字js校验代码分享
2017/11/02 Javascript
浅谈在node.js进入文件目录的问题
2018/05/13 Javascript
微信小程序视图容器(swiper)组件创建轮播图
2020/06/19 Javascript
JS检测浏览器开发者工具是否打开的方法详解
2020/10/02 Javascript
python映射列表实例分析
2015/01/26 Python
Python3.遍历某文件夹提取特定文件名的实例
2018/04/26 Python
Python之列表的插入&amp;替换修改方法
2018/06/28 Python
Python 数据库操作 SQLAlchemy的示例代码
2019/02/18 Python
python批量爬取下载抖音视频
2019/06/17 Python
python对指定字符串逆序的6种方法(小结)
2020/04/02 Python
python使用正则表达式匹配txt特定字符串(有换行)
2020/12/09 Python
详解window.open被浏览器拦截的解决方案
2019/07/18 HTML / CSS
专门出售各种儿童读物的网站:Put Me In The Story
2016/08/07 全球购物
瑞典Happy Socks美国官网:购买色彩斑斓的快乐袜子
2016/10/19 全球购物
ProBikeKit英国:在线公路自行车之家
2017/02/10 全球购物
Lookfantastic香港官网:英国知名美妆购物网站
2018/06/19 全球购物
英国羊皮鞋类领先品牌:Just Sheepskin
2019/12/12 全球购物
公司活动总结怎么写
2014/06/25 职场文书
教师民族团结演讲稿
2014/08/27 职场文书
群众路线自我剖析材料
2014/10/08 职场文书
2014年食堂工作总结
2014/11/20 职场文书
2015年世界急救日宣传活动方案
2015/05/06 职场文书
90条交通安全宣传标语
2019/10/12 职场文书
Java并发编程之详解CyclicBarrier线程同步
2021/06/23 Java/Android
MySql重置root密码 --skip-grant-tables
2022/04/11 MySQL
React如何使用axios请求数据并把数据渲染到组件
2022/08/05 Javascript