引言
在前端开发中,JS错误是一个常见的问题,它可能导致页面崩溃、功能异常或用户体验下降。为了及时发现和解决这些问题,我们需要在前端监控SDK中实现JS错误的捕获。
监听全局未捕获的错误
window.addEventListener('error', callback)
js
window.addEventListener('error', function(event) {
let lastEvent = getLastEvent()
if (event.target && (event.target.src || event.target.href)) {
tracker.send({
kind: 'stability', // 稳定性(大类)
type: 'error', // 小类(js error)
errorType: 'resourceError', // 资源加载错误
tagName: event.target.tagName, // 报错标签
filename: event.target.src || event.target.href, // 报错文件
selector: getSelector(event.target) // 最后一个操作的元素
})
} else {
tracker.send({
kind: 'stability', // 稳定性(大类)
type: 'error', // 小类(js error)
errorType: 'jsError', // JS执行错误
message: event.message, // 报错信息
filename: event.filename, // 报错文件
position: `${event.lineno}:${event.colno}`, // 报错位置 行:列
stack: getStackLines(event.error.stack),
selector: lastEvent ? getSelector(lastEvent.path) : '' // 最后一个操作的元素
})
}
}, true)
这段代码是用来监听全局错误事件的。当页面发生错误时,会触发error
事件,并执行相应的处理逻辑。
首先,代码通过window.addEventListener('error', function(event) { ... })
来添加一个错误事件的监听器。
在事件处理函数中,首先通过getLastEvent()
函数获取最后一个事件对象。
然后,代码判断错误类型。如果错误是由于资源加载失败引起的(例如图片加载失败),则会发送一个资源加载错误信息。这个信息包括了错误的类型、标签名、文件名和最后一个操作的元素等。
如果错误是由于JS执行引起的(例如语法错误或未定义变量等),则会发送一个JS执行错误信息。这个信息包括了错误的类型、报错信息、报错文件名、报错位置和堆栈跟踪等。
最后,根据不同类型的错误,调用tracker.send()
方法发送相应的错误信息到服务器进行记录和分析。
getLastEvent.js
js
let lastEvent
['click', 'touchstart', 'mousedown', 'keydown', 'mouseover'].forEach(eventType => {
document.addEventListener(eventType, (event) => {
lastEvent = event
}, {
capture: true,
passive: true // 默认不阻止默认事件
})
})
export default function () {
return lastEvent
}
通过监听多个事件(包括click
、touchstart
、mousedown
、keydown
和mouseover
)来更新这个变量的值。代码通过循环遍历事件类型数组 ,对每个事件类型都调用 document.addEventListener(eventType, (event) => { ... })
来添加事件监听器。
在事件监听器中,当事件触发时,会将当前的事件对象赋值给 lastEvent
变量。这样就可以实时更新 lastEvent
变量的值为最后一个触发的事件对象。
在添加事件监听器时,还传入了一个配置对象 { capture: true, passive: true }
。其中 capture: true
表示在捕获阶段处理事件,而不是冒泡阶段;而 passive: true
表示默认不阻止默认事件的执行。
最后,代码通过导出一个函数来返回 lastEvent
变量的值。这样其他模块可以通过调用这个函数来获取最后一个触发的事件对象。
总体来说,对多个不同类型的事件进行监听,并记录最后一个触发的事件对象。这可以用于在需要获取最后一次用户操作或交互行为时使用。
getSelector.js
js
// 定义一个函数,用于根据路径数组生成选择器字符串
function getSelectors(path) {
// 反转路径数组,并过滤掉 window 和 document 元素
return path.reverse().filter(element => {
return element !== window && element !== document;
}).map((element) => {
// 根据元素的属性生成选择器字符串
if (element.id) {
return `${element.tagName.toLowerCase()}#${element.id}`;
} else if (element.className && typeof element.className === 'string') {
return `${element.nodeName.toLowerCase()}.${element.className}`;
} else {
return element.nodeName.toLowerCase();
}
}).join(' '); // 将选择器字符串连接起来并返回
}
// 默认导出一个函数,用于根据路径数组或目标元素生成选择器字符串
export default function (pathsOrTarget) {
if (Array.isArray(pathsOrTarget)) { // 如果传入的是路径数组
return getSelectors(pathsOrTarget); // 调用 getSelectors 函数生成选择器字符串并返回
} else { // 如果传入的是目标元素
let path = [];
while (pathsOrTarget) { // 循环遍历目标元素的父节点,将其添加到路径数组中
path.push(pathsOrTarget);
pathsOrTarget = pathsOrTarget.parentNode;
}
return getSelectors(path); // 调用 getSelectors 函数生成选择器字符串并返回
}
}
getSelectors
函数用于根据路径数组生成选择器字符串,而默认导出的函数则用于根据路径数组或目标元素生成选择器字符串。
getSelectors
函数首先对路径数组进行反转,并使用 filter
方法过滤掉 window
和 document
元素。然后使用 map
方法遍历剩余的元素,并根据元素的属性生成相应的选择器字符串。如果元素有 id 属性,则返回类似于 <tagName>#<id>
的格式;如果元素有 className 属性,则返回类似于 <tagName>.<className>
的格式;否则,只返回标签名。最后,使用 join
方法将选择器字符串连接起来并返回。
默认导出的函数根据传入的参数类型进行判断。如果传入的是路径数组,则直接调用 getSelectors
函数生成选择器字符串并返回。如果传入的是目标元素,则通过循环遍历目标元素的父节点,将其添加到路径数组中,然后再调用 getSelectors
函数生成选择器字符串并返回。
getStackLines
方法
js
function getStackLines(stack) {
return stack.split('\n').slice(1).map(item => item.replace(/^\s+at\s+/g, "")).join('^')
}
首先使用 split('\n')
方法将堆栈跟踪字符串按换行符分割成数组。 然后使用 slice(1)
方法去掉数组中的第一行,因为第一行通常是错误信息本身,不包含具体的调用栈信息。
接下来,使用 map()
方法遍历数组中的每一行,并使用正则表达式和 replace()
方法将每行开头的 "at " 替换为空字符串,以去除每行前面的 "at " 标识。
最后,使用 join('^')
方法将处理后的数组元素连接成一个字符串,并以 "^" 符号作为分隔符。 这样就得到了经过处理的堆栈跟踪信息字符串。该函数通常用于提取和格式化错误堆栈跟踪信息,以便更好地理解和调试错误。
event
错误信息
处理后需上传的数据
json
{
"title": "前端监控",
"url": "http://127.0.0.1:5500/projects/bug-report/packages/sdk/src/index.html",
"timestamp": 1703152216271,
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"kind": "stability",
"type": "error",
"errorType": "jsError",
"message": "Uncaught TypeError: Cannot set properties of undefined (setting 'error')",
"filename": "http://127.0.0.1:5500/projects/bug-report/packages/sdk/src/index.html",
"position": "47:28",
"stack": "errorClick (http://127.0.0.1:5500/projects/bug-report/packages/sdk/src/index.html:47:28)^HTMLInputElement.onclick (http://127.0.0.1:5500/projects/bug-report/packages/sdk/src/index.html:19:67)",
"selector": ""
}
总结
通过addEventListener方法监听全局的error事件,当页面中加载资源(如图片、脚本等)失败时,会触发该事件,并将相关信息传递给回调函数。我们可以通过监听error事件来捕获资源加载失败的错误。