天天看點

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

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

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

Fundebug

經授權轉載,版權歸原作者所有。

背景:市面上的監控系統有很多,大多收費,對于小型前端項目來說,必然是痛點。另一點主要原因是,功能通用,卻未必能夠滿足我們自己的需求, 是以我們自給自足。

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

請移步線上:

前端監控系統

對于前端應用來說,Js錯誤的發生直接影響前端應用的品質。對前端異常的監控是整個前端監控系統中的一個重要環節。前端異常包含很多種情況:1. js編譯時異常(開發階段就能排)2. js運作時異常;3. 加載靜态資源異常(路徑寫錯、資源伺服器異常、CDN異常、跨域)4. 接口請求異常等。這一篇我們隻介紹Js運作時異常。

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

首先,我們應該對Js報錯情況有個大緻的了解,這樣才能夠及時的了解前端項目的健康狀況。是以我們需要分析出一些必要的資料。

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

然後,我們再去其中的Js錯誤進行詳細的分析,輔助我們排查出錯的位置和發生錯誤的原因。

如:JS錯誤類型、 JS錯誤資訊、JS錯誤堆棧、JS錯誤發生的位置以及相關位置的代碼;JS錯誤發生的幾率、浏覽器的類型,版本号,裝置機型等等輔助資訊

一、JS Error 監控功能 (資料概覽)

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

為了得到這些資料,我們需要在上傳的時候将其分析出來。在衆多日志分析中,很多字段及功能是重複通用的,是以應該将其封裝起來。

// 設定日志對象類的通用屬性
  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));
    }           

二、JS Error 詳細資訊解析

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

統計JS Error的目的,一、是為了了解線上項目的健康狀況,二、是為了分析錯誤,幫助我們查找問題之所在,并且解決它。

是以,如何定位線上的問題,并解決問題,是我們現在要讨論的重點。下面我們需要對幾個關鍵點進行分析:

① 某種錯誤發生的次數——發生次數跟影響使用者是成正比的, 如果發生次數跟影響使用者數量都很高,那麼這是一個比較嚴重的bug, 需要立即解決。 反之, 如果次數很多,影響使用者數量很少。說明這種錯誤隻發生在少量裝置中,優先級相對較低,可以擇時對該類機型裝置進行相容處理。當然,ip位址通路次數也能說明這個問題

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

② 頁面發生了哪些錯誤——這個有利于我們縮小問題的範圍,友善我們排查,如:

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

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

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

④ 裝置資訊——當錯誤發生是,分析出使用者當時使用裝置的浏覽器資訊,系統版本,裝置機型等等,能夠幫我們快速的定位到需要相容的裝置,進而提升解決問題的效率。

⑤ 使用者足迹——我個人覺得比較有用,但是代價太高。 因為這個需要記錄下使用者在頁面上的所有行為,需要上傳非常多的資料,功能待定。

這個功能已經在後邊進行完善了,點選 檢視足迹 按鈕即可查出這個用的行為足迹,在定位線上問題方面,有很大的作用 , 我在後邊的篇幅中有介紹

搭建前端監控系統(五)怎樣定位線上問題
一步一步搭建前端監控系統:JS錯誤監控篇一步一步搭建前端監控系統:JS錯誤監控篇

到此,已經收集到了JS錯誤日志的大部分資訊了,并且已經分析出JS錯誤的詳細資訊了。

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

既然我們已經具有了搜集js報錯和分析報錯的能力了,那麼我們也可以做到Js報錯實時監控,以及實時預警了,這樣可以防範線上事故于未然,及時的制止線上事故的持續發生, 減少損失。

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

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

關于Fundebug

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

繼續閱讀