前端error上报

2022-08-17  本文已影响0人  Yinzhishan

前言

一套健壮的系统,肯定少不了维护和监控,前端因为是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
上一篇下一篇

猜你喜欢

热点阅读