You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/** * Ensures all global unhandled exceptions are recorded. * Supported by Gecko and IE. * @param {string} message Error message. * @param {string} url URL of script that generated the exception. * @param {(number|string)} lineNo The line number at which the error occurred. * @param {(number|string)=} columnNo The column number at which the error occurred. * @param {Error=} errorObj The actual Error object. * @memberof TraceKit.report */functiontraceKitWindowOnError(message,url,lineNo,columnNo,errorObj){varstack=null;if(lastExceptionStack){TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack,url,lineNo,message);processLastException();}elseif(errorObj){stack=TraceKit.computeStackTrace(errorObj);notifyHandlers(stack,true,errorObj);}else{varlocation={'url': url,'line': lineNo,'column': columnNo};varname;varmsg=message;// must be new var or will modify original `arguments`if({}.toString.call(message)==='[object String]'){vargroups=message.match(ERROR_TYPES_RE);if(groups){name=groups[1];msg=groups[2];}}location.func=TraceKit.computeStackTrace.guessFunctionName(location.url,location.line);location.context=TraceKit.computeStackTrace.gatherContext(location.url,location.line);stack={'name': name,'message': msg,'mode': 'onerror','stack': [location]};notifyHandlers(stack,true,null);}if(_oldOnerrorHandler){return_oldOnerrorHandler.apply(this,arguments);}returnfalse;}
/** * Computes a stack trace for an exception. * @param {Error} ex * @param {(string|number)=} depth * @memberof TraceKit.computeStackTrace */functioncomputeStackTrace(ex,depth){varstack=null;depth=(depth==null ? 0 : +depth);try{// This must be tried first because Opera 10 *destroys*// its stacktrace property if you try to access the stack// property first!!stack=computeStackTraceFromStacktraceProp(ex);if(stack){returnstack;}}catch(e){if(debug){throwe;}}try{stack=computeStackTraceFromStackProp(ex);if(stack){returnstack;}}catch(e){if(debug){throwe;}}try{stack=computeStackTraceFromOperaMultiLineMessage(ex);if(stack){returnstack;}}catch(e){if(debug){throwe;}}try{stack=computeStackTraceByWalkingCallerChain(ex,depth+1);if(stack){returnstack;}}catch(e){if(debug){throwe;}}return{'name': ex.name,'message': ex.message,'mode': 'failed'};}
该方法中对异常分为 4 种类型进行处理:
computeStackTraceFromStacktraceProp(ex) 针对 Opera 10+ 中抛出的异常进行处理;
/** * Computes stack trace information from the stack property. * Chrome and Gecko use this property. * @param {Error} ex * @return {?TraceKit.StackTrace} Stack trace information. * @memberof TraceKit.computeStackTrace */functioncomputeStackTraceFromStackProp(ex){if(!ex.stack){returnnull;}varchrome=/^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,gecko=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i,winjs=/^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,// Used to additionally parse URL/line/column from eval framesisEval,geckoEval=/(\S+) line (\d+)(?: > eval line \d+)* > eval/i,chromeEval=/\((\S*)(?::(\d+))(?::(\d+))\)/,lines=ex.stack.split('\n'),stack=[],submatch,parts,element,reference=/^(.*) is undefined$/.exec(ex.message);for(vari=0,j=lines.length;i<j;++i){if((parts=chrome.exec(lines[i]))){varisNative=parts[2]&&parts[2].indexOf('native')===0;// start of lineisEval=parts[2]&&parts[2].indexOf('eval')===0;// start of lineif(isEval&&(submatch=chromeEval.exec(parts[2]))){// throw out eval line/column and use top-most line/column numberparts[2]=submatch[1];// urlparts[3]=submatch[2];// lineparts[4]=submatch[3];// column}element={'url': !isNative ? parts[2] : null,'func': parts[1]||UNKNOWN_FUNCTION,'args': isNative ? [parts[2]] : [],'line': parts[3] ? +parts[3] : null,'column': parts[4] ? +parts[4] : null};}elseif(parts=winjs.exec(lines[i])){element={'url': parts[2],'func': parts[1]||UNKNOWN_FUNCTION,'args': [],'line': +parts[3],'column': parts[4] ? +parts[4] : null};}elseif((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 numberparts[3]=submatch[1];parts[4]=submatch[2];parts[5]=null;// no column when eval}elseif(i===0&&!parts[5]&&!_isUndefined(ex.columnNumber)){// 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 evalstack[0].column=ex.columnNumber+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=guessFunctionName(element.url,element.line);}element.context=element.line ? gatherContext(element.url,element.line) : null;stack.push(element);}if(!stack.length){returnnull;}if(stack[0]&&stack[0].line&&!stack[0].column&&reference){stack[0].column=findSourceInLine(reference[1],stack[0].url,stack[0].line);}return{'mode': 'stack','name': ex.name,'message': ex.message,'stack': stack};}
/** * Cross-browser processing of unhandled exceptions * * Syntax: * ```js * TraceKit.report.subscribe(function(stackInfo) { ... }) * TraceKit.report.unsubscribe(function(stackInfo) { ... }) * TraceKit.report(exception) * try { ...code... } catch(ex) { TraceKit.report(ex); } * ``` * * Supports: * - Firefox: full stack trace with line numbers, plus column number * on top frame; column number is not guaranteed * - Opera: full stack trace with line and column numbers * - Chrome: full stack trace with line and column numbers * - Safari: line and column number for the top frame only; some frames * may be missing, and column number is not guaranteed * - IE: line and column number for the top frame only; some frames * may be missing, and column number is not guaranteed * * In theory, TraceKit should work on all of the following versions: * - IE5.5+ (only 8.0 tested) * - Firefox 0.9+ (only 3.5+ tested) * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require * Exceptions Have Stacktrace to be enabled in opera:config) * - Safari 3+ (only 4+ tested) * - Chrome 1+ (only 5+ tested) * - Konqueror 3.5+ (untested) * * Requires TraceKit.computeStackTrace. * * Tries to catch all unhandled exceptions and report them to the * subscribed handlers. Please note that TraceKit.report will rethrow the * exception. This is REQUIRED in order to get a useful stack trace in IE. * If the exception does not reach the top of the browser, you will only * get a stack trace from the point where TraceKit.report was called. * * Handlers receive a TraceKit.StackTrace object as described in the * TraceKit.computeStackTrace docs. * * @memberof TraceKit * @namespace */TraceKit.report=(functionreportModuleWrapper(){varhandlers=[],lastException=null,lastExceptionStack=null;/** * Add a crash handler. * @param {Function} handler * @memberof TraceKit.report */functionsubscribe(handler){installGlobalHandler();installGlobalUnhandledRejectionHandler();handlers.push(handler);}/** * Remove a crash handler. * @param {Function} handler * @memberof TraceKit.report */functionunsubscribe(handler){for(vari=handlers.length-1;i>=0;--i){if(handlers[i]===handler){handlers.splice(i,1);}}if(handlers.length===0){uninstallGlobalHandler();uninstallGlobalUnhandledRejectionHandler();}}/** * Dispatch stack information to all handlers. * @param {TraceKit.StackTrace} stack * @param {boolean} isWindowError Is this a top-level window error? * @param {Error=} error The error that's being handled (if available, null otherwise) * @memberof TraceKit.report * @throws An exception if an error occurs while calling an handler. */functionnotifyHandlers(stack,isWindowError,error){varexception=null;if(isWindowError&&!TraceKit.collectWindowErrors){return;}for(variinhandlers){if(_has(handlers,i)){try{handlers[i](stack,isWindowError,error);}catch(inner){exception=inner;}}}if(exception){throwexception;}}var_oldOnerrorHandler,_onErrorHandlerInstalled;var_oldOnunhandledrejectionHandler,_onUnhandledRejectionHandlerInstalled;/** * Ensures all global unhandled exceptions are recorded. * Supported by Gecko and IE. * @param {string} message Error message. * @param {string} url URL of script that generated the exception. * @param {(number|string)} lineNo The line number at which the error occurred. * @param {(number|string)=} columnNo The column number at which the error occurred. * @param {Error=} errorObj The actual Error object. * @memberof TraceKit.report */functiontraceKitWindowOnError(message,url,lineNo,columnNo,errorObj){varstack=null;if(lastExceptionStack){TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack,url,lineNo,message);processLastException();}elseif(errorObj){stack=TraceKit.computeStackTrace(errorObj);notifyHandlers(stack,true,errorObj);}else{varlocation={'url': url,'line': lineNo,'column': columnNo};varname;varmsg=message;// must be new var or will modify original `arguments`if({}.toString.call(message)==='[object String]'){vargroups=message.match(ERROR_TYPES_RE);if(groups){name=groups[1];msg=groups[2];}}location.func=TraceKit.computeStackTrace.guessFunctionName(location.url,location.line);location.context=TraceKit.computeStackTrace.gatherContext(location.url,location.line);stack={'name': name,'message': msg,'mode': 'onerror','stack': [location]};notifyHandlers(stack,true,null);}if(_oldOnerrorHandler){return_oldOnerrorHandler.apply(this,arguments);}returnfalse;}/** * Ensures all unhandled rejections are recorded. * @param {PromiseRejectionEvent} e event. * @memberof TraceKit.report * @see https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onunhandledrejection * @see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent */functiontraceKitWindowOnUnhandledRejection(e){varstack=TraceKit.computeStackTrace(e.reason);notifyHandlers(stack,true,e.reason);}/** * Install a global onerror handler * @memberof TraceKit.report */functioninstallGlobalHandler(){if(_onErrorHandlerInstalled===true){return;}_oldOnerrorHandler=window.onerror;window.onerror=traceKitWindowOnError;_onErrorHandlerInstalled=true;}/** * Uninstall the global onerror handler * @memberof TraceKit.report */functionuninstallGlobalHandler(){if(_onErrorHandlerInstalled){window.onerror=_oldOnerrorHandler;_onErrorHandlerInstalled=false;}}/** * Install a global onunhandledrejection handler * @memberof TraceKit.report */functioninstallGlobalUnhandledRejectionHandler(){if(_onUnhandledRejectionHandlerInstalled===true){return;}_oldOnunhandledrejectionHandler=window.onunhandledrejection;window.onunhandledrejection=traceKitWindowOnUnhandledRejection;_onUnhandledRejectionHandlerInstalled=true;}/** * Uninstall the global onunhandledrejection handler * @memberof TraceKit.report */functionuninstallGlobalUnhandledRejectionHandler(){if(_onUnhandledRejectionHandlerInstalled){window.onunhandledrejection=_oldOnunhandledrejectionHandler;_onUnhandledRejectionHandlerInstalled=false;}}/** * Process the most recent exception * @memberof TraceKit.report */functionprocessLastException(){var_lastExceptionStack=lastExceptionStack,_lastException=lastException;lastExceptionStack=null;lastException=null;notifyHandlers(_lastExceptionStack,false,_lastException);}/** * Reports an unhandled Error to TraceKit. * @param {Error} ex * @memberof TraceKit.report * @throws An exception if an incomplete stack trace is detected (old IE browsers). */functionreport(ex){if(lastExceptionStack){if(lastException===ex){return;// already caught by an inner catch block, ignore}else{processLastException();}}varstack=TraceKit.computeStackTrace(ex);lastExceptionStack=stack;lastException=ex;// If the stack trace is incomplete, wait for 2 seconds for// slow slow IE to see if onerror occurs or not before reporting// this exception; otherwise, we will end up with an incomplete// stack tracesetTimeout(function(){if(lastException===ex){processLastException();}},(stack.incomplete ? 2000 : 0));throwex;// re-throw to propagate to the top level (and cause window.onerror)}report.subscribe=subscribe;report.unsubscribe=unsubscribe;returnreport;}());
目录
引子
前端异常研究时,发现了 TraceKit 这个库, sentry 里面部分功能也基于这个库再改造了,就去看了下源码。
TraceKit 版本: v0.4.6 。
简介
TraceKit 对浏览器堆栈进行解析追踪,对市场上主要的浏览器都做了测试。在浏览器异常一些方面做了比较详尽的处理。
思路
源码中的一些思路:
onerror
和onunhandledrejection
事件进行了包装,支持撤销包装。下面针对主要的逻辑进行介绍。
具体实现
捕获异常并解析主要有三种途径: onerror 事件、 onunhandledrejection 事件、 TraceKit.report(ex) 。
onerror 事件
源码
封装后的
onerror
事件处理程序中将异常分为了三类:lastExceptionStack
记录的值;errorObj
有值的情况;比较多的异常会在第二类中进行处理,执行
TraceKit.computeStackTrace(ex, depth)
方法。源码
该方法中对异常分为 4 种类型进行处理:
computeStackTraceFromStacktraceProp(ex)
针对 Opera 10+ 中抛出的异常进行处理;computeStackTraceFromStackProp(ex)
针对 Chrome、 Gecko 中抛出的异常进行处理;computeStackTraceFromOperaMultiLineMessage(ex)
针对 Opera 9 及其更早版本抛出的异常进行处理;computeStackTraceByWalkingCallerChain(ex)
针对 Safari 、 IE 中抛出的异常进行处理;看看使用比较多的 Chrome 中的处理
computeStackTraceFromStackProp(ex)
方法。源码
对异常信息中
stack
处理的思路:stack
中字符串信息,以\n
进行分割得到数组,然后进行遍历进行正则匹配,提供了 3 种匹配规则,分别是chrome
、gecko
、winjs
;chrome
匹配,如果不符合,再进行winjs
,如果不符合 ,再进行gecko
匹配;这里需要提一下针对 Safari、 IE 中的处理,添加一个额外的参数
incomplete
参数,表示是否完成了异常的处理。在另外一个地方会用到。onunhandledrejection 事件
源码
onunhandledrejection
事件处理程序同样是使用了TraceKit.computeStackTrace(ex, depth)
方法。TraceKit.report(ex)
这是一种主动的上报方式,也是对外暴露的一个方法。
源码
源码中是一个立即执行函数,返回了
report(ex)
函数,并给这个函数增加了subscribe
和unsubscribe
属性,分支指向了subscribe(handler)
函数和unsubscribe(handler)
函数。接下来看看执行 report 第一次上报的时候做了什么:
TraceKit.computeStackTrace
方法,处理后结果赋给lastExceptionStack
,源异常ex
赋给lastException
。setTimeout
执行了一个逻辑:如果lastException === ex
,则执行processLastException()
方法,该方法会重置lastException
和lastExceptionStack
,把已处理的异常通知给订阅者。延时的时间由上面提过的参数incomplete
决定。throw
给上一层,触发window.onerror
。onerror
中,lastExceptionStack
有值会优先处理,并会添加了incomplete
参数,最终也会执行processLastException()
方法。数据格式
处理之后数据格式中可能有的属性:
stack
表示从异常ex.stack
中解析;stacktrace
表示从ex.stacktrace
中解析,针对的是 Opera 10+ ;multiline
表示从ex.message
中进行解析,针对的是 Opera 9 及更早版本 ;callers
表示根据arguments.caller
进行解析,主要针对 Safari 和 IE;onerror
表示onerror
事件中处理特殊一类异常;failed
表示解析失败。url
(导致异常的脚本路径) 和lineNo
(导致异常的脚本行数),才会有partial
属性。其中 stack 的存放对象格式:
特殊情况
上面是分析正常情况下的逻辑,比较极端的情况,需要同时结合几种处理逻辑进行判断。
情况 1
情景: 没有使用
TraceKit.report(ex)
方法,应用程序中出现了死循环,一直抛出异常。预计: 全局的异常捕获,会跟随死循环不停的上报异常,这也是一个风险点。
情况 2
情景: 使用
TraceKit.report(ex)
方法,异常 A 进入 ,刚执行完,这时异常 B 进入了。预计: 这时
lastExceptionStack
可能仍有值,那么就会进入到processLastException()
中,但 A 异常抛到onerror
,在onerror
处理程序中,这时lastExceptionStack
有值,就会处理,最终也会执行processLastException()
。如果捕获 A 异常的onerror
事件处理程序先执行了,那么 B 异常可以按照正常逻辑处理;如果 B 异常处理先执行,那么 A 异常的处理就会少一步,这样就会导致同类上报的信息产生了差异。尝试模拟这样的情况没有达到成功,是否有这样的情况,不太确定。参考资料
🗑️
大事化小,小事化了。
The text was updated successfully, but these errors were encountered: