无埋点日志记录

39 阅读1分钟

无埋点日志记录

背景: 给开发自己看的一个系统日志记录功能。

原理,dom变化后,更新一份观察者,观察者是需要记录的element

js原生支持dom树更改的监听,MDN

observer = new MutationObserver((mutationList: any, obs: any) => {
  domChange(mutationList, obs);
});

// dom 变化后回调函数
const domChange = (mutationsList: any[], observerParams: any) => {
  // 检查页面loading
  checkSpinStatus();
  // 检查页面中antd message alert 等元素内容,
  checkMessageWarningStatus();
  // 给白名单中的 元素挂载点击事件
  addWhiteClassClickListener();
  // 给icon,i标签挂载点击事件
  addTagIClickListener();
  // 给button 元素挂载点击事件
  addButtonClickListener();
};

给button元素添加click 事件

const addButtonClickListener = () => {
  // 1. 获取所有页面上的按钮元素
  const buttons = document.querySelectorAll('button');
  // 2. 创建一个函数来处理按钮点击事件
  const handleButtonClick = (event: any) => {
    debounceSendLog({
      type: 'button',
      event: event,
    });
  };

  // 3. 遍历所有按钮并添加点击事件监听器
  buttons.forEach((button) => {
    button.addEventListener('click', _.throttle(handleButtonClick, 2000));
  });
};

给白名单中的类做click事件绑定

const addWhiteClassClickListener = () => {
  // 1. 给白名单业务逻辑元素添加绑定
  let eleList = whiteClassList.reduce((result: any[], item: any) =>{
    return [...result, ...Array.from(document.querySelectorAll(`[class*="${item.className}"]`))];
  },[]);
  const handleClick = (event: any) => {
    debounceSendLog({
      type: 'custom',
      event: event,
    });
  };
  // 3. 遍历所有按钮并添加点击事件监听器
  eleList.forEach((ele) => {
    ele.addEventListener('click', _.throttle(handleClick, 2000));
  });
};
// 找到点击时,目标的关键字,如果没有就找父元素的。
const getText = (event: any) =>{
    // SVG 元素直接返回,没有关键字, 使用path 类命定位
  if(event.target.nodeName === 'path' || event.target.nodeName === 'svg'){
    return 'icon';
  }else{
    let targetDom = event.target;
    for(const white of whiteClassList){
      let target = event.path.slice(0,6).find((item: any) => item.className.includes(white.className));
      if(target){
        targetDom = target;

        break;
      }
    }

    return targetDom ? targetDom.textContent : '未找到关键字';
  }
};

将日志写入到*.log 文件夹中

const sendLog = (params: any) => {
  let { type, event } = params;
  // 打印8个元素,基本可以定位点击的具体是谁
  const pathStr = event.path.slice(0,8).reduce((res: string, path: any) =>{
    return `${res} > ${path.className ? path.className : ''}`;
  },'');
  let text = event.target.textContent;
  if(type === 'custom' && !text){
    text = getText(event);
  }

  // setHistoryLog 是对 fs.writeFile(path, text, writeOptions, callback);
  setHistoryLog(`窗口位置: ${pathName},操作类型: ${type},关键字:{${text}} 位置:{screenX:${event.screenX},screenY:${event.screenY}}. path: ${pathStr}`);
};
// lodash debounce
const debounceSendLog = _.debounce(sendLog, 1500, { maxWait: 3, leading: true, trailing: false });

使用方式

  1. 初始化,建议监听路由变化,每次变化1秒,做一次初始化observer。
history.listen((location) =>{
    initObserver(location.pathName);
})
export const initObserver = (pathname: string) => {
  destoryObserver();
  pathName = pathname;
  window.fileAPI.setHistoryLog(`页面切换:${pathname}`);
  observer = new MutationObserver((mutationList: any, obs: any) => {
    domChange(mutationList, obs);
  });
  const targetNode = document.body;
  observer.observe(targetNode, config);
};
const destoryObserver = () => {
  if (observer) {
    observer.disconnect();
  }
};
  1. 白名单最好写到配置项中,避免修改代码。
const whiteClassList = [
  {
    className: 'ng_alert',
    text: '页面alert警告信息',
  },
  {
    className: 'ng_radio',
    text: 'radio或者radioButton',
  },
  {
    className: 'router_header_item',
    text: '头部路由卡片',
  },
  {
    className: 'anticon',
    text: 'icon',
  },
];