前言
之前讨论了fetch
、XHR
是如何捕获的,本次我们来看看js
异常捕获是如何做到的。
关于addInstrumentationHandler
和fill
方法可以在第一篇文章中了解。
之前的文章可以看这里:
- sentry-javascript解析(一)fetch如何捕获
- sentry-javascript解析(二)XHR如何捕获
前置准备
常见的代码异常
我们列举几个常见的代码异常
js代码异常
function demoA(params) {
params.func();
}
demoA();
dom操作异常
var node = document.body.children[0];
var nextNode = node.nextSibling;
var div = document.createTextNode('div');
node.insertBefore(div, nextNode);
Promise异常
new Promise((resolve, reject) => {
reject();
});
资源加载异常
const img = document.createElement('img');
img.src = 'abc.png';
document.body.appendChild(img);
异常错误事件
通过上面的代码我们可以发现不同情况,出现的错误不同,接下来我们看一下这些异常都会触发哪些异常事件。
onerror 和 error 事件
我们最常见的就是window.onerror
和addEventListener('error', callback)
,那么它们两个能捕获哪些异常呢?它们两个又有什么区别呢?mdn - GlobalEventHandlers.onerror
window.onerror
是一个全局变量,当有js
运行时触发错误,window
会触发error
事件,并执行window.onerror()
。- 监听
js
运行时错误事件,会比window.onerror
先触发,可以全局捕获资源加载异常的错误。
unhandledrejection 事件
当Promise
被reject
且没有reject
处理器的时候,会触发unhandledrejection
事件。mdn - unhandledrejection
js异常错误捕获
接下来我们看一下sentry
是如何做异常捕获的。
onerror捕获
高阶函数封装onerror
按照addInstrumentationHandler
的代码我们可以准确看出通过type: 'error'
接下来应该执行instrumentError
方法,我们来看一下这个方法的代码:
// 全局闭包缓存当前window.onerror
let _oldOnErrorHandler: OnErrorEventHandler = null;
function instrumentError(): void {
// 全局闭包缓存当前window.onerror
_oldOnErrorHandler = global.onerror;
// 重置window.onerror
global.onerror = function(msg, url, line, column, error): boolean {
// 遍历onerror对应的回调
triggerHandlers('error', {
column,
error,
line,
msg,
url,
});
// 如果当前已经有设置onerror则继续调用执行
if (_oldOnErrorHandler) {
return _oldOnErrorHandler.apply(this, arguments);
}
return false;
};
}
onerror对应回调
我们了解完onerror
是如何封装之后,再来看看对应的回调里都做了什么。我们可以在@sentry/browser
的src/integrations/globalhandlers.ts
中找到对应的代码。
addInstrumentationHandler({
callback: (data: { msg; url; line; column; error }) => {
const error = data.error;
const currentHub = getCurrentHub();
const hasIntegration = currentHub.getIntegration(GlobalHandlers);
const isFailedOwnDelivery = error && error.__sentry_own_request__ === true;
if (!hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) {
return;
}
const client = currentHub.getClient();
// isPrimitive用于判断error的数据类型是否为原始数据类型
// 这里根据error数据类型不同,拼接成统一的数据结构
const event = isPrimitive(error)
? this._eventFromIncompleteOnError(data.msg, data.url, data.line, data.column)
: this._enhanceEventWithInitialFrame(
eventFromUnknownInput(error, undefined, {
attachStacktrace: client && client.getOptions().attachStacktrace,
rejection: false,
}),
data.url,
data.line,
data.column,
);
addExceptionMechanism(event, {
handled: false,
type: 'onerror',
});
// 记录上报本次事件
currentHub.captureEvent(event, {
originalException: error,
});
},
type: 'error',
});
onerror
的回调比较简单,简单来说就是根据捕获到的error
对象数据类型不同,经过特定的整合最终输出统一的数据结构上报就结束了。
unhandledrejection捕获
高阶函数封装unhandledrejection
按照addInstrumentationHandler
的代码我们可以准确看出通过type: 'unhandledrejection'
接下来应该执行instrumentUnhandledRejection
方法,我们来看一下这个方法的代码:
// 全局闭包缓存当前window.onunhandledrejection
let _oldOnUnhandledRejectionHandler: ((e: any) => void) | null = null;
function instrumentUnhandledRejection(): void {
// 全局闭包缓存当前window.onunhandledrejection
_oldOnUnhandledRejectionHandler = global.onunhandledrejection;
// 重置window.onunhandledrejection
global.onunhandledrejection = function(e: any): boolean {
// 遍历unhandledrejection对应回调
triggerHandlers('unhandledrejection', e);
// 如果当前已经有设置unhandledrejection则继续调用执行
if (_oldOnUnhandledRejectionHandler) {
return _oldOnUnhandledRejectionHandler.apply(this, arguments);
}
return true;
};
}
unhandledrejection对应回调
我们了解完unhandledrejection
是如何封装之后,再来看看对应的回调里都做了什么。我们可以在@sentry/browser的src/integrations/globalhandlers.ts
中找到对应的代码。
addInstrumentationHandler({
callback: (e: any) => {
let error = e;
try {
// 判断error是否包含reason字段
// 详情参考https://developer.mozilla.org/zh-CN/docs/Web/API/PromiseRejectionEvent
if ('reason' in e) {
error = e.reason;
} else if ('detail' in e && 'reason' in e.detail) {
// 这里兼容自定义事件获取对应的reason
// 详情参考https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent
error = e.detail.reason;
}
} catch (_oO) {
}
const currentHub = getCurrentHub();
const hasIntegration = currentHub.getIntegration(GlobalHandlers);
const isFailedOwnDelivery = error && error.__sentry_own_request__ === true;
if (!hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) {
return true;
}
const client = currentHub.getClient();
// isPrimitive用于判断error的数据类型是否为原始数据类型
// 这里根据error数据类型不同,拼接成统一的数据结构
const event = isPrimitive(error)
? this._eventFromRejectionWithPrimitive(error)
: eventFromUnknownInput(error, undefined, {
attachStacktrace: client && client.getOptions().attachStacktrace,
rejection: true,
});
event.level = Severity.Error;
addExceptionMechanism(event, {
handled: false,
type: 'onunhandledrejection',
});
// 记录上报本次事件
currentHub.captureEvent(event, {
originalException: error,
});
return;
},
type: 'unhandledrejection',
});
我们发现onunhandledrejection
也是比较简单,根据捕获到的error
数据结构不同,经过特定的整合最终输出统一的数据结构上报就结束了。
与onerror
的区别是兼容考虑了不同的场景:
- 捕获到的
error
包含reason
字段,则以reason
为主 - 捕获到的
error
是继承的CustomEvent
自定义事件,则以detail.reason
为主
总结
sentry
在异常错误捕获会通过onerror
和onunhandledrejection
两个方法进行监听。高阶函数封装也相对比较简单,提前执行sentry
对应的回调上报异常事件。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!