前端监控中的一项重要数据,就是请求数据html
下图描述了咱们对于请求数据一般有什么须要 前端
那么咱们如何才能作到对业务代码无侵入式的数据收集呢?json
方法很明确, 就是从新原生的api,在关键方法上进行作一层代理小程序
下面, 咱们就从这两个api的角度, 逐步分析如何实现api
通常封装的方法以下promise
// 首先将须要封装对象的原属性保存一个副本,用于代理以后调用
let xhr_open = XMLHttpRequest.prototype.open;
let xhr_send = XMLHttpRequest.prototype.send;
// 第二步,将原对象属性替换成代理对象
XMLHttpRequest.prototype.open = function (...args) {
// 在这里,咱们加入本身的获取逻辑
xhr_open.apply(this, args);
};
XMLHttpRequest.prototype.send = function (data) {
// 在这里,咱们加入本身的获取逻辑
xhr_send.apply(this, args);
}
复制代码
那么onreadystatechange事件如何处理呢浏览器
注意点: 该事件的监听须要在send方法发送以前 安全
咱们能够在封装send方法时,加入该事件的监听服务器
XMLHttpRequest.prototype.send = function (data) {
// 添加 readystatechange 事件
this.addEventListener('readystatechange', function () {
// 对请求结果作响应的处理
});
xhr_send.call(this, data);
};
复制代码
封装方法已经完成了, 那咱们该如何获取到第一张图 描述的信息呢?app
open方法的参数列表是固定的, 依次是method,url, async, username, password
在open代理过程当中,获取便可
XMLHttpRequest.prototype.open = function (...args) {
this.__monitor_xhr = {
method: args[0],
url: args[1]
}
xhr_open.apply(this, args);
};
复制代码
上面代码中,咱们在当前xhr对象上写入了一个新的属性,用于保存咱们获取到的信息。 请求body的数据,咱们在下面的分析中获取
这时候,咱们就须要对请求结果进行处理,获取咱们想要的数据
XMLHttpRequest.prototype.send = function (data) {
// 记录请求开始时间,用于计算耗时
const startTime = new Date();
// 添加 readystatechange 事件
this.addEventListener('readystatechange', function () {
try {
if (this.readyState === XMLHttpRequest.DONE) {
// 请求结束时间
const endTime = new Date();
// 请求耗时
this.__monitor.duration = (endTime - startTime) ;
// 请求body
this.__monitor.req_body = data;
// 获取response header、body等信息
}
} catch (err) {
}
}
});
xhr_send.call(this, data);
};
复制代码
上述response header信息,能够经过
xml.getAllResponseHeaders()
复制代码
获取, body信息能够经过如下方法获取
function getBody (xhrObject) {
var body = void 0;
// IE 11 sometimes throws when trying to access a large responses:
// https://connect.microsoft.com/IE/Feedback/Details/1053110
// gte IE10 will support responseType
try {
switch (xhrObject.responseType) {
case 'json':
case 'arraybuffer':
case 'blob':
{
body = xhrObject.response;
break;
}
case 'document':
{
body = xhrObject.responseXML;
break;
}
case 'text':
case '':
{
body = xhrObject.responseText;
break;
}
default:
{
body = '';
}
}
// When requesting binary data, IE6-9 will throw an exception
// on any attempt to access responseText (#11426)
if (!body && typeof xhrObject.responseText === "string" ) {
body = xhrObject.responseText;
}
} catch (err) {
body = 'monitor: Error accessing response.';
}
return body
}
复制代码
fetch因为api和xhr有很大差别, fetch返回了promise对象, 这种状况的封装
首先,咱们仍是须要对fetch加个代理, 方式相似xhr
// 首先保存原先的fetch 引用
let origFetch = window.fetch
window.fetch =function(fn, t) {
// 这边执行咱们的数据收集工做
return origFetch.apply(this, args)
};
复制代码
获取request 和response信息
window.fetch =function(fn, t) {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; ++i) {
args[i] = arguments[i];
}
var p = null;
// 因为fetch的参数列表更灵活, 因此须要对应的处理
if (typeof Request !== 'undefined' && args[0] instanceof Request) {
p = args[0].clone().text().then(function (body) {
return utils.extendsObjects({}, pluckFetchFields(args[0]), {
body: body
});
});
} else {
p = Promise.resolve(utils.extendsObjects({}, pluckFetchFields(args[1]), {
url: '' + args[0],
body: (args[1] || {}).body
}));
}
var fetchData = {
method: '',
url: '',
status_code: null,
start_time: new Date().getTime(),
request:{
headers: {},
body: ''
},
response:{
headers: {},
body: ''
},
timeline:{
dns:0,
connect:0,
response:0,
request: 0,
duration: 0
}
};
// 此处默认加一个then,对结果进行收集处理
return origFetch.apply(this, args).then(function(response) {
fetchData.status_code = response.status;
fetchData.duration = new Date().getTime() - fetchData.start_time
fetchData.timeline.duration = fetchData.duration
p.then(function(req) {
fetchData.method = req.method
fetchData.url = req.url
utils.objectMerge(fetchData.request, {mode: req.mode, referrer: req.referrer, credentials: req.credentials, headers: req.headers, body: req.body})
var clonedText = null;
try {
clonedText = response.clone().text();
} catch (err) {
// safari has a bug where cloning can fail
clonedText = Promise.resolve('Monitor fetch error: ' + err.message);
}
clonedText.then(function(body) {
fetchData.response.body = body
fetchData.response.headers = makeObjectFromHeaders(response.headers)
// 将数据发送到服务器
_reportToServer(fetchData)
})
})
return response;
});
};
复制代码
以上内容,咱们还缺乏了一步,那就是第一张图中的timeline数据, 该部分将和性能数据一块儿分析
安全, 咱们应该对涉及用户隐私的数据进行脱敏传输 或 不传输
对于response的数据, 咱们应该有须要的截取, 而不是一股脑的传输,防止无效数据太多
以上内容只是对浏览器请求数据收集和使用作了个大概的分享,除了浏览器外, 还有一些,如小程序的请求获取,会在小程序信息收集中分享