怎样定位前端线上问题,一直以来,都是很头疼的问题,由于它发生于用户的一系列操做以后。错误的缘由可能源于机型,网络环境,接口请求,复杂的操做行为等等,在咱们想要去解决的时候很难复现出来,天然也就没法解决。 固然,这些问题并不是不能克服,让咱们来一块儿看看如何去监控并定位线上的问题吧。 html
背景:市面上的前端监控系统有不少,功能齐全,种类繁多,无论你用或是不用,它都在那里,密密麻麻。每每我须要的功能都在别人家的监控系统里,手动无奈,罢了,怎么才能拥有一个私人定制的前端监控系统呢?作一个自带前端监控系统的前端工程狮是一种怎样的体验呢?前端
这是搭建前端监控系统的第二章,主要是讲如何对js错误进行监控,跟着我一步步作,你也能搭建出一个属于本身的前端监控系统。java
若是感受有帮助,或者有兴趣,请关注 or Star Me 。 git
请移步线上: 前端监控系统
github
先看一下首页的结果:web
若是天天都去盯着前端的报错数据,真的很耗费精力,并且很难看出是今天发生的,仍是一直存在的报错。ajax
因此呢,其实前端项目天天都会有些报错,好比:script error 。咱们既不能控制,也不会影响咱们的业务,只能让它一直存在。因此咱们的前端应用,天天都会有必定数量的报错数据。只要日活量不会波动太大,那么报错数据就会比较平稳,因此我选择跟7天前的报错数据进行比较,若是出现大幅上升,那么就须要我对这个项目进行关注了,而不是天天查看具体的报错数据。segmentfault
对于前端应用来讲,Js错误的发生直接影响前端应用的质量。对前端异常的监控是整个前端监控系统中的一个重要环节。前端异常包含不少种状况:1. js编译时异常(开发阶段就能排)2. js运行时异常;3. 加载静态资源异常(路径写错、资源服务器异常、CDN异常、跨域)4. 接口请求异常等。这一篇咱们只介绍Js运行时异常。跨域
监控流程:监控错误 -> 搜集错误 -> 存储错误 -> 分析错误 -> 错误报警-> 定位错误 -> 解决错误浏览器
首先,咱们应该对Js报错状况有个大体的了解,这样才可以及时的了解前端项目的健康情况。因此咱们须要分析出一些必要的数据。
如:一段时间内,应用JS报错的走势(chart图表)、JS错误发生率、JS错误在PC端发生的几率、JS错误在IOS端发生的几率、JS错误在Android端发生的几率,以及JS错误的归类。
而后,咱们再去其中的Js错误进行详细的分析,辅助咱们排查出错的位置和发生错误的缘由。
如:JS错误类型、 JS错误信息、JS错误堆栈、JS错误发生的位置以及相关位置的代码;JS错误发生的概率、浏览器的类型,版本号,设备机型等等辅助信息
为了获得这些数据,咱们须要在上传的时候将其分析出来。在众多日志分析中,不少字段及功能是重复通用的,因此应该将其封装起来。
// 设置日志对象类的通用属性 function setCommonProperty() { this.happenTime = new Date().getTime(); // 日志发生时间 this.webMonitorId = WEB_MONITOR_ID; // 用于区分应用的惟一标识(一个项目对应一个) this.simpleUrl = window.location.href.split('?')[0].replace('#', ''); // 页面的url this.customerKey = utils.getCustomerKey(); // 用于区分用户,所对应惟一的标识,清理本地数据后失效 this.pageKey = utils.getPageKey(); // 用于区分页面,所对应惟一的标识,每一个新页面对应一个值 this.deviceName = DEVICE_INFO.deviceName; this.os = DEVICE_INFO.os + (DEVICE_INFO.osVersion ? " " + DEVICE_INFO.osVersion : ""); this.browserName = DEVICE_INFO.browserName; this.browserVersion = DEVICE_INFO.browserVersion; // TODO 位置信息, 待处理 this.monitorIp = ""; // 用户的IP地址 this.country = "china"; // 用户所在国家 this.province = ""; // 用户所在省份 this.city = ""; // 用户所在城市 // 用户自定义信息, 由开发者主动传入, 便于对线上进行准肯定位 this.userId = USER_INFO.userId; this.firstUserParam = USER_INFO.firstUserParam; this.secondUserParam = USER_INFO.secondUserParam; } // JS错误日志,继承于日志基类MonitorBaseInfo function JavaScriptErrorInfo(uploadType, errorMsg, errorStack) { setCommonProperty.apply(this); this.uploadType = uploadType; this.errorMessage = encodeURIComponent(errorMsg); this.errorStack = errorStack; this.browserInfo = BROWSER_INFO; } JavaScriptErrorInfo.prototype = new MonitorBaseInfo();
封装了一个Js错误对象JavaScriptErrorInfo,用以保存页面中产生的Js错误。其中,setCommonProperty用以设置全部日志对象的通用属性。
1)重写window.onerror 方法, 你们熟知,监控JS错误必然离不开它,有人对他进行了测试测试介绍感受也是比较用心了
2)重写console.error方法,为何要重写这个方法,我不可以给出明确的答案,若是App首次向浏览器注入的Js代码报错了,window.onerror是没法监控到的,因此只能重写console.error的方式来进行捕获,也许会有更好的办法。待window.onerror成功后,此方法便再也不须要用了
3)重写window.onunhandledrejection方法。 当你用到Promise的时候,而你又忘记写reject的捕获方法的时候,系统老是会抛出一个叫 Unhandled Promise rejection. 没有堆栈,没有其余信息,特别是在写fetch请求的时候很容易发生。 因此咱们须要重写这个方法,以帮助咱们监控此类错误
下边是启动JS错误监控代码
/** * 页面JS错误监控 */ function recordJavaScriptError() { // 重写console.error, 能够捕获更全面的报错信息 var oldError = console.error; console.error = function () { // arguments的长度为2时,才是error上报的时机 // if (arguments.length < 2) return; var errorMsg = arguments[0] && arguments[0].message; var url = WEB_LOCATION; var lineNumber = 0; var columnNumber = 0; var errorObj = arguments[0] && arguments[0].stack; if (!errorObj) errorObj = arguments[0]; // 若是onerror重写成功,就无需在这里进行上报了 !jsMonitorStarted && siftAndMakeUpMessage(errorMsg, url, lineNumber, columnNumber, errorObj); return oldError.apply(console, arguments); }; // 重写 onerror 进行jsError的监听 window.onerror = function(errorMsg, url, lineNumber, columnNumber, errorObj) { jsMonitorStarted = true; var errorStack = errorObj ? errorObj.stack : null; siftAndMakeUpMessage(errorMsg, url, lineNumber, columnNumber, errorStack); }; function siftAndMakeUpMessage(origin_errorMsg, origin_url, origin_lineNumber, origin_columnNumber, origin_errorObj) { var errorMsg = origin_errorMsg ? origin_errorMsg : ''; var errorObj = origin_errorObj ? origin_errorObj : ''; var errorType = ""; if (errorMsg) { var errorStackStr = JSON.stringify(errorObj) errorType = errorStackStr.split(": ")[0].replace('"', ""); } var javaScriptErrorInfo = new JavaScriptErrorInfo(JS_ERROR, errorType + ": " + errorMsg, errorObj); javaScriptErrorInfo.handleLogInfo(JS_ERROR, javaScriptErrorInfo); }; };
OK, 错误日志有了,该怎么计算错误率呢?
JS错误发生率 = JS错误个数(一次访问页面中,全部的js错误都算一次)/PV (PC,IOS,Android平台同理)
因此咱们须要记下页面的PV记录
/** * 添加一个定时器,进行数据的上传 * 2秒钟进行一次URL是否变化的检测 * 10秒钟进行一次数据的检查并上传 */ var timeCount = 0; setInterval(function () { checkUrlChange(); // 循环5后次进行一次上传 if (timeCount >= 25) { // 若是是本地的localhost, 就忽略,不进行上传 var logInfo = (localStorage[ELE_BEHAVIOR] || "") + (localStorage[JS_ERROR] || "") + (localStorage[HTTP_LOG] || "") + (localStorage[SCREEN_SHOT] || "") + (localStorage[CUSTOMER_PV] || "") + (localStorage[LOAD_PAGE] || "") + (localStorage[RESOURCE_LOAD] || ""); if (logInfo) { localStorage[ELE_BEHAVIOR] = ""; localStorage[JS_ERROR] = ""; localStorage[HTTP_LOG] = ""; localStorage[SCREEN_SHOT] = ""; localStorage[CUSTOMER_PV] = ""; localStorage[LOAD_PAGE] = ""; localStorage[RESOURCE_LOAD] = ""; utils.ajax("POST", HTTP_UPLOAD_LOG_INFO, {logInfo: logInfo}, function (res) {}, function () {}) } timeCount = 0; } timeCount ++; }, 200);
上边的代码我用了定时器,大概的意思是200毫秒进行一次URL变化的检查,5秒进行一次数据的检查,若是有数据就进行上传,并清空上一次的数据。为何用定时器呢,由于在单页应用中,路由的切换和地址栏的变化是没法被监控的,我确实没有想到特别好的办法来监控,因此用了这种方式。
封装简易的Ajax
为了将这些数据上传到咱们的服务器,咱们总不能每次都用xmlHttpRequest来发送ajax请求吧,因此咱们须要本身封装一个简单的Ajax
/**
*
* @param method 请求类型(大写) GET/POST
* @param url 请求URL
* @param param 请求参数
* @param successCallback 成功回调方法
* @param failCallback 失败回调方法
*/
this.ajax = function(method, url, param, successCallback, failCallback) {
var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); xmlHttp.open(method, url, true); xmlHttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { var res = JSON.parse(xmlHttp.responseText); typeof successCallback == 'function' && successCallback(res); } else { typeof failCallback == 'function' && failCallback(); } }; xmlHttp.send("data=" + JSON.stringify(param)); }
统计JS Error的目的,1、是为了了解线上项目的健康情况,2、是为了分析错误,帮助咱们查找问题之所在,而且解决它。
因此,如何定位线上的问题,并解决问题,是咱们如今要讨论的重点。下面咱们须要对几个关键点进行分析:
① 某种错误发生的次数——发生次数跟影响用户是成正比的, 若是发生次数跟影响用户数量都很高,那么这是一个比较严重的bug, 须要当即解决。 反之, 若是次数不少,影响用户数量不多。说明这种错误只发生在少许设备中,优先级相对较低,能够择时对该类机型设备进行兼容处理。固然,ip地址访问次数也能说明这个问题
② 页面发生了哪些错误——这个有利于咱们缩小问题的范围,方便咱们排查,如:
③ 错误堆栈——这点不用说,是定位错误最重要的因素。正常状况下,代码都是被压缩的,因此我在后台解析并截取出错代码附近的一部分代码,进行展现,排查错误。PS: 我看到网上有人利用jsMap反向找到代码的具体位置,想法很不错,后期我会加上。 另外,代码虽然被压缩,可是依然很轻松定位到出错的位置,以下图所示, 因此这个功能暂时做为附加题,不用那么着急加上。
④ 设备信息——当错误发生是,分析出用户当时使用设备的浏览器信息,系统版本,设备机型等等,可以帮咱们快速的定位到须要兼容的设备,进而提高解决问题的效率。
⑤ 用户足迹——我我的以为比较有用,可是代价过高。 由于这个须要记录下用户在页面上的全部行为,须要上传很是多的数据,功能待定。
这个功能已经在后边进行完善了,点击 查看足迹 按钮便可查出这个用的行为足迹,在定位线上问题方面,有很大的做用 , 我在后边的篇幅中有介绍 搭建前端监控系统(五)怎样定位线上问题
到此,已经收集到了JS错误日志的大部分信息了,而且已经分析出JS错误的详细信息了。
既然咱们已经具备了搜集js报错和分析报错的能力了,那么咱们也能够作到Js报错实时监控,以及实时预警了,这样能够防范线上事故于未然,及时的制止线上事故的持续发生, 减小损失。
如上图所示,我展现了从当前时间向前推算24小时,每小时报错数量。另外展现了7天前同一时间段的报错数量,若是你的项目健康稳定,那么在相同时间段的报错数量应该不会相差太大。若是出现相差太大的状况发生,说明线上出现了问题,此刻应该发出警告,避免线上事故的发生。demo上暂未加上警告功能,可是原理清楚了,后边天然水到渠成。
上一章: 搭建前端监控系统(一)阿里云服务器搭建篇
下一章: 搭建前端监控系统(三)静态资源加载监控