為什么要做前端代碼異常采集?好問題!
為了用戶能安心用產品,不至於時不時“卡殼”崩潰。
為了能高效定位線上代碼的異常並提供簡單提示信息。
為了程序猿同胞們能睡個好覺。
本文完整示例請移步github:FEerrorLog
捕獲異常的方法
js捕獲異常的方法,兩三個而已。
- try...catch 優缺點已有很多論述和解決方案,本文的異常采集並未建立在該方法之上,只是少量使用。
- window.onerror和方法3類似但不如方法3強大,因此未選用此方法。
- window.addEventListener('error',function(){},true),采用此方法。
前端異常包含兩部分:
第一部分:window.onerror()能捕獲到的異常,當然如果用addEventListener無論冒泡還是捕獲階段也能捕獲到該異常。
第二部分:資源加載失敗,即<img>、<script>標簽上的onerror,這個異常無法通過冒泡到達window,但是可以在捕獲階段拿到,這就是為什么要將addEventListener第三個參數置成true了。
注意:為保證該異常采集腳本能執行到,不被先行執行的腳本里面的報錯阻斷,該腳本要放到最前面。
可能的異常及采集方案
- 資源加載失敗,樣式、圖片、腳本文件的請求異常,比如js加載404了
- js腳本異常,即控制台常見的Error信息
- 檢測HTML劫持,比如被運營商強行注入標簽或腳本
- 頁面樣式丟失,CSS 展現異常
1. 資源加載失敗
http異常用js幾乎抓不到有用異常信息,但是404異常可以進行簡單處理,此時是不會執行onerror的回調函數的。因為在addEventListener捕獲到的異常信息中你可以發現,對應於onerror的五個回調參數根本不存在了,但是addEventListener中除這五個外,還有其他可以用的key,如果想獲取加載失敗的資源是哪個,可以去target中找些有用信息,我使用的是e.target.outerHTML
"HttpError at " + (e.target.baseURI || location.href) + " outerHTML:" + e.target.outerHTML
2. js異常
一般需要采集的信息:
- 異常的提示信息,會直接告訴你是什么異常。這是識別一個異常的最重要依據,即e.message中的信息。
- JS 文件名:異常發生在那個文件中。是堆棧信息中最頂層的那個文件。即e.filename。
- 異常所在行、列:異常的具體位置。行信息各瀏覽器基本還是一致的,列信息的差別較大,僅供參考。
- 堆棧信息:異常信息發生的堆棧,也是函數調用的堆棧信息,每下一層都是上一層的運行環境。即e.error.stack。每一層都包含類型、文件、行、列信息。但是注意堆棧信息可能會比較多,可以根據需要截取上報。safari和firfox的e.error.stack中不包含以上1,2,3的信息,只有堆棧信息,而chrome和IE中都包含,此處需要做兼容處理。
- 發生異常的設備信息,可以從window.navigator中選取自己需要的信息,或者直接使用window.navigator.userAgent
- 發生異常的時間點,不多說。
js主動拋出異常
js異常除了可以是系統拋出的幾類異常,還可以是開發者利用throw關鍵字主動拋出異常。
值:可以是字符串、數字邏輯值或對象
使用方式:
//利用Error對象或其實例,采集到的異常系統自動為其添加了堆棧信息,和系統拋出異常基本類似
throw new Error('Problem description.')
throw Error('Problem description.')
//直接利用關鍵字拋出內容,會完全復寫event.error的內容,不推薦使用。
throw 'Problem description.'
throw null
另外:
console.error()和throw new Error()拋出的錯誤信息是有本質區別的。前者不會阻斷js運行,也不會被error事件捕捉到,只是在控制台打印錯誤信息。
以下方式可以阻止異常信息在控制台中顯示,線上可以自行收集異常信息后阻止外人看到控制台報錯,開發環境不建議使用。
window.addEventListener('error', (function(e) {
console.log("-----errorEvent----", e)
e.preventDefault() //這里換成 return false或return true均不行!
}), true);
window.onerror = function(msg, url, line, col, error) {
console.log("------errorInfo---",msg, url, line, col, error)
return true; //這里用return false不行!
}
3. 檢測html劫持
我選用的方案是保存真實環境中的html信息,並對比原html,檢測是否有被篡改。
采集html文檔用到的是document.documentElement.outerHTML。但是有一點需要注意,上面已提到,該文件需要放在最前面,所以直接用該方法拿到的可能只有<head>中的html。因此如果想拿到完整的頁面信息,需要將采集時間點放到onload以后。
//所有io操作最好都try...catch一下,這里是防止儲存的信息超過localStorage的最大限制。關於最大限制是多少已經有不少人說過了,大家可以選擇性看一些,如果有必要可以親測一下。
window.addEventListener("load", function() {
try {
localStorage.setItem("hawkeyeHtml", document.documentElement.outerHTML);
} catch (e) {
//超過限度時,chrome和safari的e.name為'QuotaExceededError',FF的e.name為'NS_ERROR_DOM_QUOTA_REACHED'
console.error(e)
}
})
4. 頁面樣式丟失
尚未做此數據采集和監控,目前考慮大體思路和html劫持類似,保存截屏圖片,用一定算法和正常樣式下做對比,超過一定差異值即判定為樣式異常。
數據處理方式:
- 上報監控服務器:便於統一監控異常數量類型等
方案:我司仍選擇img的屬性src進行上報,一是考慮到解析性能,二是考慮到要為多站點服務,方便跨域上報。 - html頁面打印前端異常:便於快速准確定位某使用環境崩潰原因
方案:采集到信息儲存在localstorage中,注意控制儲存的條數。在需要查看異常信息的網站中增加該頁面,取出進行簡單處理即可。
缺陷處理
- 線上代碼是混淆壓縮的,無行號
解決方案:線上也用sourceMap來解決,至於sourMap是什么,怎么使用,根據項目不同,可自行google。 - 跨域的js,異常信息只有一個"Script error"。雖然js拿不到任何其他異常信息,但是控制台能打印出全部異常信息。所以
解決方案一:匹配到"Script error",引導開者去控制台查看或直接過濾掉。
解決方案二:解決跨域問題,分兩步。一:靜態資源請求需要加多一個Access-Control-Allow-Origin頭部。二:<scrip>標簽加上crossorigin屬性 - 跨域的js,獲取不到js文件名,會拿到
at <anonymous>:1:1醬紫的信息,暫時沒找到解決方案
推薦文章:JavaScript Errors Handbook:里面有部分內容已不適用,具體實施請注意驗證
