前端错误捕获以及ajax监听

2017-12-20
|

上线的项目难免会有错误,通过分析日志能够有效、准确地定位、重现并解决错误,从而提升产品体验。以下是根据资料以及目前需求所实现的一个前端日志采集的方案。

前端代码异常(错误)

  1. 使用 js 的 try { } catch (e) { }: 在可能出现错误的地方主动进行捕获,上传。
try {
  // 代码块
  var a = 1;
  a.a.a;
} catch (e) {
  // 捕获的错误
  console.log(err);
  // TypeError: Cannot read property 'a' of undefined at test.html:33
  // 上报信息
  // ajax upload (err)
}

这种方式加上好的捕获处理,能够让程序不至于因为错误而崩溃。 但是,需要包裹代码块,这点可以用一个入口函数来解决:

try {
  init();
} catch (err) {
  // ajax upload (err)
}

即便如此也不能保证捕获所有错误:

function init() {
  //...
  $("button").click(function() {
    var a = 1;
    a.a.a;
  });
  //...
}

try {
  init();
} catch (err) {
  // 此处不能捕获到错误
}

// 而是控制台报未捕获的错误:Uncaught TypeError: Cannot read property 'a' of undefined

想要使用try ... catch捕获异步代码的错误,就要在代码执行的地方进行捕获:

function init() {
  //...
  $("button").click(function() {
    try {
      var a = 1;
      a.a.a;
    } catch (e) {
      // 捕获错误
    }
  });
  //...
}

init();
  1. 通过window.onerror监听页面错误
/**
* @param {String}  msg         错误信息
* @param {String}  scripturl   出错脚本的url
* @param {Long}    line        错误行号
* @param {Long}    col         错误列号
* @param {Object}  error       错误的详细信息
*/
window.onerror = function(msg, scripturl, line, col, error) {
  // return true; 可屏蔽 console 报错显示,此处依旧选择显示
  // 跨域脚本的错误,捕获的结果是 Script error.
  // 可通过使用 crossorigin 信任
  if (msg == "Script error.") {
    return false;
  }

  // 采用异步的方式
  // 参考的使用异步的方式,避免阻塞,没遇见过也不想遇到
  setTimeout(function() {
    var data = {};
    data.scripturl = scripturl;
    data.line = line;
    // 不一定所有浏览器都支持col参数
    data.col = col || (window.event && window.event.errorCharacter) || 0;
    if (!!error && !!error.stack) {
      // 如果浏览器有堆栈信息
      // 直接使用
      data.msg = error.stack.toString();
    } else {
      // 参考资料中有通过 arguments.callee.caller 获取错误信息
      // 但是严格模式下不允许访问 arguments.callee
      // 故此处无法获取错误信息
      data.msg = "无法获取详细信息";
    }
    // 把 data 错误信息上报到后台
  }, 0);

  return false;
};

这种方式相对try...catch显得方便多了,能够实现全局监听错误,有一点要注意的是实现监听最好放在前面,以免因为错误导致 js 中断从而根本没有绑定上监听事件。

同时收集 ajax 信息,协助错误定位

通过重写XMLHttpRequest对象的opensend方法进行信息收集。

var xhrArray = [];
var xhrlog = {
  // 记录请求的 url
  reqUrl: "",
  // 记录请求的方法
  reqMethod: "",
  // 保存原生的 open 方法
  xhrOpen: XMLHttpRequest.prototype.open,
  // 保存原生的 send 方法
  xhrSend: XMLHttpRequest.prototype.send,
  init: function() {
    var _self = this;

    // 重写 open
    XMLHttpRequest.prototype.open = function() {
      // 先在此处取得请求的url、method
      _self.reqUrl = arguments[1];
      _self.reqMethod = arguments[0];
      // 在调用原生 open 实现重写
      _self.xhrOpen.apply(this, arguments);
    };

    // 重写 send
    XMLHttpRequest.prototype.send = function() {
      // 记录xhr
      var xhrmsg = {
        url: _self.reqUrl,
        type: _self.reqMethod,
        // 此处可以取得 ajax 的请求参数
        data: arguments[0] || {}
      };

      this.addEventListener("readystatechange", function() {
        if (this.readyState === 4) {
          // 此处可以取得一些响应信息
          // 响应信息
          xhrmsg["res"] = this.response;
          xhrmsg["status"] = this.status;
          this.status >= 200 && this.status < 400
            ? (xhrmsg["level"] = "success")
            : (xhrmsg["level"] = "error");
          xhrArray.push(xhrmsg);
        }
      });

      _self.xhrSend.apply(this, arguments);
    };
  }
};

// 初始化,重写 xhr 方法
xhrlog.init();

// 至此,xhrArray 中就记录着当前页面的请求信息

至此,稍加修改、组合,就是一个简单而又实用的前端日志收集工具了,还是十分有意义的。

// 截止目前这份系统还没显露出威力,那,一定是因为我们的代码写的稳啊 ( ̄ ▽  ̄)/

参考资料

前端代码异常监控 :rocket: lajax - 前端日志解决方案 前端代码异常日志收集与监控 JavaScript 错误处理

☘️