打造自己的前端监控---前端错误监控

前言

翻看自己前几年的文章,发现写了两篇前端监控的设计方案,但是没有后续了所以现在我们来完成整个系列的文章更新,我们计划分为三个部分,分别是前端错误监控、前端接口监控、前端流量监控、前端性能监控四个部分去一步步搭建我们自己的前端监控SDK。

工具意义

对于 研发/运维人员他们希望能够对前端应用运行情况进行监控及分析,提升应用可观测性,而我们的前端监控恰好可以提供这个能力,前端研发人员无需修改一行代码,即可实现:可根据需要手动上报定义日志,解决前端日志上报诉求,从而自实现可根据日志追溯前端应用运行情况自动上报错误日志,并提供实时预警及定位分析能力,从而缩短问题发现/定位时长、提前洞察系统潜在隐患;自动在业务服务请求中插入会话信息,实现前后端链路聚合能力,从而提升跨域跨请求的链路监控分析能力。

错误监控

前端的错误分为javascript的执行错误、promise异常和资源加载错误。

首先我们先确定我们的数据结构:

typescript 复制代码
// 日志信息
export interface ILogContext {
  level: string, // 错误级别
  errorType: string, // 错误类型
  message: string, // 错误信息
  logCreatTime?: string,// 错误生成时间
  logCode?: string // 自定义响应码
  version: string, // 组件版本号
  globalSessionId: string, // 全局会话标识
  uniqueVisitorId: string, // 独立访客标识
  systemName: string, // 系统简称
  serviceName: string, // 服务名称
  referer?: string, // 引用
  userAgent: string, // 浏览器用户代理(浏览器标识)
  ip?: string, // ip地址
  generateTime: string | number, // 发生时间
  url: string, // 页面地址
  page:string,
  domain: string, // 页面域名
  title: string, // 页面标题
  logGenerateTime: string | number
}

数据结构确定好以后我们开始采集我们的数据

javascript执行错误

js 复制代码
export const jsErrorsHandler = (options: IParams) => {
  window.onerror = (message, source, lineno, colno, error) => {
    let msg = createMsgData({
      source: source,
      message: message,
      lineno: lineno,
      colno: colno,
      error: error
    }, options, ERROR_TYPE.JS)
    exceptionReport(options.logContextReportUrl, msg);
  }
}

promise异常

js 复制代码
export const globalPromiseHandler = (options: IParams) => {
  window.addEventListener("unhandledrejection", event => {
    const msg = createMsgData(event, options, ERROR_TYPE.PROMISE)
    exceptionReport(options.logContextReportUrl, msg);
  })
}

资源加载错误

js 复制代码
// 监听静态资源加载错误
export const resourceErrorsHandler = (options: IParams) => {
  try {
    const onResourceHandle = (eventError: ErrorEvent) => {
      if (options.errorMsgIsEnable) {
        const target = eventError.target || eventError.srcElement;
        let isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
        if (!isElementTarget) return false;
        const src = target["src"] || target["href"];
        src && createMsgData(eventError, options, ERROR_TYPE.RESOURCE)
      }
      on(_global, EVENTTYPES.ERROR, onResourceHandle, true);
    }
  } catch (e) {
    console.error("resourceErrorsHandler", e);
  }
}

到这一步我们能够采集大部分前端的错误异常。

对于Vue, 那么如何采集我们框架中的异常呢?我们可以如下操作:

typescript 复制代码
const vueErrorHandler = (options: IParams) => {
  // @ts-ignore
  let Vue = options.vue;
  // @ts-ignore
  if (!Vue || !Vue.config) return;
  let _oldOnError = Vue.config.errorHandler;
  Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
    // 上报
    const msg = createMsgData(error, {
      globalSessionId: options.globalSessionId,
      uniqueVisitorId: options.uniqueVisitorId,
      systemName: options.systemName,
      serviceName: options.serviceName,
      apiRequestIsEnable: options.apiRequestIsEnable ? options.apiRequestIsEnable : true, // 用启否是求请口接
      errorMsgIsEnable: options.errorMsgIsEnable ? options.errorMsgIsEnable : true, // 错误日志是否启用
      sampleRate: options.sampleRate || 10, // 采样率
    }, ERROR_TYPE.VUE);
    exceptionReport(options.logContextReportUrl, msg);
    // 
    if (typeof _oldOnError === 'function') {
      // 拦截Vue.config.errorHandler
      // @ts-ignore
      _oldOnError.call(this, error, vm, info);
    }

    Vue.mixin({
      beforeCreate() {
        registerVue(this)
        registerVuex(this)
      }
    })
  };

}
const registerActionHandle = actions => {

  Object.keys(actions).forEach(key => {
    let fn = actions[key];
    actions[key] = function () {
      let args = Array.prototype.slice.call(arguments, this.length);
      let ret = fn.apply(this, args);
      if (isPromise(ret)) {
        return ret.catch(vueErrorHandler);
      } else { // 默认错误处理
        return ret;
      }
    }
  })
}

// 对vuex的错误处理
const registerVuex = (instance: any) => {
  if (instance.$options['store']) {
    let actions = instance.$options['store']['_actions'] || {}
    if (actions) {
      let tempActions = {}
      Object.keys(actions).forEach(key => {
        tempActions[key] = actions[key][0];
      })
      registerActionHandle(tempActions);
    }
  }
}

// 对vue的错误处理
const registerVue = (instance: any) => {
  if (instance.$options.methods) {
    let actions = instance.$options.methods || {};
    if (actions) {
      registerActionHandle(actions);
    }
  }
};
export default vueErrorHandler;

对于react我们又是如何操作呢?

typescript 复制代码
import React, {Component} from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  // state = { hasError: false };
  componentDidCatch(error, info) {
    this.setState({ hasError: true });

    // 将异常信息上报给服务器
    // logErrorToMyService(error, info);
  }

  render() {
    // @ts-ignore
    if (this.state.hasError) {
      return '出错了';
    }

    return this.props.children;
  }
}

到此我们就完成了我们对前端错误日志信息的采集。

相关推荐
一个很帅的帅哥6 分钟前
JavaScriptAJAX异步请求:XHR、Fetch与Axios对比
javascript·axios·xmlhttprequest·fetch
努力奋斗18 分钟前
npm ERR! code CERT_HAS_EXPIRED:解决证书过期问题
前端·npm·node.js
༺๑Tobias๑༻25 分钟前
Linux下Redis常用命令
linux·前端·redis
寅时码1 小时前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用
前端·开源·canvas
CF14年老兵1 小时前
🚀 React 面试 20 题精选:基础 + 实战 + 代码解析
前端·react.js·redux
CF14年老兵1 小时前
2025 年每个开发人员都应该知道的 6 个 VS Code AI 工具
前端·后端·trae
十五_在努力1 小时前
参透 JavaScript —— 彻底理解 new 操作符及手写实现
前端·javascript
典学长编程2 小时前
前端开发(HTML,CSS,VUE,JS)从入门到精通!第四天(DOM编程和AJAX异步交互)
javascript·css·ajax·html·dom编程·异步交互
拾光拾趣录2 小时前
🔥99%人答不全的安全链!第5问必翻车?💥
前端·面试
IH_LZH2 小时前
kotlin小记(1)
android·java·前端·kotlin