如果你是一位前端工程師,那你一定不止一次去解決一些頑固的線上問題,你也曾想方設法復現用戶的bug,結果可能都不太理想。 怎樣定位前端線上問題,一直以來,都是很頭疼的問題,因為它發生於用戶的一系列操作之后。錯誤的原因可能源於機型,網絡環境,復雜的操作行為等等,在我們想要去解決的時候很難復現出來,自然也就無法解決。
身為一名前端工程師,我每天都要面臨很多線上的問題,一時間讓我焦頭爛額。公司其他的監控系統也有,但是每次解決問題都需要輾轉於各種監控系統之間,亦是疲憊不堪。所以,我便為自己(前端工程師)量身定做了這樣一款監控系統,現在分享給大家使用,歡迎點擊了解。
前端監控: www.webfunny.cn,只需要簡單幾步,就可以搭建一套屬於自己的前端監控系統,快來試試吧。(github:webfunny_monitor)
先看一下首頁的結果:
如果每天都去盯着前端的報錯數據,真的很耗費精力,而且很難看出是今天發生的,還是一直存在的報錯。
所以呢,其實前端項目每天都會有些報錯,比如:script error 。我們既不能控制,也不會影響我們的業務,只能讓它一直存在。所以我們的前端應用,每天都會有一定數量的報錯數據。只要日活量不會波動太大,那么報錯數據就會比較平穩,所以我選擇跟7天前的報錯數據進行比較,如果出現大幅上升,那么就需要我對這個項目進行關注了,而不是每天查看具體的報錯數據。
對於前端應用來說,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錯誤發生的幾率、瀏覽器的類型,版本號,設備機型等等輔助信息
一、JS Error 監控功能 (數據概覽)
為了得到這些數據,我們需要在上傳的時候將其分析出來。在眾多日志分析中,很多字段及功能是重復通用的,所以應該將其封裝起來。
// 設置日志對象類的通用屬性
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 詳細信息解析
統計JS Error的目的,一、是為了了解線上項目的健康狀況,二、是為了分析錯誤,幫助我們查找問題之所在,並且解決它。
所以,如何定位線上的問題,並解決問題,是我們現在要討論的重點。下面我們需要對幾個關鍵點進行分析:
① 某種錯誤發生的次數——發生次數跟影響用戶是成正比的, 如果發生次數跟影響用戶數量都很高,那么這是一個比較嚴重的bug, 需要立即解決。 反之, 如果次數很多,影響用戶數量很少。說明這種錯誤只發生在少量設備中,優先級相對較低,可以擇時對該類機型設備進行兼容處理。當然,ip地址訪問次數也能說明這個問題
② 頁面發生了哪些錯誤——這個有利於我們縮小問題的范圍,方便我們排查,如:
③ 錯誤堆棧——這點不用說,是定位錯誤最重要的因素。正常情況下,代碼都是被壓縮的,所以我在后台解析並截取出錯代碼附近的一部分代碼,進行展示,排查錯誤。PS: 我看到網上有人利用jsMap反向找到代碼的具體位置,想法很不錯,后期我會加上。 另外,代碼雖然被壓縮,但是依然很輕松定位到出錯的位置,如下圖所示, 所以這個功能暫時作為附加題,不用那么着急加上。
④ 設備信息——當錯誤發生是,分析出用戶當時使用設備的瀏覽器信息,系統版本,設備機型等等,能夠幫我們快速的定位到需要兼容的設備,進而提升解決問題的效率。
⑤ 用戶足跡——我個人覺得比較有用,但是代價太高。 因為這個需要記錄下用戶在頁面上的所有行為,需要上傳非常多的數據,功能待定。
這個功能已經在后邊進行完善了,點擊 查看足跡 按鈕即可查出這個用的行為足跡,在定位線上問題方面,有很大的作用 , 我在后邊的篇幅中有介紹 搭建前端監控系統(五)怎樣定位線上問題
到此,已經收集到了JS錯誤日志的大部分信息了,並且已經分析出JS錯誤的詳細信息了。
三、JS報錯的實時監控與報警
既然我們已經具有了搜集js報錯和分析報錯的能力了,那么我們也可以做到Js報錯實時監控,以及實時預警了,這樣可以防范線上事故於未然,及時的制止線上事故的持續發生, 減少損失。
如上圖所示,我展示了從當前時間向前推算24小時,每小時報錯數量。另外展示了7天前同一時間段的報錯數量,如果你的項目健康穩定,那么在相同時間段的報錯數量應該不會相差太大。如果出現相差太大的情況發生,說明線上出現了問題,此刻應該發出警告,避免線上事故的發生。demo上暫未加上警告功能,但是原理清楚了,后邊自然水到渠成。
上一章: 搭建前端監控系統(一)阿里雲服務器搭建篇
下一章: 搭建前端監控系統(三)靜態資源加載監控