sentry-javascript解析js异常错误如何捕获

前言

之前讨论了fetch、XHR是如何捕获的,本次我们来看看js异常捕获是如何做到的。

关于addInstrumentationHandler和fill方法可以在第一篇文章中了解。

之前的文章可以看这里:

sentry-javascript解析(一)fetch如何捕获sentry-javascript解析(二)XHR如何捕获

前置准备

常见的代码异常

我们列举几个常见的代码异常

js代码异常

function demoA(params) {
params.func();
}
demoA();
图片[1]-sentry-javascript解析js异常错误如何捕获-卡咪卡咪哈-一个博客

dom操作异常

var node = document.body.children[0];
var nextNode = node.nextSibling;
var div = document.createTextNode(div);
node.insertBefore(div, nextNode);
图片[2]-sentry-javascript解析js异常错误如何捕获-卡咪卡咪哈-一个博客

Promise异常

new Promise((resolve, reject) => {
reject();
});
图片[3]-sentry-javascript解析js异常错误如何捕获-卡咪卡咪哈-一个博客

资源加载异常

const img = document.createElement(img);
img.src = abc.png;
document.body.appendChild(img);
图片[4]-sentry-javascript解析js异常错误如何捕获-卡咪卡咪哈-一个博客

![资源加载异常](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/db149e15c67a4e64acd98824523741a9~tplv-k3u1fbpfcp-watermark.image)

异常错误事件

通过上面的代码我们可以发现不同情况,出现的错误不同,接下来我们看一下这些异常都会触发哪些异常事件。

onerror 和 error 事件

我们最常见的就是window.onerror和addEventListener(error, callback),那么它们两个能捕获哪些异常呢?它们两个又有什么区别呢?GlobalEventHandlers.onerror – Web API 接口参考

window.onerror是一个全局变量,当有js运行时触发错误,window会触发`error`事件,并执行`window.onerror()`。监听`js`运行时错误事件,会比`window.onerror`先触发,可以**全局捕获资源加载异常**的错误。

unhandledrejection 事件

当Promise被reject且没有reject处理器的时候,会触发unhandledrejection事件。unhandledrejection – 事件参考 | MDN

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对应的回调上报异常事件。

    THE END
    喜欢就支持一下吧
    点赞11 分享
    评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容