摘要: 徒手寫JS錯誤監控。javascript
Fundebug經受權轉載,版權歸原做者全部。html
背景:市面上的監控系統有不少,大多收費,對於小型前端項目來講,必然是痛點。另外一點主要緣由是,功能通用,卻未必可以知足咱們本身的需求, 因此咱們自給自足。前端
這是搭建前端監控系統的第二章,主要是介紹如何統計js報錯,跟着我一步步作,你也能搭建出一個屬於本身的前端監控系統。java
請移步線上:前端監控系統 web
對於前端應用來講,Js錯誤的發生直接影響前端應用的質量。對前端異常的監控是整個前端監控系統中的一個重要環節。前端異常包含不少種狀況:1. js編譯時異常(開發階段就能排)2. js運行時異常;3. 加載靜態資源異常(路徑寫錯、資源服務器異常、CDN異常、跨域)4. 接口請求異常等。這一篇咱們只介紹Js運行時異常。ajax
監控流程:監控錯誤 -> 蒐集錯誤 -> 存儲錯誤 -> 分析錯誤 -> 錯誤報警-> 定位錯誤 -> 解決錯誤編程
首先,咱們應該對Js報錯狀況有個大體的瞭解,這樣纔可以及時的瞭解前端項目的健康情況。因此咱們須要分析出一些必要的數據。小程序
如:一段時間內,應用JS報錯的走勢(chart圖表)、JS錯誤發生率、JS錯誤在PC端發生的機率、JS錯誤在IOS端發生的機率、JS錯誤在Android端發生的機率,以及JS錯誤的歸類。segmentfault
而後,咱們再去其中的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上暫未加上警告功能,可是原理清楚了,後邊天然水到渠成。
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對一、微脈、青團社等衆多品牌企業。歡迎你們免費試用!