前端監控嘗試

背景

公司舉行黑客馬拉松其中一個賽題就是進行前端錯誤和性能監控,以前一直想過作這麼一件事情,當線上發生代碼錯誤或者資源引用錯誤時不是經過用戶反饋得知,而是開發人員能在第一時間知曉,此次終於有機會進行嘗試,與另外兩名前端同事共同進行了一天一晚上的代碼長跑。javascript

設計方案

  • 監控哪些

前端監控通常分爲錯誤監控和性能監控,而咱們這次也是主要從這兩方面進行監控。html

  • 與項目集成方式

集成方式考慮到有非侵入式集成(SDK)和侵入式集成,分析二者利弊後決定採用非侵入式集成,但願能夠像JQuery同樣,經過一行代碼直接引入,在項目中直接使用,最後的理想狀態就是:一行代碼,開箱即用。前端

類型 優勢 缺點
非侵入式 主動監測,指標齊全 沒法監控複雜應用、監控的數據可能較少,沒法捕獲已經try-catch的數據
侵入式 能夠監控複雜應用,進行細緻監控 須要侵入源代碼
  • 指標的上報、存儲與展現

監控sdk收到的信息經過指定接口(性能和錯誤接口分開)上報給後臺,後臺使用MongoDB進行存儲和數據的處理,前臺頁面進行數據的直觀顯示。vue

監控數據收集方式

錯誤處理

  • js錯誤監控方式

咱們能夠經過try/catch方式進行錯誤的捕獲,可是考慮到無侵入式的原則,最後選擇使用全局的error事件進行捕獲錯誤。html5

var handleWindowError = function (_window, config) {
  _oldWindowError = _window.onerror;
  _window.onerror = function (msg, url, line, col, error) {
    // console.log(error);
    var eventId = `${url}${line}${col}`
    config.sendError({
      title: msg,
      msg: {
        resourceUrl: url,
        rowNum: line,
        colNum: col,
        info: error,
        filename: url,
        eventId: eventId,
      },
      category: 'js',
      level: 'error'
    })
    if (_oldWindowError && isFunction(_oldWindowError)) {
      windowError && windowError.apply(window, arguments);
    }
  }
}
複製代碼
  • 異步代碼錯誤監控

經過監聽全局'unhandledrejection'事件能夠捕獲未處理的 reject 。java

var handleRejectPromise = function (_window, config) {
  _window.addEventListener('unhandledrejection', function (event) {
    if (event) {
      var reason = event.reason;
      config.sendError({
        title: '不捕獲Promise異步錯誤',
        msg: reason,
        category: 'js',
        level: 'error'
      });
    }
  }, true);
}

複製代碼
  • 資源請求錯誤

當頁面中如【img src='./404.png'】引入不存在的資源時,會出現未找到資源的錯誤,若是在引入未找到的js將會形成頁面嚴重錯誤。 所以能夠經過監控監聽到的錯誤是否含有html標籤,而且標籤中是否有src或者href屬性進行資源請求的監控。node

var handleResourceError = function (_window, config) {
  _window.addEventListener('error', function (event) {
    if (event) {
      var target = event.target || event.srcElement;
      var isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
      if (!isElementTarget) return; // js error再也不處理

      var url = target.src || target.href;
      config.sendError({
        title: target.nodeName,
        msg: {
          url: url,
          eventId: url,
        },
        category: 'resource',
        level: 'error',
      });
    }
  }, true);
}

複製代碼
  • 請求錯誤

經過監控原生XMLHttpRequest屬性進行ajax請求的監控。git

var handleAjaxError = function (_window, config) {
  var protocol = _window.location.protocol;
  if (protocol === 'file:') return;

  // 處理fetch
  _handleFetchError(_window, config);

  // 處理XMLHttpRequest
  if (!_window.XMLHttpRequest) {
    return;
  }
  var xmlhttp = _window.XMLHttpRequest;

  var _oldSend = xmlhttp.prototype.send;
  var _handleEvent = function (event) {
    if (event && event.currentTarget && event.currentTarget.status !== 200) {
      config.sendError({
        title: event.target.responseURL,
        msg: {
          response: event.target.response,
          responseURL: event.target.responseURL,
          status: event.target.status,
          statusText: event.target.statusText,
          eventId: event.target.responseURL,
        },
        category: 'ajax',
        level: 'error'
      });
    }
  }
}

複製代碼
  • 控制檯錯誤監控

經過監控瀏覽器控制檯輸出的錯誤日誌進行錯誤的捕獲。github

var handleConsoleError = function (_window, config) {
  if (!_window.console || !_window.console.error) return;

  var _oldConsoleError = _window.console.error;
  _window.console.error = function () {
    config.sendError({
      title: 'consoleError',
      msg: JSON.stringify(arguments),
      category: 'js',
      level: 'error'
    });
    _oldConsoleError && _oldConsoleError.apply(_window, arguments);
  };
}

複製代碼
  • vue經過指定鉤子函數進行錯誤監控

vue中有指定的鉤子函數errorHandler進行錯誤的監控,能夠經過監控errorHandler收集的錯誤進行錯誤的監控。ajax

var handleVueError = function (_window, config) {
  var vue = _window.Vue;
  if (!vue || !vue.config) return; // 沒有找到vue實例
  
  var _oldVueError = vue.config.errorHandler;

  Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
    var metaData = {};
    if (Object.prototype.toString.call(vm) === '[object Object]') {
      metaData.componentName = vm._isVue ? vm.$options.name || vm.$options._componentTag : vm.name;
      metaData.propsData = vm.$options.propsData;
    }
    config.sendError({
      title: 'vue Error',
      msg: {
        meta: metaData,
        info,
      },
      category: 'js',
      level: 'error'
    });

    if (_oldVueError && isFunction(_oldVueError)) {
      _oldOnError.call(this, error, vm, info);
    }
  };
}

複製代碼

性能監控

  • 性能監控

    主要監測當前頁面

    1. 頁面徹底加載時間
    2. HTTP請求響應完成時間
    3. 腳本加載時間
    4. DOM加載完成時間
    5. onload事件時間 ...

    主要經過window.performance進行頁面性能監控。

使用

直接在項目首頁進行引入,會在建立項目時生成如下代碼,一行代碼,開箱即用。(示例代碼僅供參考)

<script src="https://kylenxu.github.io/monitor-td/index.js?errorMonitor=true&performanceMonitor=true&projectId=82b12c829722d727e6ca40b8aa166e43&name=test&errorUrl=http://172.30.104.166:8038/api/errors&performanceError=http://172.30.104.166:8038/api/performance&vue=true&js=true"></script>
複製代碼

字段說明

對應上面URL地址中參數:

屬性 說明 類型 默認值
projectId 項目ID String
name 項目名稱 String
errorUrl 錯誤監控數據上報地址 String
performanceError 性能監控數據上報地址 String
errorMonitor 是否進行錯誤監控 Boolean true
performanceMonitor 是否進行性能監控 Boolean true
vue 是否進行VUE項目監控 Boolean true
js 是否進行JS項目監控 Boolean true

特色

  • js語法報錯

    主要經過window.onerror()進行頁面錯誤監控。

  • 異步代碼運行報錯

    主要經過window.addEventListener('unhandledrejection',fn());進行頁面異步代碼錯誤監控。

  • 資源加載報錯

    主要經過window.addEventListener('error',fn());進行頁面異步代碼錯誤監控。

  • 接口請求報錯

    主要經過window.fetch();進行接口請求錯誤監控。

  • ajax請求報錯

    主要經過對window.XMLHttpRequest監控,進行ajax接口請求錯誤監控。

  • 控制檯錯誤信息

    主要經過window.console.error();進行控制檯錯誤監控。

    注:html5: console.error會打印日誌,顯示紅色的錯誤信息,但不會阻擋對下面js的執行。

    windows: onsole.error會阻擋程序執行,js異常就是語法或邏輯錯誤,好比 this._btn1 = abc; //abc是不存在這樣會阻擋後面語句的執行,不單阻擋了本函數,函數的外邊的後面代碼也不執行了,也就是本次調用堆棧就報廢了。

  • VUE錯誤監控

    主要經過Vue.config.errorHandler();鉤子函數進行VUE錯誤監控。

  • 性能監控

    主要檢測當前頁面徹底加載時間、HTTP請求響應完成時間、腳本加載時間等信息。

    主要經過window.performance進行頁面性能監控。

總結

最終實現了一個最小可用的前端監控系統,經過一行代碼直接引入,在項目中直接使用,實現了最終的理想狀態:一行代碼,開箱即用。

致敬

  • 一塊兒熬夜的兄弟們
相關文章
相關標籤/搜索