捕获页面中全局Javascript异常.docx

上传人:b****8 文档编号:24051909 上传时间:2023-05-23 格式:DOCX 页数:15 大小:22.27KB
下载 相关 举报
捕获页面中全局Javascript异常.docx_第1页
第1页 / 共15页
捕获页面中全局Javascript异常.docx_第2页
第2页 / 共15页
捕获页面中全局Javascript异常.docx_第3页
第3页 / 共15页
捕获页面中全局Javascript异常.docx_第4页
第4页 / 共15页
捕获页面中全局Javascript异常.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

捕获页面中全局Javascript异常.docx

《捕获页面中全局Javascript异常.docx》由会员分享,可在线阅读,更多相关《捕获页面中全局Javascript异常.docx(15页珍藏版)》请在冰豆网上搜索。

捕获页面中全局Javascript异常.docx

捕获页面中全局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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 法律资料

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1