前端监控系统之接口监控

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

前言

上一篇讲了搭建前端监控系统的资源监控,因为本系列文章主要是讲关于前端收集数据的SDK的实现,这一篇来讲接口监控,对接口的监控主要是对接口的性能接口。

为什么要对接口进行监控

接口的性能监控在系统上我们可以在后端进行统计,但是在浏览器上发送请求时,可能会出现跨域问题,chrom也有自己的请求队列,并行请求机制。客户端可能会受到多种因素的影响,有时候可能会产生同一接口在不同客户端的表现不一样,所以在前端统计更能准确的得出接口在当前客户端的性能或出现的问题。

  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的请求状态, 有如下钩子函数

    • 请求完成xhr.onload
    • 请求结束 xhr.onloadend
    • 请求出错 xhr.onerror
    • 请求超时 xhr.ontimeout
  • xhr其他方法

    • 设置超时事件 xhr.timeout = number 设置为0则表示前端永不超时

    • 设置期望的返回数据,chrom会对不同的reopenseType进行不同的处理

      xhr.responseType = 'json';

    • 设置请求头 xhr.setRequestHeader('Content-type', 'application/json');

fetch

摘自MDN:

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享