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

前言

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

数据结构

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);
  }

}

总结

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

相关推荐
素界UI设计31 分钟前
开源网页生态掘金:从Bootstrap二次开发到行业专属组件库的技术变现
前端·开源·bootstrap
潘小安33 分钟前
【译】六个开发高手使用的 css 动画秘诀
前端·css·性能优化
前端开发爱好者44 分钟前
尤雨溪官宣:Vite 历史性的一刻!超越 Webpack!
前端·javascript·vite
毛小茛44 分钟前
认识微服务
微服务·云原生·架构
前端开发爱好者1 小时前
Vue3 "抛弃" Axios !用上了 专属请求库!
前端·javascript·vue.js
前端开发爱好者1 小时前
"Lodash" 的终极版!Vue、React 通杀!
前端·javascript·全栈
前端开发爱好者1 小时前
TanStack:不止于 Vue!一个库,真·通杀所有框架!
前端·javascript·vue.js
curdcv_po1 小时前
Three.js,给纹理,设颜色空间
前端
站大爷IP1 小时前
HTTPS代理抓包完全攻略:工具、配置与高级技巧
前端
洛卡卡了1 小时前
“改个配置还要发版?”搞个配置后台不好吗
前端·后端·架构