前端監控系統之接口監控

這是我參與8月更文挑戰的第4天,活動詳情查看:8月更文挑戰前端

前言

上一篇講了搭建前端監控系統的資源監控,由於本系列文章主要是講關於前端收集數據的SDK的實現,這一篇來說接口監控,對接口的監控主要是對接口的性能接口。ios

爲何要對接口進行監控

接口的性能監控在系統上咱們能夠在後端進行統計,可是在瀏覽器上發送請求時,可能會出現跨域問題,chrom也有本身的請求隊列,並行請求機制。客戶端可能會受到多種因素的影響,有時候可能會產生同一接口在不一樣客戶端的表現不同,因此在前端統計更能準確的得出接口在當前客戶端的性能或出現的問題。ajax

  1. 直觀的得出接口的性能和可能會出現的的冗餘重複請求,能夠對應用層面獲得精準的優化定位。
    1. 去除冗餘請求
    2. 若是一個頁面出現的請求數量過多則進行合併請求
  2. 及時定位線上產生的接口問題
  3. 甩鍋

如何對接口進行監控

  • 重寫window.XMLHttpRequest 和 window.fetch 能夠對項目進行無痕埋點,沒有侵入業務代碼。
  • 咱們最經常使用的axios就是重寫的XMLHttpRequest。

XMLHttpRequest (2.0標準)

  • xhr發送請求
let xhr = new XMLHttpRequest();
xhr.open('GET', '/url', true);
xhr.send();
複製代碼
  • 獲取xhr的請求狀態, 有以下鉤子函數json

    • 請求完成xhr.onload
    • 請求結束 xhr.onloadend
    • 請求出錯 xhr.onerror
    • 請求超時 xhr.ontimeout
  • xhr其餘方法axios

    • 設置超時事件 xhr.timeout = number 設置爲0則表示前端永不超時後端

    • 設置指望的返回數據,chrom會對不一樣的reopenseType進行不一樣的處理跨域

      xhr.responseType = 'json';promise

    • 設置請求頭 xhr.setRequestHeader('Content-type', 'application/json');瀏覽器

fetch

摘自MDN:markdown

Fetch API 提供了一個 JavaScript 接口,用於訪問和操縱 HTTP 管道的一些具體部分,例如請求和響應。它還提供了一個全局 fetch() 方法,該方法提供了一種簡單,合理的方式來跨網絡異步獲取資源。

fetch返回的是promise, fetch相較於XMLHttpRequest有不少不一樣區別,在MDN上有詳細的介紹。fetch

上報接口性能的數據結構

咱們對資源須要進行上報的數據以下:

  1. duration : 開始發送到請求拿到數據的時長
  2. event: 最終請求的獲得的類型 load/error/abort
  3. method: 請求方法
  4. requestSize: request請求題大小
  5. responseSize:response響應體大小
  6. status:狀態碼
  7. success: 是否成功
  8. type: 請求方式 (xhr/fetch)
  9. url: 請求地址

設計代碼

  • 咱們須要在XMLHttpRequest上打一個標記以代表這個請求是咱們監控了的。
  • 重寫xhr.prototype.open,掛載open函數中能拿到的method、url
  • 重寫xhr.prototype.send方法,掛載在send 成功/失敗/掛起 的到的數據
// xhr hook
    let xhr = window.XMLHttpRequest
    if (xhr._myxhr_flag === true) {
      return void 0
    }
    xhr._myxhr_flag = true

    let _originOpen = xhr.prototype.open
    xhr.prototype.open = function (method, url, async, user, password) {
      // TODO myxhr url check
      this._myxhr_xhr_info = {
        url: url,
        method: method,
        status: null
      }
      return _originOpen.apply(this, arguments)
    }

    let _originSend = xhr.prototype.send
    xhr.prototype.send = function (value) {
      let _self = this
      this._myxhr_start_time = Date.now()

      let ajaxEnd = event => () => {
        if (_self.response) {
          let responseSize = null
          switch (_self.responseType) {
            case 'json':
              responseSize = JSON && JSON.stringify(_this.response).length
              break
            case 'blob':
            case 'moz-blob':
              responseSize = _self.response.size
              break
            case 'arraybuffer':
              responseSize = _self.response.byteLength
            case 'document':
              responseSize =
                _self.response.documentElement &&
                _self.response.documentElement.innerHTML &&
                _self.response.documentElement.innerHTML.length + 28
              break
            default:
              responseSize = _self.response.length
          }
          _self._myxhr_xhr_info.event = event
          _self._myxhr_xhr_info.status = _self.status
          _self._myxhr_xhr_info.success =
            (_self.status >= 200 && _self.status <= 206) || _self.status === 304
          _self._myxhr_xhr_info.duration = Date.now() - _self._myxhr_start_time
          _self._myxhr_xhr_info.responseSize = responseSize
          _self._myxhr_xhr_info.requestSize = value ? value.length : 0
          _self._myxhr_xhr_info.type = 'xhr'
          cb(this._myxhr_xhr_info)
        }
      }

      // TODO myxhr url check
      this.addEventListener('load', ajaxEnd('load'), false)
      this.addEventListener('error', ajaxEnd('error'), false)
      this.addEventListener('abort', ajaxEnd('abort'), false)

      return _originSend.apply(this, arguments)
    }
複製代碼

重寫fetch,添加了攔截函數

// fetch hook
		if (window.fetch) {
			let _origin_fetch = window.fetch
			window.fetch = function () {
				let startTime = Date.now()
				let args = [].slice.call(arguments)

				let fetchInput = args[0]
				let method = 'GET'
				let url

				if (typeof fetchInput === 'string') {
					url = fetchInput
				} else if ('Request' in window && fetchInput instanceof window.Request) {
					url = fetchInput.url
					if (fetchInput.method) {
						method = fetchInput.method
					}
				} else {
					url = '' + fetchInput
				}

				if (args[1] && args[1].method) {
					method = args[1].method
				}

				// TODO eagle check
				let fetchData = {
					method: method,
					url: url,
					status: null
				}

				return _origin_fetch.apply(this, args).then(function (response) {
					fetchData.status = response.status
					fetchData.type = 'fetch'
					fetchData.duration = Date.now() - startTime
					cb(fetchData)
					return response
				})
			}
		}
複製代碼

參考文章

XMLHttpRequest

fetch

相關文章
相關標籤/搜索