在 Sentry的前端异常监控方案中以前咱们说过,Sentry的全局异常获取方式有2种,window.onerror以及unhandledrejection。javascript
以unhandledrejection为例 globalhandlers.ts中前端
addInstrumentationHandler({ // eslint-disable-next-line @typescript-eslint/no-explicit-any 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 } const currentHub = getCurrentHub(); const hasIntegration = currentHub.getIntegration(GlobalHandlers); const isFailedOwnDelivery = error && error.__sentry_own_request__ === true; // addEventListener的事件中直接throw Error的时候,shouldIgnoreOnError会为true.即不会上报 // 缘由是在instrumentDOM方法中,用globalDOMEventHandler方法对监听事件包了一层,使得ignoreOnError>0 if (!hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) { return true; } const client = currentHub.getClient(); 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', });
总体流程分为如下几个模块讲解java
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 }
这一块都是在作兼容性处理,适配不一样浏览器中的错误类型git
const currentHub = getCurrentHub(); const hasIntegration = currentHub.getIntegration(GlobalHandlers); const isFailedOwnDelivery = error && error.__sentry_own_request__ === true; if (!hasIntegration || shouldIgnoreOnError() || isFailedOwnDelivery) { return true; }
这里的hasIntergration是在判断是否安装了对应的全局监听函数的插件(初始化的时候就会默认加载)
shouldIgnoreOnError : addEventListener的事件中直接throw Error的时候,shouldIgnoreOnError会为true.即不会上报
缘由是在instrumentDOM方法中,用globalDOMEventHandler方法对监听事件包了一层,使得ignoreOnError>0
isFailedOwnDelivery即判断是否为Sentry自身的请求错误,如果,则不上报。github
const client = currentHub.getClient(); const event = isPrimitive(error) ? this._eventFromRejectionWithPrimitive(error) : eventFromUnknownInput(error, undefined, { attachStacktrace: client && client.getOptions().attachStacktrace, rejection: true, });
export function isPrimitive(wat: any): wat is Primitive { return wat === null || (typeof wat !== 'object' && typeof wat !== 'function'); }
isPrimitive(error)即在判断error是否为原始数据类型
若为原始数据类型,则处理比较简单chrome
private _eventFromRejectionWithPrimitive(reason: Primitive): Event { return { exception: { values: [ { type: 'UnhandledRejection', // String() is needed because the Primitive type includes symbols (which can't be automatically stringified) value: `Non-Error promise rejection captured with value: ${String(reason)}`, }, ], }, }; }
若为引用数据类型,核心处理函数在tracekit.ts为typescript
stack = computeStackTraceFromStacktraceProp(ex); if (stack) { return popFrames(stack, popSize); }
具体就在computeStackTraceFromStacktraceProp,popFrames这2个函数中。segmentfault
function computeStackTraceFromStackProp(ex: any): StackTrace | null { if (!ex || !ex.stack) { return null; } const stack = []; const lines = ex.stack.split('\n'); let isEval; let submatch; let parts; let element; for (let i = 0; i < lines.length; ++i) { if ((parts = chrome.exec(lines[i]))) { const isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line if (isEval && (submatch = chromeEval.exec(parts[2]))) { // throw out eval line/column and use top-most line/column number parts[2] = submatch[1]; // url parts[3] = submatch[2]; // line parts[4] = submatch[3]; // column } element = { // working with the regexp above is super painful. it is quite a hack, but just stripping the `address at ` // prefix here seems like the quickest solution for now. url: parts[2] && parts[2].indexOf('address at ') === 0 ? parts[2].substr('address at '.length) : parts[2], func: parts[1] || UNKNOWN_FUNCTION, args: isNative ? [parts[2]] : [], line: parts[3] ? +parts[3] : null, column: parts[4] ? +parts[4] : null, }; } else if ((parts = winjs.exec(lines[i]))) { element = { url: parts[2], func: parts[1] || UNKNOWN_FUNCTION, args: [], line: +parts[3], column: parts[4] ? +parts[4] : null, }; } else if ((parts = gecko.exec(lines[i]))) { isEval = parts[3] && parts[3].indexOf(' > eval') > -1; if (isEval && (submatch = geckoEval.exec(parts[3]))) { // throw out eval line/column and use top-most line number parts[1] = parts[1] || `eval`; parts[3] = submatch[1]; parts[4] = submatch[2]; parts[5] = ''; // no column when eval } else if (i === 0 && !parts[5] && ex.columnNumber !== void 0) { // FireFox uses this awesome columnNumber property for its top frame // Also note, Firefox's column number is 0-based and everything else expects 1-based, // so adding 1 // NOTE: this hack doesn't work if top-most frame is eval stack[0].column = (ex.columnNumber as number) + 1; } element = { url: parts[3], func: parts[1] || UNKNOWN_FUNCTION, args: parts[2] ? parts[2].split(',') : [], line: parts[4] ? +parts[4] : null, column: parts[5] ? +parts[5] : null, }; } else { continue; } if (!element.func && element.line) { element.func = UNKNOWN_FUNCTION; } stack.push(element); } if (!stack.length) { return null; } return { message: extractMessage(ex), name: ex.name, stack, }; }
能够看出,核心流程其实就是在对不一样的浏览器作兼容处理,以及将数据组合成堆栈的形式promise
将异常数据进行捕获以及处理以后,就是上报流程了。详情能够参看上一篇文档 Sentry的异常数据上报机制浏览器
currentHub.captureEvent(event, { originalException: error, });
在大体了解到了整个异常数据处理流程后,发现重点是在对于不一样浏览器的兼容性处理上,整个链路比较清晰,中间不少繁琐的处理细节也都没有太多的去关注,毕竟时间和精力相对有限,想的是在对Sentry的总体脉络有了必定了解以后,对本身作监控能有不少启发,固然这也是研究Sentry源码的初衷。