前端错误监控

1. 监控并收集Javascript错误

  众所周知,我们是有办法去监听前端Js错误的,他们分别 window.onerror、window.onunhandledrejection、console.error方法。
  • 重写 window.onerror 方法

// 重写 onerror 进行jsError的监听
window.onerror = function(errorMsg, url, lineNumber, columnNumber, errorObj) {
  var errorStack = errorObj ? errorObj.stack : null;
  siftAndMakeUpMessage("on_error", errorMsg, url, lineNumber, columnNumber, errorStack);
};

  • 重写 window.onunhandledrejection 方法

window.onunhandledrejection = function(e) {
  var errorMsg = "";
  var errorStack = "";
  if (typeof e.reason === "object") {
    errorMsg = e.reason.message;
    errorStack = e.reason.stack;
  } else {
    errorMsg = e.reason;
    errorStack = "";
  }
  // 分类解析
  siftAndMakeUpMessage("on_error", errorMsg, WEB_LOCATION, 0, 0, "UncaughtInPromiseError: " + errorStack);
}

window.onunhandledrejection 能够捕获到Promise未处理的rejection异常,rejection异常并不会阻断页面运行,容易被很多小伙伴所遗忘,所以我们监控了此类型的错误。

  • 重写 console.error 方法

// 重写console.error, 可以捕获更全面的报错信息
var oldError = console.error;
console.error = function (tempErrorMsg) {
  var errorMsg = (arguments[0] && arguments[0].message) || tempErrorMsg;
  var lineNumber = 0;
  var columnNumber = 0;
  var errorObj = arguments[0] && arguments[0].stack;
    if (!errorObj) {
      if (typeof errorMsg == "object") {
        try {
          errorMsg = JSON.stringify(errorMsg)
        } catch(e) {
          errorMsg = "错误无法解析"
        }
      }
      siftAndMakeUpMessage("console_error", errorMsg, WEB_LOCATION, lineNumber, columnNumber, "CustomizeError: " + errorMsg);
    } else {
      // 如果报错中包含错误堆栈,可以认为是JS报错,而非自定义报错
      siftAndMakeUpMessage("on_error", errorMsg, WEB_LOCATION, lineNumber, columnNumber, errorObj);
    }
    return oldError.apply(console, arguments);
  };

console.error 是用来打印警告日志,所以我将其归类为自定义异常。一般像前端框架、引入第三方的插件都会用 console.error 来打印较为严重的警告信息,而我在工作中也会将后台抛出的错误信息(非后台异常)用console.error打印出来,上报到监控系统里。这样对排查异常也是有很大作用的(这一点会在行为记录查询中有体现)。

2. 存储并上报错误

Javascript错误产生后,应该存入浏览器的缓存中,然后定时上传,如果实时上传,将会对服务器造成压力。通过接口将Js错误信息上传到服务器,由后台server对数据进行清洗分类,然后再进行持久化存储。


// 设置日志对象类的通用属性
  function setCommonProperty() {
    this.wmVersion = WM_VERSION; // 探针版本号
    this.happenTime = new Date().getTime(); // 日志发生时间
    this.webMonitorId = WEB_MONITOR_ID;     // 用于区分应用的唯一标识(一个项目对应一个)
    this.simpleUrl =  window.location.href.split('?')[0].replace('#', ''); // 页面的url
    this.completeUrl =  utils.b64EncodeUnicode(encodeURIComponent(window.location.href)); // 页面的完整url
    this.customerKey = utils.getCustomerKey(); // 用于区分用户,所对应唯一的标识,清理本地数据后失效,
    // 用户自定义信息, 由开发者主动传入, 便于对线上问题进行准确定位
    var wmUserInfo = lsg.wmUserInfo ? JSON.parse(lsg.wmUserInfo) : {};
    this.userId = wmUserInfo.userId;
    this.firstUserParam = utils.b64EncodeUnicode(wmUserInfo.firstUserParam || "");
    this.secondUserParam = utils.b64EncodeUnicode(wmUserInfo.secondUserParam || "");
  }
  // JS错误日志,继承于日志基类MonitorBaseInfo
  function JavaScriptErrorInfo(uploadType, infoType, errorMsg, errorStack) {
    setCommonProperty.apply(this);
    this.uploadType = uploadType;
    this.infoType = infoType;
    this.pageKey = utils.getPageKey();  // 用于区分页面,所对应唯一的标识,每个新页面对应一个值
    this.deviceName = DEVICE_INFO.deviceName;
    this.os = DEVICE_INFO.os + (DEVICE_INFO.osVersion ? " " + DEVICE_INFO.osVersion : "");
    this.browserName = DEVICE_INFO.browserName;
    this.browserVersion = DEVICE_INFO.browserVersion;
    // TODO 位置信息, 待处理
    this.monitorIp = utils.getCookie("webfunny_ip");  // 用户的IP地址
    this.country = "china";  // 用户所在国家
    this.province = "";  // 用户所在省份
    this.city = "";  // 用户所在城市
    this.errorMessage = utils.b64EncodeUnicode(errorMsg)
    this.errorStack = utils.b64EncodeUnicode(errorStack);
    this.browserInfo = "";
  }

Js错误信息需要包含系统版本号、应用版本号、平台信息、页面Url、错误信息、错误堆栈、发生时间等等,这样才能帮助我们准确定位。

3. 分析并聚合错误

首先,我们对捕获的异常类型进行了分类(TypeError、ReferenceError、UncaughtInPromiseError),这样错误类型可以一目了然。

同时我们对发生错误的操作系统(Android、ios、Pc)进行了分类统计,比如截图中的第一个错误,就只会在苹果手机上发生,排查范围也就缩小了很多。

另外,我们把错误影响的人数也统计出来,就可以知道这个错误影响了多少用户,从而确定修复的优先级。

4. 发送错误报警

这一步属于监控的附加功能,主要包括邮箱、钉钉、短信等消息通知。

5. 定位并解决JS错误

针对某一个错误,我们需要分析它发生的平台,影响的人数,系统版本,网络环境等等,同时也需要分析最为重要的一步,就是代码的位置。

首先,我们分析了错误发生的具体时间、发生次数、影响人数、IP地址、浏览器版本、操作系统等环境因素

其次,我们还需统计这个报错发生的时间曲线,如果是大量报错,我们可以很容易定位到错误发生的起始点,针对那个时间点,对报错的原因进行定位。

前端监控系统Webfunny