捕获页面中全局Javascript异常.docx
- 文档编号:24051909
- 上传时间:2023-05-23
- 格式:DOCX
- 页数:15
- 大小:22.27KB
捕获页面中全局Javascript异常.docx
《捕获页面中全局Javascript异常.docx》由会员分享,可在线阅读,更多相关《捕获页面中全局Javascript异常.docx(15页珍藏版)》请在冰豆网上搜索。
捕获页面中全局Javascript异常
捕获页面中全局Javascript异常
主题UglifyJS
一个流量巨大的前端页面面临的浏览器环境是非常复杂的,尤其是移动端页面(Android的碎片化所致)。
面对如此多样的浏览器环境,常规的测试是无法完全覆盖的,我们需要一种页面脚本异常监控机制作为补充,保证能够发现前端页面脚本异常的原因。
有很多种情况会导致Javascript抛出异常,包括网络失效、语法错误、运行时错误等。
我们希望在页面上有异常发生时,能够获得脚本错误的基本信息、文件url、行号。
接下来我们探讨几种实现方式。
1使用window.onError
浏览器提供了全局的onError函数,我们可以使用它搜集页面上的错误
window.onerror=function(message,source,lineno,colno,error){...}
其中mesage为异常基本信息,source为发生异常Javascript文件url,lineno为发生错误的行号,我们可以通过error.stack获取异常的堆栈信息。
下面是chrome中通过window.onError捕获的错误例子:
message:
UncaughtReferenceError:
testisnotdefined
source:
lineno:
16144
colno:
6
error:
ReferenceError:
testisnotdefined
at
atHTMLDocument.
这种方式看似完美,其实有一个致命的问题。
有些浏览器为了安全方面的考虑,对于不同域的Javascript文件,通过window.onError无法获取有效的错误信息。
比如firefox的错误消息只有Scripterror,而且无法获得确切的行号,更没有错误堆栈信息:
message:
Scripterror.
source:
"
lineno:
0
colno:
0
error:
null
为了使得浏览器针对window.onError的跨域保护失效,我们可以在静态资源服务器或者CDN的HTTP头中加上如下允许跨域提示:
Access-Control-Allow-Origin:
*
并在引用Javascript脚本是加上crossorigin属性:
完成上述两步后,我们就可以方便的使用window.onError进行全局异常捕获,并获取丰富的异常信息了。
但是有时对于第三方的CDN,我们无法添加跨域相关的头信息,下面我们就讨论针这种情况的全局Javascript异常捕获方法。
2使用AST为所有函数加上trycatch
上文中提到了使用window.onError进行浏览器全局异常捕获,但是当我们无法添加跨域相关头信息时,window.onError就失效了。
针对这种情况,我们可以对每一个函数添加trycatch来捕获函数内的异常,但是一个大型项目的函数太多,对每一个函数都手动添加trycatch无疑是一个巨大的工作量。
本文我们借助AST(抽象语法树)技术,对源文件进行预处理,对每个函数自动的添加trycatch。
语法树是对源代码最精确的表示,通过遍历和操作语法树,我们能够精确的控制源代码。
生成JavaScript的AST是一件非常复杂的工作,本文暂时不打算涉及,好在UglifyJS已经有了完整的实现。
比如如下代码:
functiontest(){
vara=1;
varb=2;
console.log(a+b);
}
可以用语法树表示:
通过使用Uglify提供的操作AST(抽象语法树)的API,我们可以对每个函数添加trycatch代码块,并在catch中捕获该函数的一切异常,下面是我的实现(请参考我的github:
try-catch-global.js):
varfs=require('fs');
var_=require('lodash');
varUglifyJS=require('uglify-js');
varisASTFunctionNode=function(node){
returnnodeinstanceofUglifyJS.AST_Defun||nodeinstanceofUglifyJS.AST_Function;
}
varglobalFuncTryCatch=function(inputCode,errorHandler){
if(!
_.isFunction(errorHandler)){
throw'errorHandlershouldbeavalidfunction';
}
varerrorHandlerSource=errorHandler.toString();
varerrorHandlerAST=UglifyJS.parse('('+errorHandlerSource+')(error);');
vartryCatchAST=UglifyJS.parse('try{}catch(error){}');
varinputAST=UglifyJS.parse(inputCode);
vartopFuncScope=[];
//将错误处理函数包裹进入catch中
tryCatchAST.body[0].bcatch.body[0]=errorHandlerAST;
//搜集所有函数
varwalker=newUglifyJS.TreeWalker(function(node){
if(isASTFunctionNode(node)){
topFuncScope.push(node);
}
});
inputAST.walk(walker);
//对函数进行变换,添加trycatch语句
vartransfer=newUglifyJS.TreeTransformer(null,
function(node){
if(isASTFunctionNode(node)&&_.includes(topFuncScope,node)){
//函数内部代码搜集
varstream=UglifyJS.OutputStream();
for(vari=0;i node.body[i].print(stream) } varinnerFuncCode=stream.toString(); //清除trycatch中定义的多余语句 tryCatchAST.body[0].body.splice(0,tryCatchAST.body[0].body.length); //用trycatch包裹函数代码 varinnerTyrCatchNode=UglifyJS.parse(innerFuncCode,{toplevel: tryCatchAST.body[0]}); //获取函数壳 node.body.splice(0,node.body.length); //生成有trycatch的函数 returnUglifyJS.parse(innerTyrCatchNode.print_to_string(),{toplevel: node}); } }); inputAST.transform(transfer); varoutputCode=inputAST.print_to_string({beautify: true}); returnoutputCode; } module.exports.globalFuncTryCatch=globalFuncTryCatch; 借助于globalFuncTryCatch,我们对每个函数进行自动化地添加trycatch语句,并使用自定义的错误处理函数: globalFuncTryCatch(inputCode,function(error){ //此处是异常处理代码,可以上报并记录日志 console.log(error); }); 通过将globalFuncTryCatch功能集成到构建工具中,我们就可以对目标Javascript文件进行trycatch处理。 综上所述: 当静态资源服务器可以添加Access-Control-Allow-Origin: *时,我们可以直接使用window.onError进行全局异常捕获;当静态资源服务器不受控制,window.onError失效,我们可以借助AST技术,自动化地对全部目标Javascript函数添加trycatch来捕获所有异常。 参考文档: CaptureandreportJavaScripterrorswithwindow.onerror onerrorisaspecialbrowsereventthatfireswheneveranuncaughtJavaScripterrorhasbeenthrown.It’soneoftheeasiestwaystologclient-sideerrorsandreportthemtoyourservers.It’salsooneofthemajormechanismsbywhichSentry’sclientJavaScriptintegration(raven-js)works. Youlistentotheonerroreventbyassigningafunctiontowindow.onerror: window.onerror=function(msg,url,lineNo,columnNo,error){ //...handleerror... returnfalse; } Whenanerroristhrown,thefollowingargumentsarepassedtothefunction: msg–Themessageassociatedwiththeerror,e.g.“UncaughtReferenceError: fooisnotdefined” url–TheURLofthescriptordocumentassociatedwiththeerror,e.g.“/dist/app.js” lineNo–Thelinenumber(ifavailable) columnNo–Thecolumnnumber(ifavailable) error–TheErrorobjectassociatedwiththiserror(ifavailable) Thefirstfourargumentstellyouinwhichscript,line,andcolumntheerroroccurred.Thefinalargument,Errorobject,isperhapsthemostvaluable.Let’slearnwhy. TheErrorobjectanderror.stack AtfirstglancetheErrorobjectisn’tveryspecial.Itcontains3standardizedproperties: message,fileName,andlineNumber.Redundantvaluesthatalreadyprovidedtoyouviawindow.onerror. Thevaluablepartisanon-standardproperty: Error.prototype.stack.Thisstackpropertytellsyouatwhatsourcelocationeachframeoftheprogramwaswhentheerroroccurred.Thestacktracecanbeacriticalpartofdebugginganerror.Anddespitebeingnon-standard,thispropertyisavailableineverymodernbrowser. Here’sanexampleoftheErrorobject’sstackpropertyinChrome46: "Error: foobar\natnewbar( 241: 11)\natfoo( 245: 5)\nat 250: 5\nat 251: 3\nat 267: 4\natcallFunction( 229: 33)\nat 239: 23\nat 240: 3\natObject.InjectedScript._evaluateOn( 875: 140)\natObject.InjectedScript._evaluateAndWrap( 808: 34)" Hardtoread,right? Thestackpropertyisactuallyjustanunformattedstring. Here’swhatitlookslikeformatted: Error: foobar atnewbar( 241: 11) atfoo( 245: 5) atcallFunction( 229: 33) atObject.InjectedScript._evaluateOn( 875: 140) atObject.InjectedScript._evaluateAndWrap( 808: 34) Onceit’sbeenformatted,it’seasytoseehowthestackpropertycanbecriticalinhelpingtodebuganerror. There’sjustonesnag: thestackpropertyisnon-standard,anditsimplementationdiffersamongbrowsers.Forexample,here’sthesamestacktracefromInternetExplorer11: Error: foobar atbar(Unknownscriptcode: 2: 5) atfoo(Unknownscriptcode: 6: 5) atAnonymousfunction(Unknownscriptcode: 11: 5) atAnonymousfunction(Unknownscriptcode: 10: 2) atAnonymousfunction(Unknownscriptcode: 1: 73) Notonlyistheformatofeachframedifferent,theframesalsohavelessdetail.Forexample,Chromeidentifiesthatthenewkeywordhasbeenused,andhasgreaterinsightintoevalinvocations.AndthisisjustIE11vsChrome–otherbrowserssimilarhavevaryingformatsanddetail. Luckily,therearetoolsouttherethatnormalizestackpropertiessothatitisconsistentacrossbrowsers.Forexample,raven-jsusesTraceKittonormalizeerrorstrings.There’salsostacktrace.jsandafewotherprojects. Browsercompatibility window.onerrorhasbeenavailableinbrowsersforsometime–you’llfinditinbrowsersasoldasIE6andFirefox2. Theproblemisthateverybrowserimplementswindow.onerrordifferently.Particularly,inhowmanyargumentsaresenttototheonerrorlistener,andthestructureofthosearguments. Here’satableofwhichargumentsarepassedtoonerrorinmostbrowsers: BrowserMessageURLlineNocolNoerrorObj Firefox42 Chrome46 AndroidBrowser4.4 Edge IE11 IE10 IE9,8 Safari9 iOS9 You’llnoticethatthelatestApplebrowsers–SafariandiOS–don’tsupporta5therrorobjectargument.AndwhilethefinalversionofInternetExplorer(11)supportstheerrorobject,Microsoft’slatestbrowser,Edge,doesnot. Withouttheerrorobject,thereisnostacktraceproperty.Thismeansthatthesebrowserscannotretrievevaluablestackinformationfromerrorscaughtbyonerror. Polyfillingwindow.onerrorwithtry/catch Butthereisaworkaround–youcanwrapcodeinyourapplicationinsideatry/catchandcatchtheerroryourself.Thiserrorobjectwillcontainourcovetedstackpropertyineverymodernbrowser. Considerthefollowinghelpermethod,invoke,whichcallsafunctiononanobjectwithanarrayofarguments: functioninvoke(obj,method,args){ returnobj[method].apply(this,args); } invoke(Math,'max',[1,2]);//returns2 Here’sinvokeagain,thistimewrappedintry/catch,inordertocaptureanythrownerror: functioninvoke(obj,method,args){ try{ returnobj[method].apply(this,args); }catch(e){ captureError(e);//reporttheerror throwe;//re-throwtheerror } } invoke(Math,'highest',[1,2]);//throwserror,nomethodMath.highest Ofcourse,doingthismanuallyeverywhereisprettycumbersome.Youcanmakeiteasierbycreatingagenericwrapperutilityfunction: functionwrapErrors(fn){ //don'twrapfunctionmorethanonce if(! fn.__wrapped__){ fn.__wrapped__=function(){ try{ returnfn.apply(this,arguments); }catch(e){ captureError(e);//reporttheerror throwe;//re-throwtheerror } }; } returnfn.__wrapped__; } varinvoke=wrapErrors(function(obj,method,args){ returnobj[method].apply(this,args); }); invoke(Math,'highest',[1,2]);//nomethodMath.highest BecauseJavaScriptissinglethreaded,youdon
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 捕获 页面 全局 Javascript 异常