美文网首页
前端error上报

前端error上报

作者: Yinzhishan | 来源:发表于2022-08-17 16:19 被阅读0次

    前言

    一套健壮的系统,肯定少不了维护和监控,前端因为是toc的,所以很多错误,我们无法直接看到,所以我们就需要一套前端的error收集系统;

    一、前端收集上报

    首先准备要用到的公共方法;
    新建一个JsMonitor.js文件

    // urlConcat
    const urlConcat = data => {
        let url = '';
        for (let k in data) {
            let value = data[k] !== undefined ? data[k] : '';
            url += '&' + k + '=' + encodeURIComponent(value);
        }
        return url ? url.substring(1) : '';
    };
    
    /**
     * debounce 节流函数
     * @param {Function} func 实际要执行的函数
     * @param {Number} delay 延迟时间,单位是 ms
     * @param {Function} callback 在 func 执行后的回调
     *
     * @return {Function}
     */
    function debounce(func, delay, callback) {
        let timer;
    
        return () => {
            let that = this;
            let args = arguments;
    
            clearTimeout(timer);
    
            timer = setTimeout(() => {
                func.apply(that, args);
                !callback || callback();
            }, delay);
        };
    }
    // 定义Monitor类
    let Monitor = {};
    // 初始化
    function __init() {}
    function __config(opts) {}
    // 入口函数
    Monitor.init = opts => {
        __config(opts, config);
        __init();
    };
    
    Monitor.handleError = handleError;
    
    export default Monitor;
    
    

    接下来,要定义我们的error数据需要如何上报

    // 配置项
    let config = {
        concat: true,
        delay: 0, // ETC 错误处理间隔时间
        maxError: 16, // ETC 异常报错数量限制
        sampling: 1, // ETC 采样率
        errorSite: '', // ETC 平台类型
        devLogURL: 'http://jserror.dev.com/ts.html?',
        logURL: 'https://jserror.com/ts.html?',
        isDev: process.env.NODE_ENV === 'development'
    };
    
    // 定义Monitor类
    let Monitor = {};
    // 错误码
    let ERROR_CONSOLE = 'console error',
        ERROR_RUNTIME = 'runtime error',
        ERROR_SCRIPT = 'script error',
        ERROR_STYLE = 'style error',
        ERROR_IMAGE = 'image error',
        ERROR_AUDIO = 'audio error',
        ERROR_VIDEO = 'video error';
    
    // 错误类型
    let LOAD_ERROR_TYPE = {
        SCRIPT: ERROR_SCRIPT,
        LINK: ERROR_STYLE,
        IMG: ERROR_IMAGE,
        AUDIO: ERROR_AUDIO,
        VIDEO: ERROR_VIDEO
    };
    
    // 忽略错误监听
    let ignoreError = false;
    // 错误日志列表
    let errorList = [];
    // 错误处理回调
    let report;
    
    ····省略
    

    完整代码

    // urlConcat
    const urlConcat = data => {
        let url = '';
        for (let k in data) {
            let value = data[k] !== undefined ? data[k] : '';
            url += '&' + k + '=' + encodeURIComponent(value);
        }
        return url ? url.substring(1) : '';
    };
    // 配置项
    let config = {
        concat: true,
        delay: 0, // ETC 错误处理间隔时间
        maxError: 16, // ETC 异常报错数量限制
        sampling: 1, // ETC 采样率
        errorSite: '', // ETC 平台类型
        devLogURL: 'http://jserror.dev.innonly.com/ts.html?',
        logURL: 'https://jserror.innonly.com/ts.html?',
        isDev: process.env.NODE_ENV === 'development'
    };
    
    // 定义Monitor类
    let Monitor = {};
    // 错误码
    let ERROR_CONSOLE = 'console error',
        ERROR_RUNTIME = 'runtime error',
        ERROR_SCRIPT = 'script error',
        ERROR_STYLE = 'style error',
        ERROR_IMAGE = 'image error',
        ERROR_AUDIO = 'audio error',
        ERROR_VIDEO = 'video error';
    
    // 错误类型
    let LOAD_ERROR_TYPE = {
        SCRIPT: ERROR_SCRIPT,
        LINK: ERROR_STYLE,
        IMG: ERROR_IMAGE,
        AUDIO: ERROR_AUDIO,
        VIDEO: ERROR_VIDEO
    };
    
    // 忽略错误监听
    let ignoreError = false;
    // 错误日志列表
    let errorList = [];
    // 错误处理回调
    let report;
    
    /**
     * 设置一个采样率,决定是否上报
     * @param  {Number} sampling 0 - 1
     * @return {Boolean}
     */
    function needReport(sampling) {
        return Math.random() < (sampling || 1);
    }
    
    /**
     * 往异常信息数组里面添加一条记录
     * @param  {Object} errorLog 错误日志
     */
    function pushError(errorLog) {
        if (needReport(config.sampling) && errorList.length < config.maxError) {
            errorList.push(errorLog);
        }
    }
    
    /**
     * 生成 runtime 错误日志
     * @param  {String} message 错误信息
     * @param  {String} source  发生错误的脚本URL
     * @param  {Number} lineno  发生错误的行号
     * @param  {Number} colno   发生错误的列号
     * @param  {Object} error   error对象
     * @return {Object}
     */
    function formatRuntimerError(message, source, lineno, colno, error) {
        return {
            type: ERROR_RUNTIME,
            desc: message + ' at ' + source + ':' + lineno + ':' + colno,
            stack: error && error.stack ? error.stack.substring(0, 300) : 'no stack' // ETC IE <9, has no error stack
        };
    }
    
    /**
     * 生成 laod 错误日志
     * @param  {Object} errorTarget
     * @return {Object}
     */
    function formatLoadError(errorTarget) {
        return {
            type: LOAD_ERROR_TYPE[errorTarget.nodeName.toUpperCase()],
            desc: errorTarget.baseURI + '@' + (errorTarget.src || errorTarget.href),
            stack: 'no stack'
        };
    }
    
    /**
     * 发送错误日志
     * @param {Object} params
     */
    function logReport(params) {
        let baseInfo = {
            tp: 'jserror',
            site: config.errorSite,
            url: global.location.href,
            version: '',
            os: window.navigator.userAgent
        };
        let [url, msg] = ['', ''];
    
        if (typeof params === 'string') {
            msg = {
                desc: params
            };
        }
        if (typeof params === 'object') {
            msg = params;
        }
        let errorinfo = urlConcat(Object.assign(baseInfo, msg));
    
        // 创建script发送
        let domScript = document.createElement('script');
        if (config.isDev) {
            url = config.devLogURL + errorinfo;
        } else {
            url = config.logURL + errorinfo;
        }
    
        domScript.src = url;
        domScript.onload = () => {
            domScript && domScript.remove && domScript.remove();
            domScript && domScript.removeNode && domScript.removeNode();
        };
        let head = document.head || document.documentElement;
        head.appendChild(domScript);
        return true;
    }
    
    /**
     * 错误数据预处理
     * @param {Object} errorLog 错误日志
     */
    function handleError(errorLog) {
        // 是否延时处理
        if (!config.delay) {
            !needReport(config.sampling) || logReport(errorLog);
        } else {
            pushError(errorLog);
            report(errorList);
        }
    }
    
    /**
     * debounce 节流函数
     * @param {Function} func 实际要执行的函数
     * @param {Number} delay 延迟时间,单位是 ms
     * @param {Function} callback 在 func 执行后的回调
     *
     * @return {Function}
     */
    function debounce(func, delay, callback) {
        let timer;
    
        return () => {
            let that = this;
            let args = arguments;
    
            clearTimeout(timer);
    
            timer = setTimeout(() => {
                func.apply(that, args);
                !callback || callback();
            }, delay);
        };
    }
    
    function __config(opts) {
        // merge配置
        Object.assign(config, opts);
    
        report = debounce(logReport, config.delay, () => {
            errorList = [];
        });
    }
    
    // 初始化
    function __init() {
        // 监听 Javascript 报错
        window.onerror = (...arg) => {
            if (ignoreError) {
                ignoreError = false;
                return;
            }
            handleError(formatRuntimerError.apply(null, arg));
        };
    
        // 针对vue的console.error 劫持
        console.error = (origin => {
            return info => {
                let errorLog = {
                    type: ERROR_CONSOLE,
                    desc: info,
                    stack: 'no stack'
                };
                handleError(errorLog);
                origin.call(console, info);
            };
        })(console.error);
    }
    
    // 入口函数
    Monitor.init = opts => {
        __config(opts, config);
        __init();
    };
    
    Monitor.handleError = handleError;
    
    export default Monitor;
    
    

    相同的,如果我们有自定义埋点的需求,也可以通过这种方式实现。

    二、直接使用sentry框架

    https://sentry.io/
    什么都有

    image.png
    image.png

    相关文章

      网友评论

          本文标题:前端error上报

          本文链接:https://www.haomeiwen.com/subject/iiwjgrtx.html