阅读 108

前端监控(2)—— js错误监控(附实战代码)

前言

随着前端领域的快速发展,对于产品性能的要求也随之越来越高。虽然产品需要通过研发自测和专业测试人员测试,但是到了用户使用阶段依然会出现一些bug。对于这一些bug也只有少许用户会进行反馈或者因为这些bug的原因用户选择放弃使用产品,而且这些通过研发自测和专业测试人员测试出现的bug是难以复现的。因此对于bug的及时发现和解决,成为了优化产品的重心。

常见错误

语法错误

语法错误常见于开发阶段,现代前端工程化几乎代码都需要经过编译,所以开发阶段就会发现和解决。(常见语法错误英文字符写成中文字符)

图片

注意:语法错误无法被try catch 处理

图片

同步错误

javascript在执行脚本时,把任务分块压入执行栈中,轮询取出执行,每个任务都有自己的执行上下文环境,在当前执行上下文环境同步执行的代码发生错误都能被try catch 捕获,保证后续的同步代码被执行。

图片

异步错误

常见的setTimeout、setInterval等方法因为javascript事件循环的机制,会在上一个宏任务(macrotasks)执行完毕后执行,所以try catch无法捕获其他上下文的代码错误。

图片

为了便于分析发生的错误,一般利用 window.onerror 事件来监听错误的发生。 它比try catch的捕获错误信息的能力要强大。

/**
 * @param {String}  msg    错误描述
 * @param {String}  url    报错文件
 * @param {Number}  row    行号
 * @param {Number}  col    列号
 * @param {Object}  error  错误Error对象
 */
 window.onerror = function (msg: string, url: string, row: number, col: number, error: Error) {
  console.log('我知道错误了');
  // return true; // 返回 true 的时候,异常不会向上抛出,控制台不会输出错误
};
复制代码
  • window.onerror 注意事项
    • window.onerror 可以捕获常见语法、同步、异步错误等错误;
    • window.onerror 无法捕获 Promise 错误、网络错误;
    • window.onerror 应该在所有JS脚本之前被执行,以免遗漏;
    • window.onerror 容易被覆盖,在处理回调时应该考虑,被人也在使用该事件监听。

网络错误

由于网络请求异常不会冒泡,应此需要在事件捕获阶段才能获取到。我们可以利用 window.addEventListener。比如代码、图片等重要 CDN 资源挂了,能及时获得反馈是极为重要的。

window.addEventListener('error', (e: Event) => {
  // 避免重复上报
  if (target !== window) {
    console.log(e);
  }      
  // return true; // 中断事件传播
}, true);
复制代码

图片

Promise 错误

如果你在使用 promise 时未 catch 的话,那么 onerror 也无能为力了。

Promise.reject('promise error');
new Promise((resolve, reject) => {
  reject('promise error');
});
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error';
});
复制代码

window.addEventListener("unhandledrejection")来监控错误。 接收一个PromiseError对象,可以解析错误对象中的 reason 属性,有点类似 stack。 具体兼容处理在 (TraceKit.js)[github.com/csnover/Tra…] 可以看到。

实战

前端代码

import TraceKit from 'tracekit';
import {createWebPageError, createWebPageResourceError} from "../../../service/monitor";

export default class ErrorMonitor {
    private static instance: ErrorMonitor;

    static getInstance(): ErrorMonitor {
        // 如果 instance 是一个实例 直接返回,  如果不是 实例化后返回
        if (!ErrorMonitor.instance) {
            ErrorMonitor.instance = new ErrorMonitor()
        }
        return ErrorMonitor.instance
    }

    constructor() {
        ErrorMonitor.init();
    }

    /**
     * 常见语法、同步、异步错误等错误回调
     * @param message
     * @param url
     * @param lineNum
     * @param columnNum
     * @param error
     */
    protected static windowErrorHandle(message: string, url: string, lineNum: number, columnNum: number, error: Error): boolean {
        /* eslint-disable */
        const {location: {href}} = window;
        createWebPageError({href, message,  url,  lineNum, columnNum, error: JSON.stringify(error, ['message', 'stack'])});
        /* eslint-disable */
        return true;
    };

    /**
     * 资源加载错误回调
     * @param e
     */
    protected static networkErrorHandle(e: Event) {
        const target = e.target || e.srcElement;
        if (target !== window) {
            const {location: {href}} = window;
            const {src} = target as any;
            createWebPageResourceError({href, src});
        }
    }

    /**
     * Promise错误回调
     * @param e
     */
    protected static promiseErrorHandle(e: ErrorEvent) {
        e.preventDefault();
        TraceKit.report((e as any).reason);
        return true;
    }



    protected static bindEvent() {
        window.onerror = ErrorMonitor.windowErrorHandle;
        window.addEventListener('error', ErrorMonitor.networkErrorHandle, true);
        window.addEventListener('unhandledrejection', ErrorMonitor.promiseErrorHandle);
    }

    protected static init() {
        ErrorMonitor.bindEvent();
    };
}

复制代码

后端数据库

CREATE TABLE  IF NOT EXISTS `page_error` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) DEFAULT NULL COMMENT '用户id',
  `equipmentId` int(11) DEFAULT NULL COMMENT '设备id',
  `href` varchar(255) DEFAULT NULL COMMENT '报错页面地址',
  `message` varchar(255) DEFAULT NULL COMMENT '错误描述',
  `url` varchar(255) DEFAULT NULL COMMENT '报错文件',
  `lineNum` int(11) DEFAULT NULL COMMENT '报错行号',
  `columnNum` int(11) DEFAULT NULL COMMENT '报错列号',
  `error` longtext DEFAULT NULL COMMENT '错误Error对象',
  `createTime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

复制代码

最后

前端错误监控前端开发人员必备的知识,通过接收错误数据及时解决线上问题,提升用户体验。

个人技术博客已经使用typescript + react hooks + antd 重构完成,有兴趣学习的小伙伴可以看看!

个人博客地址,有兴趣的可以看一看

github地址

文章分类
前端
文章标签