




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
捕获页面中全局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:testisnotdefinedsource:/release/attach.jslineno:16144colno:6error:ReferenceError:testisnotdefinedat/release/attach.js:16144:6atHTMLDocument.<anonymous>(/release/vendor.js:654:71)这种方式看似完美,其实有一个致命的问题。有些浏览器为了安全方面的考虑,对于不同域的Javascript文件,通过window.onError无法获取有效的错误信息。比如firefox的错误消息只有Scripterror,而且无法获得确切的行号,更没有错误堆栈信息:message:Scripterror.source:"/release/attach.jslineno:0colno:0error:null为了使得浏览器针对window.onError的跨域保护失效,我们可以在静态资源服务器或者CDN的HTTP头中加上如下允许跨域提示:Access-Control-Allow-Origin:*并在引用Javascript脚本是加上crossorigin属性:<scriptcrossoriginsrc=""></script>完成上述两步后,我们就可以方便的使用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.length;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.onerroronerrorisaspecialbrowsereventthatfireswheneveranuncaughtJavaScripterrorhasbeenthrown.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.stackAtfirstglancetheErrorobjectisn’tveryspecial.Itcontains3standardizedproperties:message,fileName,andlineNumber.Redundantvaluesthatalreadyprovidedtoyouviawindow.onerror.Thevaluablepartisanon-standardproperty:Etotype.stack.Thisstackpropertytellsyouatwhatsourcelocationeachframeoftheprogramwaswhentheerroroccurred.Thestacktracecanbeacriticalpartofdebugginganerror.Anddespitebeingnon-standard,thispropertyisavailableineverymodernbrowser.Here’sanexampleoftheErrorobject’sstackpropertyinChrome46:"Error:foobar\natnewbar(<anonymous>:241:11)\natfoo(<anonymous>:245:5)\nat<anonymous>:250:5\nat<anonymous>:251:3\nat<anonymous>:267:4\natcallFunction(<anonymous>:229:33)\nat<anonymous>:239:23\nat<anonymous>:240:3\natObject.InjectedScript._evaluateOn(<anonymous>:875:140)\natObject.InjectedScript._evaluateAndWrap(<anonymous>:808:34)"Hardtoread,right?Thestackpropertyisactuallyjustanunformattedstring.Here’swhatitlookslikeformatted:Error:foobaratnewbar(<anonymous>:241:11)atfoo(<anonymous>:245:5)atcallFunction(<anonymous>:229:33)atObject.InjectedScript._evaluateOn(<anonymous>:875:140)atObject.InjectedScript._evaluateAndWrap(<anonymous>:808:34)Onceit’sbeenformatted,it’seasytoseehowthestackpropertycanbecriticalinhelpingtodebuganerror.There’sjustonesnag:thestackpropertyisnon-standard,anditsimplementationdiffersamongbrowsers.Forexample,here’sthesamestacktracefromInternetExplorer11:Error:foobaratbar(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.Browsercompatibilitywindow.onerrorhasbeenavailableinbrowsersforsometime–you’llfinditinbrowsersasoldasIE6andFirefox2.Theproblemisthateverybrowserimplementswindow.onerrordifferently.Particularly,inhowmanyargumentsaresenttototheonerrorlistener,andthestructureofthosearguments.Here’satableofwhichargumentsarepassedtoonerrorinmostbrowsers:Browser Message URL lineNo colNo errorObjFirefox42 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/catchButthereisaworkaround–youcanwrapcodeinyourapplicationinsideatry/catchandcatchtheerroryourself.Thiserrorobjectwillcontainourcovetedstackpropertyineverymodernbrowser.Considerthefollowinghelpermethod,invoke,whichcallsafunctiononanobjectwithanarrayofarguments:functioninvoke(obj,method,args){returnobj[method].apply(this,args);}invoke(Math,'max',[1,2]);//returns2Here’sinvokeagain,thistimewrappedintry/catch,inordertocaptureanythrownerror:functioninvoke(obj,method,args){try{returnobj[method].apply(this,args);}catch(e){captureError(e);//reporttheerrorthrowe;//re-throwtheerror}}invoke(Math,'highest',[1,2]);//throwserror,nomethodMath.highestOfcourse,doingthismanuallyeverywhereisprettycumbersome.Youcanmakeiteasierbycreatingagenericwrapperutilityfunction:functionwrapErrors(fn){//don'twrapfunctionmorethanonceif(!fn.__wrapped__){fn.__wrapped__=function(){try{returnfn.apply(this,arguments);}catch(e){captureError(e);//reporttheerrorthrowe;//re-throwtheerror}};}returnfn.__wrapped__;}varinvoke=wrapErrors(function(obj,method,args){returnobj[method].apply(this,args);});invoke(Math,'highest',[1,2]);//nomethodMath.highestBecauseJavaScriptissinglethreaded,youdon’tneedtousewrapeverywhere–justatthebeginningofeverynewstack.Thatmeansyou’llneedtowrapfunctiondeclarations:Atthestartofyourapplication(e.g.in$(document).readyifyouusejQuery)Ineventhandlers,e.g.addEventListeneror$.fn.clickTimer-basedcallbacks,e.g.setTimeoutorrequestAnimationFrameForexample:$(wrapErrors(function(){//applicationstartdoSynchronousStuff1();//doesn'tneedtobewrappedsetTimeout(wrapErrors(function(){doSynchronousStuff2();//doesn'tneedtobewrapped});$('.foo').click(wrapErrors(function(){doSynchronousStuff3();//doesn'tneedtobewrapped});}));Ifthatseemslikeaheckofalotofwork,don’tworry!Mosterrorreportinglibrarieshavemechanismsforaugmentingbuilt-infunctionslikeaddEventListenerandsetTimeoutsothatyoudon’thavetocallawrappingutilityeverytimeyourself.Andyes,raven-jsdoesthistoo.TransmittingtheerrortoyourserversOkay,soyou’vedoneyourjob–you’vepluggedintowindow.onerror,andyou’readditionallywrappingfunctionsintry/catchinordertocatchasmucherrorinformationaspossible.There’sjustonelaststep:transmittingtheerrorinformationtoyourservers.Inorderforthistowork,you’llneedtosetupsomekindofreportingwebservicethatwillacceptyourerrordataoverHTTP,logittoafileand/storeitinadatabase.Ifthiswebserviceisonthesamedomainasyourwebapplication,thisisachievedeasilybyusingXMLHttpRequest.Intheexamplebelow,weusejQuery’sAJAXfunctiontotransmitthedatatoourservers:functioncaptureError(ex){varerrorData={name:,//e.g.ReferenceErrormessage:ex.line,//e.g.xisundefinedurl:document.location.href,stack:ex.stack//stacktracestring;remember,differentper-browser!};$.post('/logger/js/',{data:errorData});}Notethatifyouhavetotransmityourerroracrossdifferentorigins,yourreportingendpointwillneedtosupportCORS(CrossOriginResourceSharing).SummaryIfyou’vemadeitthisfar,younowhaveallthetoolsyouneedtorollyourownbasicerrorreportinglibraryandintegrateitwithyourapplication:Howwindow.onerrorworks,andwhatbrowsersitsupportsHowtousetry/catchtocapturestacktraceswherewindow.onerrorislackingTransmittingerrordatatoyourserversOfcourse,ifyoudon’twanttobotherwithallofthis,thereareplentyofcommercialandopensourcetoolsthatdoalltheheavy-liftingofclient-sidereportingforyou.(Psst,youmightwanttotrySentry.)That’sit!Happyerrorhunting.SharethisonTwitterFacebookHackerNewsTheweb'scheckenginelight.Sentryprovidesreal-timecrashreportingforyourwebapps,mobileapps,andgames.TrySentryfreefor14daysGlobalEventHandlers.onerrorINTHISARTICLESyntaxwindow.onerrorelement.onerrorNotesSpecificationsBrowsercompatibilitySeealsoAneventhandlerfortheerrorevent.Erroreventsarefiredatvarioustargetsfordifferentkindsoferrors:WhenaJavaScriptruntimeerror(includingsyntaxerrors)occurs,anerroreventusinginterfaceErrorEventisfiredatwindowandwindow.onerror()isinvoked.Whenaresource(suchasan<img>or<script>)failstoload,anerroreventusinginterfaceEventisfiredattheelement,thatinitiatedtheload,andtheonerror()handlerontheelementisinvoked.Theseerroreventsdonotbubbleuptowindow,but(atleastinFirefox)canbehandledwithasinglecapturingwindow.addEventListener.Installingaglobalerroreventhandlerisusefulforautomatedcollectionoferrorreports.SyntaxForhistoricalreasons,differentargumentsarepassedtowindow.onerrorandelement.onerrorhandlers.window.onerrorwindow.onerror=function(message,source,lineno,colno,error){...}Functionparameters:message:errormessage(string).Availableasevent(sic!)inHTMLonerror=""handler.source:URLofthescriptwheretheerrorwasraised(string)lineno:Linenumberwhereerrorwasraised(number)colno:Columnnumberforthelinewheretheerroroccurred(number)error:ErrorObject(object)Whenthefunctionreturnstrue,thispreventsthefiringofthedefaulteventhandler.element.onerrorelement.onerror=function(event){...}element.onerroracceptsafunctionwithasingleargumentoftypeEvent.NotesWhenasyntax(?)erroroccursinascript,loadedfromadifferentorigin,thedetailsofthesyntaxerrorarenotreportedtopreventleakinginformation(seebug363897).Insteadtheerrorreportedissimply"Scripterror."Thisbehaviorcanbeoverrideninsomebrowsersusingthecrossoriginattributeon<script>andhavingtheserversendtheappropriateCORSHTTPresponseheaders.Aworkaroundistoisolate"Scripterror."andhandleitknowingthattheerrordetailisonlyviewableinthebrowserconsoleandnotaccessibleviaJavaScript.window.onerror=function(msg,url,lineNo,columnNo,error){varstring=msg.toLowerCase();varsubstring="scripterror";if(string.indexOf(substring)>-
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 文化创意产品研发资金申请2025年政策扶持与产业升级策略报告
- 2025年新能源汽车废旧电池回收处理技术及案例分析报告
- 2025年生物科技行业可持续发展目标(SDGs)实践与产业融合报告
- 煤炭清洁高效燃烧技术在煤炭洗选加工中的应用与发展报告
- 医疗器械临床试验质量管理与规范化2025年发展趋势研究报告
- 2025年建筑信息模型(BIM)在施工全过程精细化管理中的应用策略报告
- 工业互联网平台量子密钥分发技术在智慧医疗领域的应用与挑战报告
- 2025年电商平台内容营销与种草经济产业链研究报告
- 深度解析:2025年工业互联网平台AR交互技术在制造领域的应用创新报告
- 绿色环保产业资金申请政策变化与应对策略报告2025
- 粮油仓储管理员(高级)职业技能鉴定参考试题(附答案)
- 2024北京朝阳区四年级(下)期末语文试题及答案
- 2025年中考语文常考作文押题《10个主题+15篇范文》
- 2025年新音乐节明星艺人歌手演出场费报价单
- 主要施工机械设备、劳动力、设备材料投入计划及其保证措施
- 云南省昆明市官渡区2023-2024学年五年级下学期期末考试数学试题
- 四柱特高弟子班绝密资料——席学易
- 广安市教育局文件材料归档范围及保管期限表
- (完整版)20以内进位加法和退位减法练习1500题最新(精华版)
- 手术器械台的准备及注意事项
- 清华大学学报投稿模板
评论
0/150
提交评论