一步一步搭建前端监控系统:接口请求异常监控篇

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
应用实时监控服务-应用监控,每月50GB免费额度
应用实时监控服务-用户体验监控,每月100OCU免费额度
简介: 摘要: 如何监控HTTP请求错误?作者:一步一个脚印一个坑原文:搭建前端监控系统(四)接口请求异常监控篇Fundebug经授权转载,版权归原作者所有。背景:市面上的监控系统有很多,大多收费,对于小型前端项目来说,必然是痛点。

摘要: 如何监控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; // 接口请求耗时
}

所有工作准备完毕,如果把收集到的日志从不同的维度展现出来,我就不细说了,直接上图了。如此,便能够对前端接口报错的情况有一个清晰的了解,也能够快速的发现线上的问题。

参考

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
目录
相关文章
|
2月前
|
移动开发 监控 Android开发
Android & iOS 使用 ARMS 用户体验监控(RUM)的最佳实践
本文主要介绍了 ARMS 用户体验监控的基本功能特性,并介绍了在几种常见场景下的最佳实践。
364 14
|
4月前
|
运维 监控 数据可视化
ARMS的微服务监控
【8月更文挑战第23天】
78 6
|
22天前
|
缓存 监控 前端开发
基于 CoffeeScript 的内网管理监控软件前端代码优化
本文探讨了基于CoffeeScript的内网管理监控软件前端代码优化方法,包括数据请求缓存、界面更新采用虚拟DOM技术以及增强错误处理机制,旨在提升软件性能、响应速度和用户体验。
34 4
|
4月前
|
监控 前端开发 JavaScript
ARMS的Web应用监控
【8月更文挑战第23天】
76 8
|
4月前
|
监控 JavaScript 前端开发
ARMS的移动应用监控
【8月更文挑战第23天】
91 6
|
17天前
|
监控 开发工具 Android开发
ARMS 用户体验监控正式发布原生鸿蒙应用 SDK
阿里云 ARMS 用户体验监控(RUM)推出了针对原生鸿蒙应用的 SDK。SDK 使用 ArkTS 语言开发,支持页面采集、资源加载采集、异常采集及自定义采集等功能,能够全面监控鸿蒙应用的表现。集成简单,只需几步即可将 SDK 接入项目中,为鸿蒙应用的开发者提供了强有力的支持。
|
4月前
|
前端开发 JavaScript Java
【前端学java】Java中的接口和枚举概念(8)
【8月更文挑战第9天】Java中的接口和枚举概念(8)
53 4
|
20天前
|
监控 前端开发 JavaScript
OCamlScript 用于局域网电脑监控软件前端开发的探索
在数字化时代,局域网电脑监控软件广泛应用于企业管理和教育机构。OCamlScript 作为新兴技术,在此类软件的前端开发中展现出独特潜力,特别是在网络请求、界面更新及用户交互处理方面。通过合理利用其特性,可构建高效稳定的前端应用,提升用户体验,具有广阔的应用前景。
34 9
|
1月前
|
存储 Prometheus 运维
在云原生环境中,阿里云ARMS与Prometheus的集成提供了强大的应用实时监控解决方案
在云原生环境中,阿里云ARMS与Prometheus的集成提供了强大的应用实时监控解决方案。该集成结合了ARMS的基础设施监控能力和Prometheus的灵活配置及社区支持,实现了全面、精准的系统状态、性能和错误监控,提升了应用的稳定性和管理效率。通过统一的数据视图和高级查询功能,帮助企业有效应对云原生挑战,促进业务的持续发展。
39 3
|
2月前
|
存储 前端开发 JavaScript
前端的全栈之路Meteor篇(四):RPC方法注册及调用-更轻量的服务接口提供方式
RPC机制通过前后端的`callAsync`方法实现了高效的数据交互。后端通过`Meteor.methods()`注册方法,支持异步操作;前端使用`callAsync`调用后端方法,代码更简洁、易读。本文详细介绍了Methods注册机制、异步支持及最佳实践。

热门文章

最新文章