怎样定位前端的问题,一直以来,都是很头疼的问题,因为它发生于用户的一系列操作之后。错误的原因可能源于机型,网络环境,接口请求,复杂的操作行为等等,在我们想要去解决的时候很难复现出来,自然也就无法解决。 当然,这些问题并非不能克服,让我们来一起看看如何去监控并定位线上的问题吧。
我们把前端的问题分为两大类,JS代码错误、和前端异常表现。代码错误很容易理解,只要你的代码报错了,就会有错误日志产生,你就可以根据错误日志定位到问题所在。但是前端异常就比较麻烦一些,导致异常的原因可能是外界因素,我前面提到的,机型,网络环境,接口请求,复杂的操作行为等。
JS错误监控
图一:js错误错误的监控和分类
首先,我们应该监控JS错误每日的数量。因为错误每天都会发生,无法做到100%的避免,正常情况下报错量会稳定在一个范围里,所以我们只需要从趋势中看出比较突出的一天,集中精力去解决即可。
然后,我们根据错误类型对所有的JS错误进行分类聚合,这样页面上发生的错误就可以一目了然。根据监控的结果我们可以看到,JS代码错误主要包含以下几个类型,如下:
TypeError(类型错误)、
SyntaxError(语法错误)、
ReferenceError(引用错误)、
RangeError(范围错误)、
Error、
Script error(第三方文件报错)、
...
此类错误的的前几个都属于运行时,并且阻断型的错误,它们会阻断程序的运行,是需要优先解决的。后边几个属于非阻断型的错误,或者是属于第三方文件,你无法处理的错误类型,可以考虑优化处理。此类错误的监控方法是:重写window.onerror 方法, 研究过JS错误的人对此方法应该都有所了解,这个是最经常用到的JS错误监控方法。
window.onerror = function(errorMsg, url, lineNumber, columnNumber, errorObj) { //此处书写错误监控的逻辑 }
再者,还有一种报错方式,是通过console.error打印出来。这种错误不会阻断程序运行,通常是由第三方插件通过警告的方式,打印出来提醒你的。在我的监控系统中,称之为「自定义错误」。在我们的程序中也可以用这种方式打印出警告信息,比如,接口返回异常信息、接口请求超时、网络请求失败等等,都可以用来帮我们判断前端应用是否监控
图二:自定义错误分类
此类错误从严格意义上来讲,不能够算是报错,只是一种比较严重的警告方式。此类错误的监控方法是:通过重写console.error方法获取,这个方法,可能大多数小伙伴都会忽略。很多第三方的插件和库都是用这种方式把异常打印出来,所以我们需要重写console.error的方式来进行捕获,这样,第三方打印出来的异常也不会被遗漏了。
console.error = function (tempErrorMsg) { var errorMsg = (arguments[0] && arguments[0].message) || tempErrorMsg; var lineNumber = 0; var columnNumber = 0; var errorObj = arguments[0] && arguments[0].stack; if (!errorObj) { if (typeof errorMsg == "object") { try { errorMsg = JSON.stringify(errorMsg) } catch(e) { errorMsg = "错误无法解析" } } siftAndMakeUpMessage("console_error", errorMsg, WEB_LOCATION, lineNumber, columnNumber, "CustomizeError: " + errorMsg); } else { // 如果报错中包含错误堆栈,可以认为是JS报错,而非自定义报错 siftAndMakeUpMessage("on_error", errorMsg, WEB_LOCATION, lineNumber, columnNumber, errorObj); } return oldError.apply(console, arguments); };
最后,有一类错误叫:UncaughtinPromiseError;此错误也是非阻断型的,当你用到Promise的时候,而你又忘记写reject的捕获方法的时候,系统总是会抛出一个叫 Unhandled Promise rejection. 没有堆栈,没有其他信息,特别是在写fetch请求的时候很容易发生。此类错误的监控方法:通过重写window.onunhandledrejection方法获取。
window.onunhandledrejection = function(e) { var errorMsg = ""; var errorStack = ""; if (typeof e.reason === "object") { errorMsg = e.reason.message; errorStack = e.reason.stack; } else { errorMsg = e.reason; errorStack = ""; } //处理上报 siftAndMakeUpMessage("on_error", errorMsg, WEB_LOCATION, 0, 0, "UncaughtInPromiseError: " + errorStack); }
JS错误分析
通过上文中介绍的三种方式,基本上可以将前端的大部分JS报错进行监控。下边来谈谈如何分析并解决这些报错问题。
一、判断错误的影响范围,发生的设备有哪些、影响人数是多少、最近发生的时间等。
图三(影响范围):
二、对具体的错误细节进行分析,从而定位到问题的真正原因,系统,机型,设备,代码位置。
图四(错误的具体细节):
三、针对单个错误,可以查看它的报错趋势,精确到每分钟
图五(单个错误报错趋势):
四、针对单个错误,可以通过查看它的行为轨迹,来确定js错误发生于用户操作的哪一步之后
图六(查看行为轨迹):
具体错误具体分析
1. Script error
“Script error.”是一个常见错误,但由于该错误不提供完整的报错信息(错误堆栈),问题排查往往无从下手,大部分都是发生在跨域情况下。参考解决方案
2. TypeError(类型错误)、SyntaxError(语法错误)、ReferenceError(引用错误)、RangeError(范围错误)
这一类的错误必然都是有堆栈信息的,可以根据代码解析定位到源码的位置。
3. UncaughtinPromiseError(Unhandled Promise rejection 未处理的promise rejection)
这一类错误同样的可以根据源码定位,找到对应的promise, 然后去处理reject逻辑。