收集请求数据集详情-前端监控之数据采集篇
前端监控中的一项重要数据,就是请求数据
第一,我们先要清楚,对于请求,我们通常需要哪些信息
下图描述了我们对于请求数据通常有什么需要
对于浏览器而言, 主要有xmlHttpRequest和fetch两个请求api(ws暂未考虑),
那么我们如何才能做到对业务代码无侵入式的数据收集呢?
方法很明确, 就是重新原生的api,在关键方法上进行做一层代理
下面, 我们就从这两个api的角度, 逐步分析如何实现
a. xmlHttpRequest
上图可以发现, 主要方法就是open和send, 主要事件为onreadystatechange, 我们只需要对这三个属性进行包装,就可以轻松获取xhr相关的消息
一般封装的方法如下
// 首先将需要封装对象的原属性保存一个副本,用于代理之后调用 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); };
封装方法已经完成了, 那我们该如何获取到第一张图 描述的信息呢?
① request信息
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的数据,我们在下面的分析中获取
② reponse 和 timeline信息
这时候,我们就需要对请求结果进行处理,获取我们想要的数据
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
}
b. fetch
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数据, 该部分将和性能数据一起分析
第二,对于这些数据,我们又用来做些什么
1. 请求数据反映了用户轨迹的一部分
2. 请求信息可以帮助我们定位问题
3. 通过分析请求的成功率,及时发现服务端问题
第三, 注意点
1. 安全, 我们应该对涉及用户隐私的数据进行脱敏传输 或 不传输
2. 对于response的数据, 我们应该有需要的截取, 而不是一股脑的传输,防止无效数据太多
标题:收集请求数据集详情-前端监控之数据采集篇
作者:hugh0524
地址:https://blog.uproject.cn/articles/2019/07/24/1563983351674.html
0 0