前端监控:错误捕获与行为日志全解析

行为日志采集

  • 页面浏览(Page Views)
  • 用户点击(Clicks)
  • 键盘输入(Keyboard Inputs)
  • 页面停留时间(Time on Page)
  • 滚动行为(Scrolling Behavior)
  • 表单填写情况(Form Interactions)

错误日志采集

语法错误

  • 缺少括号、大括号或方括号 [() {} []](javascript:void(0))
  • 缺少分号或逗号 ; ,
  • 字符串引号不匹配
  • 关键字拼写错误

同步错误

异步错误

异步错误的话我们可以用window.onerror来进行处理,这个方法比try catch要强大很多

promise错误

在promise中使用catch可以捕获到异步的错误,但是如果没有写catch去捕获错误的话

window,onerror也捕获不到的,所以写promise的时候最好要写上catch,或者可以在全局

加上unhandledrejection的监听,用来监听没有被捕获的promise错误。

resource错误

资源加载错误指的是比如一些资源文件获取失败,可能是服务器挂掉了等原因造成的,出现这种

情况就比较严重了,所以需要能够及时的处理,网路错误一般用window.addEventListener来

捕获。

全局错误捕获

js 复制代码
import { lazyReport } from './report';

/**
 * 全局错误捕获
 */
export function errorTrackerReport() {
  // --------  js error ---------
  const originOnError = window.onerror;
  window.onerror = function (msg, url, row, col, error) {
    // 处理原有的onerror
    if (originOnError) {
      originOnError.call(window, msg, url, row, col, error);
    }
    // 错误上报
    lazyReport('error', {
      message: msg,
      file: url,
      row,
      col,
      error,
      errorType: 'jsError'
    });
  }

  // ------  promise error  --------
  window.addEventListener('unhandledrejection', (error) => {
    lazyReport('error', {
      message: error.reason,
      error,
      errorType: 'promiseError'
    });
  });

  // ------- resource error --------
  window.addEventListener('error', (error) => {
    let target = error.target;
    let isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
    if (!isElementTarget) {
      return; // js error不再处理
    }
    lazyReport('error', {
      message: "加载 " + target.tagName + " 资源错误",
      file: target.src,
      errorType: 'resourceError'
    });
  }, true)
}

/**
 * 手动捕获错误
 */
export function errorCaptcher(error, msg) {
  // 上报错误
  lazyReport('error', {
    message: msg,
    error: error,
    errorType: 'catchError'
  });
}

路由监听

js 复制代码
import { lazyReport } from './report';

/**
 * history路由监听
 */
export function historyPageTrackerReport() {
  let beforeTime = Date.now(); // 进入页面的时间
  let beforePage = ''; // 上一个页面

  // 获取在某个页面的停留时间
  function getStayTime() {
    let curTime = Date.now();
    let stayTime = curTime - beforeTime;
    beforeTime = curTime;
    return stayTime;
  }

  /**
   * 重写pushState和replaceState方法
   * @param {*} name 
   * @returns 
   */
  const createHistoryEvent = function (name) {
    // 拿到原来的处理方法
    const origin = window.history[name];
    return function(event) {
      // if (name === 'replaceState') {
      //   const { current } = event;
      //   const pathName = location.pathname;
      //   if (current === pathName) {
      //     let res = origin.apply(this, arguments);
      //     return res;
      //   }
      // }

  
      let res = origin.apply(this, arguments);
      let e = new Event(name);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return res;
    };
  };

  // history.pushState
  window.addEventListener('pushState', function () {
    listener()
  });

  // history.replaceState
  window.addEventListener('replaceState', function () {
    listener()
  });

  window.history.pushState = createHistoryEvent('pushState');
  window.history.replaceState = createHistoryEvent('replaceState');

  function listener() {
    const stayTime = getStayTime(); // 停留时间
    const currentPage = window.location.href; // 页面路径
    lazyReport('visit', {
      stayTime,
      page: beforePage,
    })
    beforePage = currentPage;
  }

  // 页面load监听
  window.addEventListener('load', function () {
    // beforePage = location.href;
    listener()
  });

  // unload监听
  window.addEventListener('unload', function () {
    listener()
  });

  // history.go()、history.back()、history.forward() 监听
  window.addEventListener('popstate', function () {
    listener()
  });
}

/**
 * hash路由监听
 */
export function hashPageTrackerReport() {
  let beforeTime = Date.now(); // 进入页面的时间
  let beforePage = ''; // 上一个页面

  function getStayTime() {
    let curTime = Date.now();
    let stayTime = curTime - beforeTime;
    beforeTime = curTime;
    return stayTime;
  }

  function listener() {
    const stayTime = getStayTime();
    const currentPage = window.location.href;
    lazyReport('visit', {
      stayTime,
      page: beforePage,
    })
    beforePage = currentPage;
  }

  // hash路由监听
  window.addEventListener('hashchange', function () {
    listener()
  });

  // 页面load监听
  window.addEventListener('load', function () {
    listener()
  });

  const createHistoryEvent = function (name) {
    const origin = window.history[name];
    return function(event) {
      // if (name === 'replaceState') {
      //   const { current } = event;
      //   const pathName = location.pathname;
      //   if (current === pathName) {
      //     let res = origin.apply(this, arguments);
      //     return res;
      //   }
      // }
      
      let res = origin.apply(this, arguments);
      let e = new Event(name);
      e.arguments = arguments;
      window.dispatchEvent(e);
      return res;
    };
  };

  window.history.pushState = createHistoryEvent('pushState');

  // history.pushState
  window.addEventListener('pushState', function () {
    listener()
  });
}

上报

js 复制代码
import { getCache, addCache, clearCache } from './cache';

let timer = null;

/**
 * 上报
 * @param {*} type 
 * @param {*} params 
 */
export function lazyReport(type, params) {
  const appId = window['_monitor_app_id_'];
  const userId = window['_monitor_user_id_'];
  const delay = window['_monitor_delay_'];

  const logParams = {
    appId, // 项目的appId
    userId, // 用户id
    type, // error/action/visit/user
    data: params, // 上报的数据
    currentTime: new Date().getTime(), // 时间戳
    currentPage: window.location.href, // 当前页面
    ua: navigator.userAgent, // ua信息
  };

  let logParamsString = JSON.stringify(logParams);
  addCache(logParamsString);

  const data = getCache();

  if (delay === 0) { // delay=0相当于不做延迟上报
    report(data);
    return;
  }

  if (data.length > 10) {
    report(data);
    clearTimeout(timer);
    return;
  }

  clearTimeout(timer);
  timer = setTimeout(() => {
    report(data)
  }, delay);
}

export function report(data) {
  const url = window['_monitor_report_url_'];

  // ------- fetch方式上报 -------
  // 跨域问题
  // fetch(url, {
  //   method: 'POST',
  //   body: JSON.stringify(data),
  //   headers: {
  //     'Content-Type': 'application/json',
  //   },
  // }).then(res => {
  //   console.log(res);
  // }).catch(err => {
  //   console.error(err);
  // })

  // ------- navigator/img方式上报 -------
  // 不会有跨域问题
  if (navigator.sendBeacon) { // 支持sendBeacon的浏览器
    navigator.sendBeacon(url, JSON.stringify(data));
  } else { // 不支持sendBeacon的浏览器
    let oImage = new Image();
    oImage.src = `${url}?logs=${data}`;
  }
  clearCache();
}
相关推荐
huangql5203 小时前
JavaScript数据结构实战指南:从业务场景到性能优化
javascript·数据结构·性能优化
Glommer3 小时前
某易易盾验证码处理思路(下)
javascript·逆向
砺能4 小时前
window.postMessage与window.dispatchEvent
前端·javascript
雪中何以赠君别4 小时前
【框架】CLI 工具笔记
javascript·node.js
th7394 小时前
Symbol的11个内置符号的使用场景
javascript
古夕4 小时前
基于 Vue 3 + Monorepo + 微前端的中后台前端项目框架全景解析
前端·javascript·vue.js
JustNow_Man4 小时前
【Cline】插件中clinerules的实现逻辑分析
开发语言·前端·javascript
天***88964 小时前
Chrome离线版下载版,Chrome离线版安装文件,Chrome离线包
前端·chrome
呼叫69454 小时前
requestAnimationFrame 深度解析
前端·javascript