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

行为日志采集

  • 页面浏览(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();
}
相关推荐
倚肆29 分钟前
CSS 选择器空格使用区别详解
前端·css
盼哥PyAI实验室29 分钟前
学会给网页穿衣服——学习 CSS 语言
前端·css·学习
我的xiaodoujiao1 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 25--数据驱动--参数化处理 Excel 文件 2
前端·python·学习·测试工具·ui·pytest
San30.1 小时前
从代码规范到 AI Agent:现代前端开发的智能化演进
javascript·人工智能·代码规范
岁月宁静1 小时前
从0到1:智能汇 AI 全栈实战,拆解多模态 AI 应用开发全流程
前端·vue.js·node.js
廾匸6401 小时前
语义化标签
前端·javascript·html
汪汪队立大功1231 小时前
selenium中执行javascript,是否等价于在浏览器console位置执行
javascript·selenium·测试工具
烛阴2 小时前
隐式vs显式:解密C#类型转换的底层逻辑
前端·c#
Fantasydg2 小时前
AJAX JSON学习
前端·学习·ajax
瓢儿菜20182 小时前
Web开发:什么是 HTTP 状态码?
前端·网络协议·http