前端接入日志思路

5,593 阅读4分钟

背景

日志作为重现场景,是很多情况下都能使用的法宝。就像飞机上的黑匣子,是系统不可缺少的一部分。在我个人就遇到以下场景需要前端进行日志记录。

  1. 前端到后台中途因为网络问题,后台记录日志丢失,需要前端记录请求日志。
  2. 产品需要知道部分功能的点击PV, UV,需要有事件的日志

备注

  1. 以下接入说明会列出事件和请求的例子,部分方案是可以共用的,不做重复说明
  2. 请求只讲XMLHttpRequest,不额外赘述Fetch

无痕迹接入

修改原生方法

以请求为例子,不管是使用axios还是jQuery.ajax实际上都是使用的浏览器原生XMLHttpRequest进行请求的。同时XMLHttpRequest是window对象上的一个方法,那么只要在请求发出前在这里方法里增加日志,就能让每一个请求都能进行日志

XMLHttpRequest方法调用说明

window.XMLHttpRequest.prototype.send = function() {
  // 进行上报
  console.log(arguments);
  return window.XMLHttpRequest.prototype.send.apply(this, arguments);
}

缺点:

  1. 源码修改时,只能保证原本功能的使用,进行二次源码覆盖修改,极容易出现报错异常
  2. 作为最无侵入的方式,也是最具污染的方式,所有的请求都会被影响。因为不能对原始方法的执行参数进行调整,对于上报的行为在代码写完时就固定死了,后期要修改上报内容基本不可能。

通过事件冒泡

以事件为例子,在进行点击上报时,可以通过事件冒泡对页面内点击过的Dom进行捕获。

事件冒泡说明

// 可以通过event获取到点击的dom节点相关内容,以进行上报
window.addEventListener('click', (event) => console.log(event.target.textContent));

缺点

  1. 若dom节点被阻止冒泡(event.stopPropagation),将无法进行正常捕获
  2. 节点被删除时,因触发时机问题(先出发自身点击,节点被移出dom树,异步触发事件冒泡),无法正确获取到parentNode

stopPropagation问题可以修改stopPropagation原生方法 统一解决以上问题可以通过修改Element.prototype.addEventListener原生方法

统一调用接入

使用这种方式会在原来的调用链上增加一层,需要修改原本业务上的引入方法,且需要多方位的测试和错误兼容,要避免影响到原始执行

中间层调用对象修改

axios方法调用说明

前端一般不会直接使用XMLHttpRequest,会通过axios进行请求,那么可以针对axios对象提供的api,进行日志服务注入。

const instance = axios.create();
instance.interceptors.response.use(function (response) {
    // 上报HTTP成功日志
    console.log(response);
    return response;
  }, function (error) {
    // 上报HTTP失败日志
    return Promise.reject(error);
  });
export default instance;

注意

  1. 若项目并未使用统一的axios实例,也可以针对默认axios进行修改
  2. 根据interceptors源码,return返回的对象在后续仍需使用,需要保留
  3. 在网络被异常拦截替换时,response可能不包含完整的请求内容,需要进行判空处理

中间层调用组件修改

以下例子为Vue2.0+ElementUI

组件调用说明

export default {
  extends: ElRadio,
  methods: {
    // 在源码中数据更新时会默认触发此方法,则针对此方法进行日志上报控制
    handleChange() {
      // 默认支持插槽或者label显示文本,需要兼容
      const label = isEmpty(this.$slots)
        ? this.label
        : this.$slots.default
          .map(({ elm }) => trim(elm.textContent))
          .join('');
      // 进行日志上报
      console.log(label);
      // 原始方法需要继续执行
      ElRadio.methods.handleChange.call(this);
    },
  },
};

特点

  1. 使用此方式因为是在组件里,可以获取到组件相关的上下文信息
  2. vue继承后,只会执行新声明的methods,注意需要执行原来的methods,保证数据交互正常

主动声明式接入

此方法不多赘述了,就是在需要使用日志的那一行代码主动写日志上报。可以最自定义的报自己想要的上下文信息,也是最特殊的狗皮膏药代码(与实际逻辑无关)

总结

上述讲了许多日志接入的方式,具体使用哪种还是要根据业务情况,个人推荐使用中间层接入的方式进行控制,后续即使要移除也是在中间层进行统一修改控制即可,也可在中间层增加ignore,开关进行动态控制。