一步一步搭建前端監控系統:JS錯誤監控篇

摘要: 徒手寫JS錯誤監控。javascript

Fundebug經受權轉載,版權歸原做者全部。html

背景:市面上的監控系統有不少,大多收費,對於小型前端項目來講,必然是痛點。另外一點主要緣由是,功能通用,卻未必可以知足咱們本身的需求, 因此咱們自給自足。前端

這是搭建前端監控系統的第二章,主要是介紹如何統計js報錯,跟着我一步步作,你也能搭建出一個屬於本身的前端監控系統。java

請移步線上:前端監控系統 web

對於前端應用來講,Js錯誤的發生直接影響前端應用的質量。對前端異常的監控是整個前端監控系統中的一個重要環節。前端異常包含不少種狀況:1. js編譯時異常(開發階段就能排)2. js運行時異常;3. 加載靜態資源異常(路徑寫錯、資源服務器異常、CDN異常、跨域)4. 接口請求異常等。這一篇咱們只介紹Js運行時異常。ajax

監控流程:監控錯誤 -> 蒐集錯誤 -> 存儲錯誤 -> 分析錯誤 -> 錯誤報警-> 定位錯誤 -> 解決錯誤編程

首先,咱們應該對Js報錯狀況有個大體的瞭解,這樣纔可以及時的瞭解前端項目的健康情況。因此咱們須要分析出一些必要的數據。小程序

如:一段時間內,應用JS報錯的走勢(chart圖表)、JS錯誤發生率、JS錯誤在PC端發生的機率、JS錯誤在IOS端發生的機率、JS錯誤在Android端發生的機率,以及JS錯誤的歸類。segmentfault

而後,咱們再去其中的Js錯誤進行詳細的分析,輔助咱們排查出錯的位置和發生錯誤的緣由。微信小程序

如:JS錯誤類型、 JS錯誤信息、JS錯誤堆棧、JS錯誤發生的位置以及相關位置的代碼;JS錯誤發生的概率、瀏覽器的類型,版本號,設備機型等等輔助信息

1、JS Error 監控功能 (數據概覽)

爲了獲得這些數據,咱們須要在上傳的時候將其分析出來。在衆多日誌分析中,不少字段及功能是重複通用的,因此應該將其封裝起來。

// 設置日誌對象類的通用屬性
  function setCommonProperty() {
    this.happenTime = new Date().getTime(); // 日誌發生時間
    this.webMonitorId = WEB_MONITOR_ID;     // 用於區分應用的惟一標識(一個項目對應一個)
    this.simpleUrl =  window.location.href.split('?')[0].replace('#', ''); // 頁面的url
    this.customerKey = utils.getCustomerKey(); // 用於區分用戶,所對應惟一的標識,清理本地數據後失效
    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 = "";  // 用戶的IP地址
    this.country = "china";  // 用戶所在國家
    this.province = "";  // 用戶所在省份
    this.city = "";  // 用戶所在城市
    // 用戶自定義信息, 由開發者主動傳入, 便於對線上進行準肯定位
    this.userId = USER_INFO.userId;
    this.firstUserParam = USER_INFO.firstUserParam;
    this.secondUserParam = USER_INFO.secondUserParam;
  }

  // JS錯誤日誌,繼承於日誌基類MonitorBaseInfo
  function JavaScriptErrorInfo(uploadType, errorMsg, errorStack) {
    setCommonProperty.apply(this);
    this.uploadType = uploadType;
    this.errorMessage = encodeURIComponent(errorMsg);
    this.errorStack = errorStack;
    this.browserInfo = BROWSER_INFO;
  }
  JavaScriptErrorInfo.prototype = new MonitorBaseInfo();

封裝了一個Js錯誤對象JavaScriptErrorInfo,用以保存頁面中產生的Js錯誤。其中,setCommonProperty用以設置全部日誌對象的通用屬性。

1)重寫window.onerror 方法, 你們熟知,監控JS錯誤必然離不開它,有人對他進行了測試測試介紹感受也是比較用心了

2)重寫console.error方法,爲何要重寫這個方法,我不可以給出明確的答案,若是App首次向瀏覽器注入的Js代碼報錯了,window.onerror是沒法監控到的,因此只能重寫console.error的方式來進行捕獲,也許會有更好的辦法。待window.onerror成功後,此方法便再也不須要用了

3)重寫window.onunhandledrejection方法。 當你用到Promise的時候,而你又忘記寫reject的捕獲方法的時候,系統老是會拋出一個叫 Unhandled Promise rejection. 沒有堆棧,沒有其餘信息,特別是在寫fetch請求的時候很容易發生。 因此咱們須要重寫這個方法,以幫助咱們監控此類錯誤

下邊是啓動JS錯誤監控代碼

/**
   * 頁面JS錯誤監控
   */
  function recordJavaScriptError() {
    // 重寫console.error, 能夠捕獲更全面的報錯信息
    var oldError = console.error;
    console.error = function () {
      // arguments的長度爲2時,纔是error上報的時機
      // if (arguments.length < 2) return;
      var errorMsg = arguments[0] && arguments[0].message;
      var url = WEB_LOCATION;
      var lineNumber = 0;
      var columnNumber = 0;
      var errorObj = arguments[0] && arguments[0].stack;
      if (!errorObj) errorObj = arguments[0];
      // 若是onerror重寫成功,就無需在這裏進行上報了
      !jsMonitorStarted && siftAndMakeUpMessage(errorMsg, url, lineNumber, columnNumber, errorObj);
      return oldError.apply(console, arguments);
    };
    // 重寫 onerror 進行jsError的監聽
    window.onerror = function(errorMsg, url, lineNumber, columnNumber, errorObj)
    {
      jsMonitorStarted = true;
      var errorStack = errorObj ? errorObj.stack : null;
      siftAndMakeUpMessage(errorMsg, url, lineNumber, columnNumber, errorStack);
    };

    function siftAndMakeUpMessage(origin_errorMsg, origin_url, origin_lineNumber, origin_columnNumber, origin_errorObj) {
      var errorMsg = origin_errorMsg ? origin_errorMsg : '';
      var errorObj = origin_errorObj ? origin_errorObj : '';
      var errorType = "";
      if (errorMsg) {
        var errorStackStr = JSON.stringify(errorObj)
        errorType = errorStackStr.split(": ")[0].replace('"', "");
      }
      var javaScriptErrorInfo = new JavaScriptErrorInfo(JS_ERROR, errorType + ": " + errorMsg, errorObj);
      javaScriptErrorInfo.handleLogInfo(JS_ERROR, javaScriptErrorInfo);
    };
  };

OK, 錯誤日誌有了,該怎麼計算錯誤率呢?

JS錯誤發生率 = JS錯誤個數(一次訪問頁面中,全部的js錯誤都算一次)/PV (PC,IOS,Android平臺同理)

因此咱們須要記下頁面的PV記錄

/**
       * 添加一個定時器,進行數據的上傳
       * 2秒鐘進行一次URL是否變化的檢測
       * 10秒鐘進行一次數據的檢查並上傳
       */
      var timeCount = 0;
      setInterval(function () {
        checkUrlChange();
        // 循環5後次進行一次上傳
        if (timeCount >= 25) {
          // 若是是本地的localhost, 就忽略,不進行上傳

          var logInfo = (localStorage[ELE_BEHAVIOR] || "") +
            (localStorage[JS_ERROR] || "") +
            (localStorage[HTTP_LOG] || "") +
            (localStorage[SCREEN_SHOT] || "") +
            (localStorage[CUSTOMER_PV] || "") +
            (localStorage[LOAD_PAGE] || "") +
            (localStorage[RESOURCE_LOAD] || "");

          if (logInfo) {
            localStorage[ELE_BEHAVIOR] = "";
            localStorage[JS_ERROR] = "";
            localStorage[HTTP_LOG] = "";
            localStorage[SCREEN_SHOT] = "";
            localStorage[CUSTOMER_PV] = "";
            localStorage[LOAD_PAGE] = "";
            localStorage[RESOURCE_LOAD] = "";
            utils.ajax("POST", HTTP_UPLOAD_LOG_INFO, {logInfo: logInfo}, function (res) {}, function () {})
          }
          timeCount = 0;
        }
        timeCount ++;
      }, 200);

上邊的代碼我用了定時器,大概的意思是200毫秒進行一次URL變化的檢查,5秒進行一次數據的檢查,若是有數據就進行上傳,並清空上一次的數據。爲何用定時器呢,由於在單頁應用中,路由的切換和地址欄的變化是沒法被監控的,我確實沒有想到特別好的辦法來監控,因此用了這種方式,若是有人有更好的辦法,請給我留言,謝謝。

封裝簡易的Ajax

爲了將這些數據上傳到咱們的服務器,咱們總不能每次都用xmlHttpRequest來發送ajax請求吧,因此咱們須要本身封裝一個簡單的Ajax

/**
     *
     * @param method  請求類型(大寫)  GET/POST
     * @param url     請求URL
     * @param param   請求參數
     * @param successCallback  成功回調方法
     * @param failCallback   失敗回調方法
     */
    this.ajax = function(method, url, param, successCallback, failCallback) {
      var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
      xmlHttp.open(method, url, true);
      xmlHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
      xmlHttp.onreadystatechange = function () {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
          var res = JSON.parse(xmlHttp.responseText);
          typeof successCallback == 'function' && successCallback(res);
        } else {
          typeof failCallback == 'function' && failCallback();
        }
      };
      xmlHttp.send("data=" + JSON.stringify(param));
    }

2、JS Error 詳細信息解析

統計JS Error的目的,1、是爲了瞭解線上項目的健康情況,2、是爲了分析錯誤,幫助咱們查找問題之所在,而且解決它。

因此,如何定位線上的問題,並解決問題,是咱們如今要討論的重點。下面咱們須要對幾個關鍵點進行分析:

① 某種錯誤發生的次數——發生次數跟影響用戶是成正比的, 若是發生次數跟影響用戶數量都很高,那麼這是一個比較嚴重的bug, 須要當即解決。 反之, 若是次數不少,影響用戶數量不多。說明這種錯誤只發生在少許設備中,優先級相對較低,能夠擇時對該類機型設備進行兼容處理。固然,ip地址訪問次數也能說明這個問題

② 頁面發生了哪些錯誤——這個有利於咱們縮小問題的範圍,方便咱們排查,如:

③ 錯誤堆棧——這點不用說,是定位錯誤最重要的因素。正常狀況下,代碼都是被壓縮的,因此我在後臺解析並截取出錯代碼附近的一部分代碼,進行展現,排查錯誤。PS: 我看到網上有人利用jsMap反向找到代碼的具體位置,想法很不錯,後期我會加上。 另外,代碼雖然被壓縮,可是依然很輕鬆定位到出錯的位置,以下圖所示, 因此這個功能暫時做爲附加題,不用那麼着急加上。

④ 設備信息——當錯誤發生是,分析出用戶當時使用設備的瀏覽器信息,系統版本,設備機型等等,可以幫咱們快速的定位到須要兼容的設備,進而提高解決問題的效率。

⑤ 用戶足跡——我我的以爲比較有用,可是代價過高。 由於這個須要記錄下用戶在頁面上的全部行爲,須要上傳很是多的數據,功能待定。

這個功能已經在後邊進行完善了,點擊 查看足跡 按鈕便可查出這個用的行爲足跡,在定位線上問題方面,有很大的做用 , 我在後邊的篇幅中有介紹 搭建前端監控系統(五)怎樣定位線上問題

到此,已經收集到了JS錯誤日誌的大部分信息了,而且已經分析出JS錯誤的詳細信息了。

3、JS報錯的實時監控與報警

既然咱們已經具備了蒐集js報錯和分析報錯的能力了,那麼咱們也能夠作到Js報錯實時監控,以及實時預警了,這樣能夠防範線上事故於未然,及時的制止線上事故的持續發生, 減小損失。

如上圖所示,我展現了從當前時間向前推算24小時,每小時報錯數量。另外展現了7天前同一時間段的報錯數量,若是你的項目健康穩定,那麼在相同時間段的報錯數量應該不會相差太大。若是出現相差太大的狀況發生,說明線上出現了問題,此刻應該發出警告,避免線上事故的發生。demo上暫未加上警告功能,可是原理清楚了,後邊天然水到渠成。

關於Fundebug

Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對一、微脈、青團社等衆多品牌企業。歡迎你們免費試用!

相關文章
相關標籤/搜索