行为埋点体系

113 阅读3分钟

目前,埋点一般分为,代码埋点,零代码,服务端埋点

代码埋点的话一般在实际业务中是最为常用的,但是有一个弊端在于,容易错埋 埋点: 一种收集用户特定信息工具

用于分析,模块使用情况,是否如何业务需求,也能辅助排查用户异常问题,通过行为栈,就能有效复现步骤,
一般分类为 行为监控,数据监控,错误监控

数据监控

  1. 关注,pv,uv 访问来源,行为栈,操作系统,浏览器等信息。

对于数据这块发送

  1. xmlRequest/fetch 传统,发起 ajax 请求,兼容性好,可能阻塞渲染。
  1. sendbeacon,方便,兼容性差
  1. img 发送。支持跨域,发生数据有限。

行为埋点

  1. 代码埋点
  1. 可视化埋点
  1. 服务端埋点
  1. 全埋点

行为监控sdk

用户信息

export interface PageInformation {
  host: string;
  hostname: string;
  href: string;
  protocol: string;
  origin: string;
  port: string;
  pathname: string;
  search: string;
  hash: string;
  // 网页标题
  title: string;
  // 浏览器的语种 (eg:zh) ; 这里截取前两位,有需要也可以不截取
  language: string;
  // 用户 userAgent 信息
  userAgent?: string;
  // 屏幕宽高 (eg:1920x1080)  屏幕宽高意为整个显示屏的宽高
  winScreen: string;
  // 文档宽高 (eg:1388x937)   文档宽高意为当前页面显示的实际宽高(有的同学喜欢半屏显示)
  docScreen: string;
}


// 获取用户 信息
  getPageInfo = (): PageInformation => {
    const { host, hostname, href, protocol, origin, port, pathname, search, hash } = window.location;
    const { width, height } = window.screen;
    const { language, userAgent } = navigator;

    return {
      host,
      hostname,
      href,
      protocol,
      origin,
      port,
      pathname,
      search,
      hash,
      title: document.title,
      language: language.substr(0, 2),
      userAgent,
      winScreen: `${width}x${height}`,
      docScreen: `${document.documentElement.clientWidth || document.body.clientWidth}x${document.documentElement.clientHeight || document.body.clientHeight
        }`,
    };
  };

用户行为记录栈

  1. 虽然错误监控,能够捕获,对应错误信息,但是具体何种操作逻辑导致的,最好还是能记录用户操作逻辑,方便问题排查。
  2. demo示例
    class Stack {
      constructor(options: behaviorRecordsOptions) {
        this.maxBehaviorRecords = options.maxBehaviorRecords || 5;
        this.stack = []
      }
      private maxBehaviorRecords: number;
      private stack: Array<behaviorStack>

      push = (item) => {
        if (this.stack.length >= this.maxBehaviorRecords) {
          this.stack.shift()
        }
        this.stack.push(item)
      }

      pop = () => {
        return this.stack.pop()
      }
      shift = () => {
        this.stack.shift()

      } 
      getStack = () => {
        return this.stack.length
      }
      clear = () => {
        this.stack = []
      }
    }

  }


路由切换

一般常见的路由其实就是一个是hash 路由,history路由 hash路由,常见就是会去触发hashChange事件,但是会先触发popState因此统一监听 但是对于history路由,对于replaceState 和 pushState 不会触发popState ,因此为了统一,重新构建一个自定义事件 主体思路,得先去统一创建自定义事件,并进行监听所有路由触发事件,并统一执行对应回调,返回统一格式的信息


  proxyRouter = (type: RouterType) => {
    let newEvent = new Event(type);
    window.dispatchEvent(newEvent)
  }

  proxyHistory = (handler: Function): void => {
    // 添加对 replaceState 的监听
    window.addEventListener('replaceState', (e) => handler(e), true);
    // 添加对 pushState 的监听
    window.addEventListener('pushState', (e) => handler(e), true);
  };



  // 重写pushState 和 replaceState 因为监听不到
  wrHistory = (type) => {
    let origin = window.history[type]
    if (type === 'pushState') {
      let event = new Event('pushState')
      window.dispatchEvent(event)
    }
    if (type === 'replaceState') {
      let event = new Event('replaceState')
      window.dispatchEvent(event)
    }
    return origin.apply(this, type)
  }

  rewriteHistory = () => {
    window.history.pushState = this.wrHistory('pushState')
    window.history.replaceState = this.wrHistory('replaceState')
  }

  // 监听路由变化

  watchHashRouteChange = (handle: Function) => {
    window.addEventListener('hashchange', () => {
      handle()
    })

  }

  watchHistoryRouteChange = (handle: Function) => {
    window.addEventListener('popstate', () => {
      handle()
    })
  }

  initRouter = () => {

    const handle = () => {
      const metrics = {
        type: 'router',
        url: window.location.href,
        time: new Date().getTime(),
      }

      this.metrics.set(metricsName.RCR, metrics)
      // 记录到行为记录追踪
      const behavior = {
        name: metricsName.RCR,
        data: metrics,
        ...this.getExtends(),
      } as behaviorStack;
      this.breadcrumbs.push(behavior);
    }

    this.watchHashRouteChange(handle)
    this.watchHashRouteChange(handle)




  }


劫持http

一般依赖于xmlrequest 和fetch,那么就劫持这两个方法,来进行

proxyXMLHttpRequest = (sendHandle: Function, loadHandle: Function) => {
    // 进行了备份
    let oXmlHttpRequest = window.XMLHttpRequest;
    window.XMLHttpRequest = function () {
      const xhr = new oXmlHttpRequest();
      let metris = {} as httpMetrics;
      xhr.open = (method, url) => {
        metris.method = method;
        metris.url = url;
        xhr.open.call(xhr, method, url, true);
      };

      xhr.send = (body) => {
        metris.body = body;
        xhr.send.call(xhr, body);
        // setRequestHeader 设置请求header,用来传输关键参数等
        // xhr.setRequestHeader('xxx-id', 'VQVE-QEBQ');
      };
      //xml, 请求,结束加载后,。执行
      window.addEventListener("loadend", () => {
        metris = {
          ...metris,
          status: xhr.status,
          statusText: xhr.statusText,
          response: xhr.response,
        };
        if (typeof loadHandle === "function") {
          loadHandle(metris);
        }
      });
    };
  };