最近在做前端的监控时,研究了一下sentry的异常监控方案,特此记录一下。
主要介绍 window.onerror、window.onunhandledrejection 以及请求上报 三个方面来了解sentry源码
文中用到的sentry代码版本为5.16.0-beta.5,地址为 sentry-javascript/packages at master · getsentry/sentry-javascript · GitHub
关于前端监控的一些知识可以见 前端监控的思考
Sentry前端异常监控基本原理
异常捕获原理之重写window.onerror方法
let _oldOnErrorHandler: OnErrorEventHandler = null;
/** JSDoc */
function instrumentError(): void {
_oldOnErrorHandler = global.onerror;
global.onerror = function (msg: any, url: any, line: any, column: any, error: any): boolean {
triggerHandlers('error', {
column,
error,
line,
msg,
url,
});
if (_oldOnErrorHandler) {
return _oldOnErrorHandler.apply(this, arguments);
}
return false;
};
}
具体的处理流程为
class GlobalHandlers {
// 收集全局的 window.onerror错误
/** JSDoc */
private _installGlobalOnErrorHandler(): void {
if (this._onErrorHandlerInstalled) {
return;
}
// 通过事件订阅的方式,指定当 发生特定类型的错误(参数中的type字段)时,触发callBack事件
addInstrumentationHandler({
/*
msg:错误信息(字符串)。可用于HTML onerror=""处理程序中的event。
url:发生错误的脚本URL(字符串)
line:发生错误的行号(数字)
column:发生错误的列号(数字)
error:Error对象(对象)或 其他类型
*/
callback: (data: { msg: any; url: any; line: any; column: any; error: any }) => {
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();
const event = isPrimitive(error) // 判断是否为基本类型
? this._eventFromIncompleteOnError(data.msg, data.url, data.line, data.column)// 仅上报错误附加信息,没有具体错误内容
: this._enhanceEventWithInitialFrame(
// 返回Event 对象,包括错误的堆栈位置的错误信息
/** eg 堆栈位置的错误信息
* "Error: test error
at Module../src/main.js (http://dev.xiguacity.club:3001/static/js/main.bundle.js:96299:68)
at __webpack_require__ (http://dev.xiguacity.club:3001/static/js/main.bundle.js:790:30)
at fn (http://dev.xiguacity.club:3001/static/js/main.bundle.js:101:20)
at Object.1 (http://dev.xiguacity.club:3001/static/js/main.bundle.js:101544:18)
at __webpack_require__ (http://dev.xiguacity.club:3001/static/js/main.bundle.js:790:30)
at http://dev.xiguacity.club:3001/static/js/main.bundle.js:857:37
at http://dev.xiguacity.club:3001/static/js/main.bundle.js:860:10"
*/
eventFromUnknownInput(error, undefined, {
attachStacktrace: client && client.getOptions().attachStacktrace,// 是否收集错误的堆栈信息
rejection: false,
}),
data.url,
data.line,
data.column,
);
// 相当于 Object.assign(event.exception!.values![0], { handled: false, type: 'onerror' })
addExceptionMechanism(event, {
handled: false,
type: 'onerror',
});
// 添加eventId 序列化数据 之后发送数据 ajax fetch or beacon
currentHub.captureEvent(event, {
originalException: error,
});
},
type: 'error',
});
this._onErrorHandlerInstalled = true;
}
}
异常捕获原理之重写window.unhandleRejection方法
let _oldOnUnhandledRejectionHandler: ((e: any) => void) | null = null;
/** JSDoc */
function instrumentUnhandledRejection(): void {
_oldOnUnhandledRejectionHandler = global.onunhandledrejection;
global.onunhandledrejection = function (e: any): boolean {
triggerHandlers('unhandledrejection', e);
if (_oldOnUnhandledRejectionHandler) {
return _oldOnUnhandledRejectionHandler.apply(this, arguments);
}
return true;
};
}
具体异常处理的流程为
/** JSDoc */
private _installGlobalOnUnhandledRejectionHandler(): void {
if (this._onUnhandledRejectionHandlerInstalled) {
return;
}
addInstrumentationHandler({
callback: (e: any) => {
let error = e;
// dig the object of the rejection out of known event types
try {
// PromiseRejectionEvents store the object of the rejection under 'reason'
// see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
if ('reason' in e) {
error = e.reason;
}
// something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents
// to CustomEvents, moving the `promise` and `reason` attributes of the PRE into
// the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec
// see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and
// https://github.com/getsentry/sentry-javascript/issues/2380
else if ('detail' in e && 'reason' in e.detail) {
error = e.detail.reason;
}
} catch (_oO) {
// no-empty
}
// 后续与 _installGlobalOnErrorHandler流程一致 省略不写
this._onUnhandledRejectionHandlerInstalled = true;
}
异常上报机制
protected _setupTransport(): Transport {
if (!this._options.dsn) {
// We return the noop transport here in case there is no Dsn.
return super._setupTransport();
}
const transportOptions = {
...this._options.transportOptions,
dsn: this._options.dsn,
};
// 若有自定义的上报方式,则使用自定义的
if (this._options.transport) {
return new this._options.transport(transportOptions);
}
// 默认先使用fetch上报,否则使用xhr
if (supportsFetch()) {
return new FetchTransport(transportOptions);
}
return new XHRTransport(transportOptions);
}
未完待续中…