1、捕获页面中全局Javascript异常捕获页面中全局Javascript异常主题 UglifyJS一个流量巨大的前端页面面临的浏览器环境是非常复杂的,尤其是移动端页面(Android的碎片化所致)。面对如此多样的浏览器环境,常规的测试是无法完全覆盖的,我们需要一种页面脚本异常监控机制作为补充,保证能够发现前端页面脚本异常的原因。有很多种情况会导致Javascript抛出异常,包括网络失效、语法错误、运行时错误等。 我们希望在页面上有异常发生时,能够获得脚本错误的基本信息、文件url、行号 。接下来我们探讨几种实现方式。1 使用window.onError浏览器提供了全局的onError函数,我
2、们可以使用它搜集页面上的错误window.onerror = function(message, source, lineno, colno, error) . 其中mesage为异常基本信息,source为发生异常Javascript文件url,lineno为发生错误的行号,我们可以通过error.stack获取异常的堆栈信息。下面是chrome中通过window.onError捕获的错误例子:message: Uncaught ReferenceError: test is not definedsource: lineno: 16144colno: 6error: ReferenceEr
3、ror: test is not defined at at HTMLDocument. (这种方式看似完美,其实有一个致命的问题。有些浏览器为了安全方面的考虑,对于不同域的Javascript文件,通过window.onError无法获取有效的错误信息。比如firefox的错误消息只有 Script error ,而且无法获得确切的行号,更没有错误堆栈信息:message: Script error.source: lineno: 0colno: 0error: null为了使得浏览器针对window.onError的跨域保护失效, 我们可以在静态资源服务器或者CDN的HTTP头中加上如下允
4、许跨域提示:Access-Control-Allow-Origin: *并在引用Javascript脚本是加上crossorigin属性:完成上述两步后,我们就可以方便的使用window.onError进行全局异常捕获,并获取丰富的异常信息了。但是有时对于第三方的CDN,我们无法添加跨域相关的头信息,下面我们就讨论针这种情况的全局Javascript异常捕获方法。2 使用AST为所有函数加上try catch上文中提到了使用window.onError进行浏览器全局异常捕获,但是当我们无法添加跨域相关头信息时,window.onError就失效了。针对这种情况,我们可以对每一个函数添加try
5、catch来捕获函数内的异常,但是一个大型项目的函数太多,对每一个函数都手动添加try catch无疑是一个巨大的工作量。本文我们借助AST(抽象语法树)技术,对源文件进行预处理,对每个函数自动的添加try catch。语法树是对源代码最精确的表示,通过遍历和操作语法树,我们能够精确的控制源代码。生成JavaScript的AST是一件非常复杂的工作,本文暂时不打算涉及,好在UglifyJS已经有了完整的实现。比如如下代码:function test() var a = 1; var b = 2; console.log(a+b);可以用语法树表示:通过使用Uglify提供的操作AST(抽象语法
6、树)的API,我们可以对每个函数添加try catch代码块,并在catch中捕获该函数的一切异常,下面是我的实现(请参考我的github: try-catch-global.js ):var fs = require(fs);var _ = require(lodash);var UglifyJS = require(uglify-js);var isASTFunctionNode = function (node) return node instanceof UglifyJS.AST_Defun | node instanceof UglifyJS.AST_Function;var gl
7、obalFuncTryCatch = function (inputCode, errorHandler) if(!_.isFunction(errorHandler) throw errorHandler should be a valid function; var errorHandlerSource = errorHandler.toString(); var errorHandlerAST = UglifyJS.parse( + errorHandlerSource + )(error);); var tryCatchAST = UglifyJS.parse(trycatch(err
8、or); var inputAST = UglifyJS.parse(inputCode); var topFuncScope = ; /将错误处理函数包裹进入catch中 tryCatchAST.body0.bcatch.body0 = errorHandlerAST; /搜集所有函数 var walker = new UglifyJS.TreeWalker(function (node) if (isASTFunctionNode(node) topFuncScope.push(node); ); inputAST.walk(walker); /对函数进行变换, 添加try catch语句
9、 var transfer = new UglifyJS.TreeTransformer(null, function (node) if (isASTFunctionNode(node) & _.includes(topFuncScope, node) /函数内部代码搜集 var stream = UglifyJS.OutputStream(); for (var i = 0; i node.body.length; i+) node.bodyi.print(stream) var innerFuncCode = stream.toString(); /清除try catch中定义的多余语句
10、 tryCatchAST.body0.body.splice(0, tryCatchAST.body0.body.length); /用try catch包裹函数代码 var innerTyrCatchNode = UglifyJS.parse(innerFuncCode, toplevel: tryCatchAST.body0); /获取函数壳 node.body.splice(0, node.body.length); /生成有try catch的函数 return UglifyJS.parse(innerTyrCatchNode.print_to_string(), toplevel:
11、node); ); inputAST.transform(transfer); var outputCode = inputAST.print_to_string(beautify: true); return outputCode;module.exports.globalFuncTryCatch = globalFuncTryCatch;借助于 globalFuncTryCatch ,我们对每个函数进行自动化地添加try catch语句,并使用自定义的错误处理函数:globalFuncTryCatch(inputCode, function (error) /此处是异常处理代码,可以上报并
12、记录日志 console.log(error););通过将globalFuncTryCatch功能集成到构建工具中,我们就可以对目标Javascript文件进行try catch处理。综上所述:当静态资源服务器可以添加 Access-Control-Allow-Origin: * 时,我们可以直接使用window.onError进行全局异常捕获;当静态资源服务器不受控制,window.onError失效,我们可以借助AST技术,自动化地对全部目标Javascript函数添加try catch来捕获所有异常。参考文档:Capture and report JavaScript errors wi
13、th window.onerroronerror is a special browser event that fires whenever an uncaught JavaScript error has been thrown. Its one of the easiest ways to log client-side errors and report them to your servers. Its also one of the major mechanisms by which Sentrys client JavaScript integration (raven-js)
14、works.You listen to the onerror event by assigning a function to window.onerror:window.onerror = function (msg, url, lineNo, columnNo, error) / . handle error . return false;When an error is thrown, the following arguments are passed to the function:msg The message associated with the error, e.g. “U
15、ncaught ReferenceError: foo is not defined”url The URL of the script or document associated with the error, e.g. “/dist/app.js”lineNo The line number (if available)columnNo The column number (if available)error The Error object associated with this error (if available)The first four arguments tell y
16、ou in which script, line, and column the error occurred. The final argument, Error object, is perhaps the most valuable. Lets learn why.The Error object and error.stackAt first glance the Error object isnt very special. It contains 3 standardized properties: message, fileName, and lineNumber. Redund
17、ant values that already provided to you via window.onerror.The valuable part is a non-standard property: Error.prototype.stack. This stack property tells you at what source location each frame of the program was when the error occurred. The stack trace can be a critical part of debugging an error. A
18、nd despite being non-standard, this property is available in every modern browser.Heres an example of the Error objects stack property in Chrome 46:Error: foobarn at new bar (:241:11)n at foo (:245:5)n at :250:5n at :251:3n at :267:4n at callFunction (:229:33)n at :239:23n at :240:3n at Object.Injec
19、tedScript._evaluateOn (:875:140)n at Object.InjectedScript._evaluateAndWrap (:808:34)Hard to read, right? The stack property is actually just an unformatted string.Heres what it looks like formatted:Error: foobar at new bar (:241:11) at foo (:245:5) at callFunction (:229:33) at Object.InjectedScript
20、._evaluateOn (:875:140) at Object.InjectedScript._evaluateAndWrap (:808:34)Once its been formatted, its easy to see how the stack property can be critical in helping to debug an error.Theres just one snag: the stack property is non-standard, and its implementation differs among browsers. For example
21、, heres the same stack trace from Internet Explorer 11:Error: foobar at bar (Unknown script code:2:5) at foo (Unknown script code:6:5) at Anonymous function (Unknown script code:11:5) at Anonymous function (Unknown script code:10:2) at Anonymous function (Unknown script code:1:73)Not only is the for
22、mat of each frame different, the frames also have less detail. For example, Chrome identifies that the new keyword has been used, and has greater insight into eval invocations. And this is just IE 11 vs Chrome other browsers similar have varying formats and detail.Luckily, there are tools out there
23、that normalize stack properties so that it is consistent across browsers. For example, raven-js uses TraceKit to normalize error strings. Theres also stacktrace.js and a few other projects.Browser compatibilitywindow.onerror has been available in browsers for some time youll find it in browsers as o
24、ld as IE6 and Firefox 2.The problem is that every browser implements window.onerror differently. Particularly, in how many arguments are sent to to the onerror listener, and the structure of those arguments.Heres a table of which arguments are passed to onerror in most browsers:Browser Message URL l
25、ineNo colNo errorObjFirefox 42 Chrome 46 Android Browser 4.4 Edge IE 11 IE 10 IE 9, 8 Safari 9 iOS 9 Youll notice that the latest Apple browsers Safari and iOS dont support a 5th error object argument. And while the final version of Internet Explorer (11) supports the error object, Microsofts latest
26、 browser, Edge, does not.Without the error object, there is no stack trace property. This means that these browsers cannot retrieve valuable stack information from errors caught by onerror.Polyfilling window.onerror with try/catchBut there is a workaround you can wrap code in your application inside
27、 a try/catch and catch the error yourself. This error object will contain our coveted stack property in every modern browser.Consider the following helper method, invoke, which calls a function on an object with an array of arguments:function invoke(obj, method, args) return objmethod.apply(this, ar
28、gs);invoke(Math, max, 1, 2); / returns 2Heres invoke again, this time wrapped in try/catch, in order to capture any thrown error:function invoke(obj, method, args) try return objmethod.apply(this, args); catch (e) captureError(e); / report the error throw e; / re-throw the error invoke(Math, highest
29、, 1, 2); / throws error, no method Math.highestOf course, doing this manually everywhere is pretty cumbersome. You can make it easier by creating a generic wrapper utility function:function wrapErrors(fn) / dont wrap function more than once if (!fn._wrapped_) fn._wrapped_ = function () try return fn
30、.apply(this, arguments); catch (e) captureError(e); / report the error throw e; / re-throw the error ; return fn._wrapped_;var invoke = wrapErrors(function(obj, method, args) return objmethod.apply(this, args););invoke(Math, highest, 1, 2); / no method Math.highestBecause JavaScript is single threaded, you don
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1