




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
捕獲頁(yè)面中全局Javascript異常主題UglifyJS一個(gè)流量巨大的前端頁(yè)面面臨的瀏覽器環(huán)境是非常復(fù)雜的,尤其是移動(dòng)端頁(yè)面(Android的碎片化所致)。面對(duì)如此多樣的瀏覽器環(huán)境,常規(guī)的測(cè)試是無(wú)法完全覆蓋的,我們需要一種頁(yè)面腳本異常監(jiān)控機(jī)制作為補(bǔ)充,保證能夠發(fā)現(xiàn)前端頁(yè)面腳本異常的原因。有很多種情況會(huì)導(dǎo)致Javascript拋出異常,包括網(wǎng)絡(luò)失效、語(yǔ)法錯(cuò)誤、運(yùn)行時(shí)錯(cuò)誤等。我們希望在頁(yè)面上有異常發(fā)生時(shí),能夠獲得腳本錯(cuò)誤的基本信息、文件url、行號(hào)。接下來(lái)我們探討幾種實(shí)現(xiàn)方式。1使用window.onError瀏覽器提供了全局的onError函數(shù),我們可以使用它搜集頁(yè)面上的錯(cuò)誤window.onerror=function(message,source,lineno,colno,error){...}其中mesage為異?;拘畔?,source為發(fā)生異常Javascript文件url,lineno為發(fā)生錯(cuò)誤的行號(hào),我們可以通過(guò)error.stack獲取異常的堆棧信息。下面是chrome中通過(guò)window.onError捕獲的錯(cuò)誤例子:message:UncaughtReferenceError:testisnotdefinedsource:/release/attach.jslineno:16144colno:6error:ReferenceError:testisnotdefinedat/release/attach.js:16144:6atHTMLDocument.<anonymous>(/release/vendor.js:654:71)這種方式看似完美,其實(shí)有一個(gè)致命的問(wèn)題。有些瀏覽器為了安全方面的考慮,對(duì)于不同域的Javascript文件,通過(guò)window.onError無(wú)法獲取有效的錯(cuò)誤信息。比如firefox的錯(cuò)誤消息只有Scripterror,而且無(wú)法獲得確切的行號(hào),更沒(méi)有錯(cuò)誤堆棧信息:message:Scripterror.source:"/release/attach.jslineno:0colno:0error:null為了使得瀏覽器針對(duì)window.onError的跨域保護(hù)失效,我們可以在靜態(tài)資源服務(wù)器或者CDN的HTTP頭中加上如下允許跨域提示:Access-Control-Allow-Origin:*并在引用Javascript腳本是加上crossorigin屬性:<scriptcrossoriginsrc=""></script>完成上述兩步后,我們就可以方便的使用window.onError進(jìn)行全局異常捕獲,并獲取豐富的異常信息了。但是有時(shí)對(duì)于第三方的CDN,我們無(wú)法添加跨域相關(guān)的頭信息,下面我們就討論針這種情況的全局Javascript異常捕獲方法。2使用AST為所有函數(shù)加上trycatch上文中提到了使用window.onError進(jìn)行瀏覽器全局異常捕獲,但是當(dāng)我們無(wú)法添加跨域相關(guān)頭信息時(shí),window.onError就失效了。針對(duì)這種情況,我們可以對(duì)每一個(gè)函數(shù)添加trycatch來(lái)捕獲函數(shù)內(nèi)的異常,但是一個(gè)大型項(xiàng)目的函數(shù)太多,對(duì)每一個(gè)函數(shù)都手動(dòng)添加trycatch無(wú)疑是一個(gè)巨大的工作量。本文我們借助AST(抽象語(yǔ)法樹(shù))技術(shù),對(duì)源文件進(jìn)行預(yù)處理,對(duì)每個(gè)函數(shù)自動(dòng)的添加trycatch。語(yǔ)法樹(shù)是對(duì)源代碼最精確的表示,通過(guò)遍歷和操作語(yǔ)法樹(shù),我們能夠精確的控制源代碼。生成JavaScript的AST是一件非常復(fù)雜的工作,本文暫時(shí)不打算涉及,好在UglifyJS已經(jīng)有了完整的實(shí)現(xiàn)。比如如下代碼:functiontest(){vara=1;varb=2;console.log(a+b);}可以用語(yǔ)法樹(shù)表示:通過(guò)使用Uglify提供的操作AST(抽象語(yǔ)法樹(shù))的API,我們可以對(duì)每個(gè)函數(shù)添加trycatch代碼塊,并在catch中捕獲該函數(shù)的一切異常,下面是我的實(shí)現(xiàn)(請(qǐng)參考我的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=[];//將錯(cuò)誤處理函數(shù)包裹進(jìn)入catch中tryCatchAST.body[0].bcatch.body[0]=errorHandlerAST;//搜集所有函數(shù)varwalker=newUglifyJS.TreeWalker(function(node){if(isASTFunctionNode(node)){topFuncScope.push(node);}});inputAST.walk(walker);//對(duì)函數(shù)進(jìn)行變換,添加trycatch語(yǔ)句vartransfer=newUglifyJS.TreeTransformer(null,function(node){if(isASTFunctionNode(node)&&_.includes(topFuncScope,node)){//函數(shù)內(nèi)部代碼搜集varstream=UglifyJS.OutputStream();for(vari=0;i<node.body.length;i++){node.body[i].print(stream)}varinnerFuncCode=stream.toString();//清除trycatch中定義的多余語(yǔ)句tryCatchAST.body[0].body.splice(0,tryCatchAST.body[0].body.length);//用trycatch包裹函數(shù)代碼varinnerTyrCatchNode=UglifyJS.parse(innerFuncCode,{toplevel:tryCatchAST.body[0]});//獲取函數(shù)殼node.body.splice(0,node.body.length);//生成有trycatch的函數(shù)returnUglifyJS.parse(innerTyrCatchNode.print_to_string(),{toplevel:node});}});inputAST.transform(transfer);varoutputCode=inputAST.print_to_string({beautify:true});returnoutputCode;}module.exports.globalFuncTryCatch=globalFuncTryCatch;借助于globalFuncTryCatch,我們對(duì)每個(gè)函數(shù)進(jìn)行自動(dòng)化地添加trycatch語(yǔ)句,并使用自定義的錯(cuò)誤處理函數(shù):globalFuncTryCatch(inputCode,function(error){//此處是異常處理代碼,可以上報(bào)并記錄日志console.log(error);});通過(guò)將globalFuncTryCatch功能集成到構(gòu)建工具中,我們就可以對(duì)目標(biāo)Javascript文件進(jìn)行trycatch處理。綜上所述:當(dāng)靜態(tài)資源服務(wù)器可以添加Access-Control-Allow-Origin:*時(shí),我們可以直接使用window.onError進(jìn)行全局異常捕獲;當(dāng)靜態(tài)資源服務(wù)器不受控制,window.onError失效,我們可以借助AST技術(shù),自動(dòng)化地對(duì)全部目標(biāo)Javascript函數(shù)添加trycatch來(lái)捕獲所有異常。參考文檔: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. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 連鎖藥店合作協(xié)議
- 分包制作合同范本
- 個(gè)人出售軟件合同范本
- 辦公樓電梯廣告合同范本
- 公司與司機(jī)合同范本
- 加固鋼板包工合同范本
- 廠家委托施工合同范本
- 個(gè)人資金轉(zhuǎn)讓合同范本
- 南京裝修合同范本
- 出租商場(chǎng)攤位合同范本
- 2025年包頭輕工職業(yè)技術(shù)學(xué)院?jiǎn)握新殬I(yè)適應(yīng)性測(cè)試題庫(kù)及答案解析
- 2025中國(guó)中材海外科技發(fā)展有限公司校園招聘筆試參考題庫(kù)附帶答案詳解
- 2025-2030年即食麥片球行業(yè)深度調(diào)研及發(fā)展戰(zhàn)略咨詢報(bào)告
- - 《中國(guó)課件》揭示西安古都的千年歷史與文化
- 公司積分制管理實(shí)施方案
- 《Maya三維模型制作項(xiàng)目式教程(微課版)》全套教學(xué)課件
- 《電梯安全教育培訓(xùn)》課件
- 2024年山東司法警官職業(yè)學(xué)院高職單招語(yǔ)文歷年參考題庫(kù)含答案解析
- 《業(yè)財(cái)一體化實(shí)訓(xùn)教程-金蝶云星空V7.5》
- 《性病防治知識(shí)講座》課件
- 工業(yè)機(jī)器人工作站系統(tǒng)組建課件 5.1康耐視is2000工業(yè)相機(jī)視覺(jué)識(shí)別操作
評(píng)論
0/150
提交評(píng)論