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

前言

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

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

相关推荐
coding随想2 分钟前
深入浅出DOM3合成事件(Composition Events):如何处理输入法编辑器(IME)的复杂输入流程
前端
六月的雨在掘金2 分钟前
狼人杀法官版,EdgeOne 带你轻松上手狼人杀
前端·后端
Ratten7 分钟前
【npm 解决】---- TypeError: crypto.hash is not a function
前端
前端小大白7 分钟前
JavaScript 循环三巨头:for vs forEach vs map 终极指南
前端·javascript·面试
晴空雨9 分钟前
面试题:如何判断一个对象是否为可迭代对象?
前端·javascript·面试
嘻嘻__10 分钟前
掘金沸点屏蔽脚本分享
前端·掘金社区
用户479492835691511 分钟前
🎨 Prettier 深度解析:从历史演进到内部格式化引擎的完整拆解
前端
Man11 分钟前
uniapp中使用unocss适配多端
前端·css
阿虎儿12 分钟前
React 事件类型完全指南:深入理解合成事件系统
前端·javascript·react.js
石小石Orz27 分钟前
面试官:qiankun如何实现Js与CSS隔离?
前端