这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
前言
上一篇讲了搭建前端监控系统的资源监控,因为本系列文章主要是讲关于前端收集数据的SDK的实现,这一篇来讲接口监控,对接口的监控主要是对接口的性能接口。
为什么要对接口进行监控
接口的性能监控在系统上我们可以在后端进行统计,但是在浏览器上发送请求时,可能会出现跨域问题,chrom也有自己的请求队列,并行请求机制。客户端可能会受到多种因素的影响,有时候可能会产生同一接口在不同客户端的表现不一样,所以在前端统计更能准确的得出接口在当前客户端的性能或出现的问题。
- 直观的得出接口的性能和可能会出现的的冗余重复请求,可以对应用层面得到精准的优化定位。
- 去除冗余请求
- 如果一个页面出现的请求数量过多则进行合并请求
- 及时定位线上产生的接口问题
- 甩锅
如何对接口进行监控
- 重写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
上报接口性能的数据结构
我们对资源需要进行上报的数据如下:
- duration : 开始发送到请求拿到数据的时长
- event: 最终请求的得到的类型 load/error/abort
- method: 请求方法
- requestSize: request请求题大小
- responseSize:response响应体大小
- status:状态码
- success: 是否成功
- type: 请求方式 (xhr/fetch)
- 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
})
}
}
复制代码
参考文章
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END