共计 5043 个字符,预计需要花费 13 分钟才能阅读完成。
摘要: 如何监控 HTTP 请求错误?
- 作者:一步一个脚印一个坑
- 原文:搭建前端监控系统(四)接口请求异常监控篇
Fundebug 经授权转载,版权归原作者所有。
背景:市面上的监控系统有很多,大多收费,对于小型前端项目来说,必然是痛点。另一点主要原因是,功能虽然通用,却未必能够满足我们自己的需求, 所以我们自给自足也许是个不错的办法。
这是搭建前端监控系统的第四章,主要是介绍如何统计静态资源加载报错,跟着我一步步做,你也能搭建出一个属于自己的前端监控系统。
上一章介绍了如何统计静态资源加载报错,今天要说的是接口请求报错。可能有人会认为接口的报错应该由后台来关注,统计,并修复。确实如此,而且后台服务有了很多成熟完善的统计工具,完全能够应对大部分的异常情况,那么为什么还需要前端对接口请求进行监控呢。原因很简单,因为前端是 bug 的第一发现位置,在你帮后台背锅之前怎么快速把过甩出去呢,这时候,我们就需要有一个接口的监控系统,哈哈:)
- 我们要监控接口报错的情况,及时定位线上问题产生的原因
- 我们要分析接口的性能,以辅助我们对前端应用的优化。
好了,进入正题吧:
如何监控前端接口请求呢?
一般前端请求都是用 jquery 的 ajax 请求,也有用 fetch 请求的,以及前端框架自己封装的请求等等。总之他们封装的方法各不相同,但是万变不离其宗,他们都是对浏览器的这个对象 window.XMLHttpRequest 进行了封装,所以我们只要能够监听到这个对象的一些事件,就能够把请求的信息分离出来。
1. 如何监听 ajax 请求
如果你用的 jquery、zepto、或者自己封装的 ajax 方法,就可以用如下的方法进行监听。我们监听 XMLHttpRequest 对象的两个事件 loadstart,loadend。但是监听的结果并不是像我们想象的那么容易理解,我们先看下 ajaxLoadStart,ajaxLoadEnd 的回调方法。
/**
* 页面接口请求监控
*/
function recordHttpLog() {
// 监听 ajax 的状态
function ajaxEventTrigger(event) {
var ajaxEvent = new CustomEvent(event, {detail: this});
window.dispatchEvent(ajaxEvent);
}
var oldXHR = window.XMLHttpRequest;
function newXHR() {var realXHR = new oldXHR();
realXHR.addEventListener(
"loadstart",
function() {ajaxEventTrigger.call(this, "ajaxLoadStart");
},
false
);
realXHR.addEventListener(
"loadend",
function() {ajaxEventTrigger.call(this, "ajaxLoadEnd");
},
false
);
// 此处的捕获的异常会连日志接口也一起捕获,如果日志上报接口异常了,就会导致死循环了。// realXHR.onerror = function () {// siftAndMakeUpMessage("Uncaught FetchError: Failed to ajax", WEB_LOCATION, 0, 0, {});
// }
return realXHR;
}
var timeRecordArray = [];
window.XMLHttpRequest = newXHR;
window.addEventListener("ajaxLoadStart", function(e) {
var tempObj = {timeStamp: new Date().getTime(),
event: e
};
timeRecordArray.push(tempObj);
});
window.addEventListener("ajaxLoadEnd", function() {for (var i = 0; i < timeRecordArray.length; i++) {if (timeRecordArray[i].event.detail.status > 0) {var currentTime = new Date().getTime();
var url = timeRecordArray[i].event.detail.responseURL;
var status = timeRecordArray[i].event.detail.status;
var statusText = timeRecordArray[i].event.detail.statusText;
var loadTime = currentTime - timeRecordArray[i].timeStamp;
if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return;
var httpLogInfoStart = new HttpLogInfo(
HTTP_LOG,
url,
status,
statusText,
"发起请求",
timeRecordArray[i].timeStamp,
0
);
httpLogInfoStart.handleLogInfo(HTTP_LOG, httpLogInfoStart);
var httpLogInfoEnd = new HttpLogInfo(
HTTP_LOG,
url,
status,
statusText,
"请求返回",
currentTime,
loadTime
);
httpLogInfoEnd.handleLogInfo(HTTP_LOG, httpLogInfoEnd);
// 当前请求成功后就在数组中移除掉
timeRecordArray.splice(i, 1);
}
}
});
}
一个页面上会有很多个请求,当一个页面发出多个请求的时候,ajaxLoadStart 事件被监听到,但是却无法区分出来到底发送的是哪个请求,只返回了一个内容超多的事件对象,而且事件对象的内容几乎完全一样。当 ajaxLoadEnd 事件被监听到的时候,也会返回一个内容超多的时间对象,这个时候事件对象里包含了接口请求的所有信息。幸运的是,两个对象是同一个引用,也就意味着,ajaxLoadStart 和 ajaxLoadEnd 事件被捕获的时候,他们作用的是用一个对象。那我们就有办法分析出来了。
当 ajaxLoadStart 事件发生的时候,我们将回调方法中的事件对象全都放进数组 timeRecordArray 里,当 ajaxLoadEnd 发生的时候,我们就去遍历这个数据,遇到又返回结果的事件对象,说明接口请求已经完成,记录下来,并从数组中删除该事件对象。这样我们就能够逐一分析出接口请求的内容了。
如何监听 fetch 请求
通过第一种方法,已经能够监听到大部分的 ajax 请求了。然而,使用 fetch 请求的人越来越多,因为 fetch 的链式调用可以让我们摆脱 ajax 的嵌套地狱,被更多的人所青睐。奇怪的是,我用第一种方式,却无法监听到 fetch 的请求事件,这是为什么呢?
return new Promise(function(resolve, reject) {var request = new Request(input, init);
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || "")
};
options.url =
"responseURL" in xhr
? xhr.responseURL
: options.headers.get("X-Request-URL");
var body = "response" in xhr ? xhr.response : xhr.responseText;
resolve(new Response(body, options));
};
// .......
xhr.send(typeof request._bodyInit === "undefined" ? null : request._bodyInit);
});
这个是 fetch 的一段源码,可以看到,它创建了一个 Promise, 并新建了一个 XMLHttpRequest 对象 var xhr =newXMLHttpRequest()。由于 fetch 的代码是内置在浏览器中的,它必然先用监控代码执行,所以,我们在添加监听事件的时候,是无法监听 fetch 里边的 XMLHttpRequest 对象的。怎么办呢,我们需要重写一下 fetch 的代码。只要在监控代码执行之后,我们重写一下 fetch,就可以正常监听使用 fetch 方式发送的请求了。就这么简单:)
看一下需要监听的字段:
// 设置日志对象类的通用属性
function setCommonProperty() {this.happenTime = new Date().getTime(); // 日志发生时间
this.webMonitorId = WEB_MONITOR_ID; // 用于区分应用的唯一标识(一个项目对应一个)this.simpleUrl = window.location.href.split("?")[0].replace("#", ""); // 页面的 url
this.completeUrl = utils.b64EncodeUnicode(encodeURIComponent(window.location.href)
); // 页面的完整 url
this.customerKey = utils.getCustomerKey(); // 用于区分用户,所对应唯一的标识,清理本地数据后失效,// 用户自定义信息,由开发者主动传入,便于对线上问题进行准确定位
var wmUserInfo = localStorage.wmUserInfo
? JSON.parse(localStorage.wmUserInfo)
: "";
this.userId = utils.b64EncodeUnicode(wmUserInfo.userId || "");
this.firstUserParam = utils.b64EncodeUnicode(wmUserInfo.firstUserParam || "");
this.secondUserParam = utils.b64EncodeUnicode(wmUserInfo.secondUserParam || "");
}
// 接口请求日志,继承于日志基类 MonitorBaseInfo
function HttpLogInfo(
uploadType,
url,
status,
statusText,
statusResult,
currentTime,
loadTime
) {setCommonProperty.apply(this);
this.uploadType = uploadType; // 上传类型
this.httpUrl = utils.b64EncodeUnicode(encodeURIComponent(url)); // 请求地址
this.status = status; // 接口状态
this.statusText = statusText; // 状态描述
this.statusResult = statusResult; // 区分发起和返回状态
this.happenTime = currentTime; // 客户端发送时间
this.loadTime = loadTime; // 接口请求耗时
}
所有工作准备完毕,如果把收集到的日志从不同的维度展现出来,我就不细说了,直接上图了。如此,便能够对前端接口报错的情况有一个清晰的了解,也能够快速的发现线上的问题。
参考
- 一步一步搭建前端监控系统:JS 错误监控篇
- 用 Fundebug 插件记录网络请求异常
关于 Fundebug
Fundebug 专注于 JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js 和 Java 线上应用实时 BUG 监控。自从 2016 年双十一正式上线,Fundebug 累计处理了 10 亿 + 错误事件,付费客户有阳光保险、核桃编程、荔枝 FM、掌门 1 对 1、微脉、青团社等众多品牌企业。欢迎大家免费试用!