打造自己的前端监控---前端接口监控

前言

上一篇我们分享了如何进行前端的错误采集,今天我们来分析一下如何进行前端接口的采集。

数据结构

typescript 复制代码
export interface IApiContext {
  version: string, // 组件版本号
  globalSessionId: string, // 全局会话标识
  uniqueVisitorId: string, // 独立访客标识
  systemName: string, // 系统简称
  serviceName: string, // 服务名称
  referer?: string, // 引用
  userAgent: string, // 浏览器用户代理(浏览器标识)
  ip?: string, // ip地址
  generateTime: string | number, // 发生时间
  url: string, // 页面地址
  domain: string, // 页面域名
  page:string,
  title: string, // 页面标题
  reqURL: string, // 请求地址
  reqParam?: string, // 请求参数
  reqMsg?: string, // 请求报文
  reqTime: string | number,// 请求时间戳
  httpStatusCode?:string | number,
  respCode?: string, // 响应吗
  respDesc?: string, // 响应描述
  respMsg?: string, // 响应报文
  respTime?: string | number // 响应时间戳
}

如何实现采集

  • 重写xhr的Open方法、Send方法

  • 监听loadend事件

这些操作都是在原型上来完成的,其实这样操作我觉得类似切面(AOP),我门能在这里拦截所有的请求,能获取到所有请求的状态码,参数,计算请求耗时,响应时间-请求时间(当然这个耗时仅供参考),要想获取准确的耗时需要使用浏览器的performance里面的resource中的耗时,这里不在做过多描述,我们在其他文章里已经全面说过这个耗时。

同时我们为了前后端日志串联,在我们拦截的所有请求中添加自定义请求头,这个请求头的值是一个唯一的uuid,这样根据这个uuid可以把前后端日志进行串联,实现全端的链路追踪。

js 复制代码
export const ajaxInterceptorHandler = (options: IParams) => {

  try {
    if (!XMLHttpRequest) {
      return;
    }
    let originalXhrProto = XMLHttpRequest.prototype;
    let reqUrl: string = "";
    let xhrInstance = null;
    // @ts-ignore
    // 可以拿到请求信息
    replaceOld(originalXhrProto, EVENTTYPES.OPEN, (originalOpen: voidFun): voidFun => {
      return function (this: BTRACEXMLHttpReques, ...args: any[]): void {
        xhrInstance = this;
        xhrInstance.btrace = {
          url: args[1],
          reqId: uuid(),
          sendTime: getTimeStamp()
        };
        reqUrl = reqUrlTool(args[1]);
        originalOpen.apply(this, args);
      }
    })
    replaceOld(originalXhrProto, EVENTTYPES.SEND, (originalSend: voidFun): voidFun => {
      return function (this: BTRACEXMLHttpReques, ...args: any[]): void {
        isCludeUrl(this.btrace.url, options) && xhrInstance.setRequestHeader(HEADERS.XB3SESSIONId, getCookie(HEADERS.XB3SESSIONId));
        on(this, EVENTTYPES.LOADEND, function (this: BTRACEXMLHttpReques){
          if (options.apiRequestIsEnable) {
            if (isCludeUrl(this.btrace.url, options) && !this.btrace.timeout) {
               this.btrace.resTime = getTimeStamp();
               this.btrace.url = reqUrlTool(this.btrace.url);
               this.btrace.duration = this.btrace.resTime - this.btrace.sendTime;
               this.btrace.reqParam = getReqParams(this.btrace.url);
               createApiMsgData(xhrInstance, xhrInstance, options, ERROR_TYPE.AJAX);
            }

          }
        });
        on(this, EVENTTYPES.TIMEOUT, function (this: BTRACEXMLHttpReques){
          this.btrace.resTime = getTimeStamp();
          if (this && this.type === EVENTTYPES.TIMEOUT) {
            this.btrace.timeout = true;
            this.btrace.url = reqUrl;
            this.btrace.duration = this.btrace.resTime - this.btrace.sendTime;
            this.btrace.reqParam = getReqParams(this.btrace.url);
            createApiMsgData(xhrInstance, xhrInstance, options, ERROR_TYPE.AJAX);
          }
        })
        originalSend.apply(this, args);
      }
    })

  } catch (e) {
    console.warn("btrace:" + e);
  }

}
  • 重写fetch请求
js 复制代码
export const fetchInterceptorHandler = (options: IParams) => {

  try {

    if (!_global.fetch) {
      return;
    }

    replaceOld(_global, EVENTTYPES.FETCH, (originalFetch: voidFun) => {
      return function (url: string, config: Partial<RequestInit> = {}): void {
        let sendTime = getTimeStamp(); // 发送时间
        let reqId = uuid();
        const headers = config.headers || {};
        if (isCludeUrl(url, options)) {
          if (config && 'headers' in config) {
            headers[HEADERS.XB3SESSIONId] = getCookie(HEADERS.XB3SESSIONId);
          } else {
            let customHeader = {
              headers: {
                [HEADERS.XB3SESSIONId]: getCookie(HEADERS.XB3SESSIONId)
              }
            };
            config.headers = customHeader.headers;
          }
        }

        return originalFetch.apply(_global, [url, config]).then(
          (res: Response) => {
            let resTime = getTimeStamp();
            let duration = resTime - sendTime;
            const tempRes = res.clone();
            tempRes["btrace"] = {
              resTime: resTime,
              sendTime: sendTime,
              duration: duration,
              reqParam: getReqParams(url),
              reqId: reqId
            };
            if (options.apiRequestIsEnable) {
              createApiMsgData(tempRes, tempRes, options, ERROR_TYPE.FETCH)
            }

            return res;
          },
          (e: Error) => {
            if (options.apiRequestIsEnable) {
              if (isCludeUrl(url, options)) {
                e["status"] = 0;
                let resTime = getTimeStamp();
                let duration = resTime - sendTime;
                e["btrace"] = {
                  resTime: resTime,
                  sendTime: sendTime,
                  duration: duration,
                  reqParam: getReqParams(url),
                  reqId: reqId
                };
                createApiMsgData(e, e, options, ERROR_TYPE.FETCH)
              }
            }
            throw e;
          }
        )
      }
    })
  } catch (e) {
    console.log('ee', e);
  }

}

总结

基于以上方式我们完成了对请求的拦截并添加了我们自己的需求,在这里获取请求状态码、计算请求耗时、请求的唯一标识(这个标识能起大作用)。我们也完成了线上环境的验证,目前正在推广使用中。

相关推荐
福兮说8 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理8 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6669 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
IT大白鼠9 小时前
图数据库系列 · 第 02 篇——架构拆解:原生图存储到因果集群
数据库·架构·nosql
凤城老人9 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)9 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan10 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
mftang10 小时前
CANopen协议:基于CAN的高层协议架构、通信模型与工业应用深度解析
架构·canopen·对象字典·canopen fd
可乐鸡翅yeah_10 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线