前言
一套健壮的系统,肯定少不了维护和监控,前端因为是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
网友评论