前端異常監控
如果debug是移除bug的流程,那麼編程就一定是將bug放進去的流程。
如果沒有用戶反饋問題,那就代表我們的產品棒棒噠,對不對?
主要內容
- Web規範中相關前端異常
- 異常按照捕獲方式分類
- 異常的捕獲方式
- 日誌上報的方式
前端異常類型(Execption)
WebIDL和ecma-262中的錯誤類型
-
ECMAScript exceptions <==> IDL 的簡單異常
當腳本代碼運行時發生的錯誤,會創建Error對象,並將其拋出,除了通用的Error構造函數外,以下是另外幾個ECMAScript 2015中定義的錯誤構造函數。
- EvalError eval錯誤
- RangeError 範圍錯誤
- ReferenceError 引用錯誤
- TypeError 類型錯誤
- URIError URI錯誤
- SyntaxError 語法錯誤 (這個錯誤WebIDL中故意省略,保留給ES解析器使用)
- Error 通用錯誤 (這個錯誤WebIDL中故意省略,保留給開發者使用使用)
-
DOMException 最新的DOM規範定義的錯誤類型集,兼容舊瀏覽的DOMError接口, 完善和規範化DOM錯誤類型。
- IndexSizeError 索引不在允許的範圍內
- HierarchyRequestError 節點樹層次結構是不正確的。
- WrongDocumentError 對象是錯誤的
- InvalidCharacterError 字符串包含無效字符。
- NoModificationAllowedError 對象不能被修改。
- NotFoundError 對象不能在這裏被找到。
- NotSupportedError 不支持的操作
- InvalidStateError 對象是一個無效的狀態。
- SyntaxError 字符串不匹配預期的模式
- InvalidModificationError 對象不能以這種方式被修改
- NamespaceError 操作在XML命名空間內是不被允許的
- InvalidAccessError 對象不支持這種操作或參數。
- TypeMismatchError 對象的類型不匹配預期的類型。
- SecurityError 此操作是不安全的。
- NetworkError 發生網絡錯誤
- AbortError 操作被中止
- URLMismatchError 給定的URL不匹配另一個URL。
- QuotaExceededError 已經超過給定配額。
- TimeoutError 操作超時。
- InvalidNodeTypeError 這個操作的 節點或節點祖先 是不正確的
- DataCloneError 對象不能克隆。
前端錯誤異常按照捕獲方式分類
-
[x] 運行時異常
- 語法錯誤
-
[x] 資源加載異常
- img
- script
- link
- audio
- video
- iframe
- ...外鏈資源的DOM元素
-
[x] 異步請求異常
- XMLHttpRequest
- fetch
- [x] Promise異常
-
[ ]
CSS中資源異常- @font-face
- background-image
- ...暫時無法捕獲
前端錯誤異常的捕獲方式
-
try-catch (ES提供基本的錯誤捕獲語法)
- 只能捕獲同步代碼的異常
回調setTimeoutpromise
-
window.onerror = cb (DOM0)
- img
- script
- link
- window.addEventListener('error', cb, true) (DOM2)
- window.addEventListener("unhandledrejection", cb) (DOM4)
- Promise.then().catch(cb)
- 封裝XMLHttpRequest&fetch | 覆寫請求接口對象
try-catch-finally
將能引發異常的代碼塊放到try中,並對應一個響應,然後有異常會被捕獲
try {
// 模擬一段可能有錯誤的代碼
throw new Error("會有錯誤的代碼塊")
} catch(e){
// 捕獲到try中代碼塊的錯誤得到一個錯誤對象e,進行處理分析
report(e)
} finally {
console.log("finally")
}
onerror事件
window.onerror
當JavaScript運行時錯誤(包括語法錯誤)發生時,window會觸發一個ErrorEvent接口的事件,並執行window.onerror()
/**
* @description 運行時錯誤處理器
* @param {string} message 錯誤信息
* @param {string} source 發生錯誤的腳本URL
* @param {number} lineno 發生錯誤的行號
* @param {number} colno 發生錯誤的列號
* @param {object} error Error對象
*/
function err(message,source,lineno,colno,error) {...}
window.onerror = err
element.onerror
當一項資源(如<img>
或<script>
)加載失敗,加載資源的元素會觸發一個Event接口的error事件,並執行該元素上的onerror()處理函數。
element.onerror = function(event) { ... } //注意和window.onerror的參數不同
注意:這些error事件不會向上冒泡到window,不過能被單一的window.addEventListener捕獲。
window.addEventListener
addEventListener相關的一些內容
W3C DOM2 Events規範中提供的註冊事件監聽器的方法, 在這之前均使用el.onclick
的形式(DOM0 規範的基本內容,幾乎所有瀏覽器都支持)。
注意: 接口的幾種語法
error事件捕獲資源加載錯誤
資源加載失敗,不會冒泡,但是會被addEventListener捕獲,所以我們可以指定在加載失敗事件的捕獲階段捕獲該錯誤。
注意: 接口同時也能捕獲運行時錯誤。
window.addEventListener("error", function(e) {
var eventType = [].toString.call(e, e);
if (eventType === "[object Event]") { // 過濾掉運行時錯誤
// 上報加載錯誤
report(e)
}
},
true
);
unhandledrejection事件捕獲Promise異常
最新的規範中定義了 unhandledrejection事件用於全局捕獲promise對象沒有rejection處理器時異常情況。
window.addEventListener("unhandledrejection", function (event) {
// ...your code here to handle the unhandled rejection...
// Prevent the default handling (error in console)
event.preventDefault();
});
Promise.then().catch(cb).finally()
Promise中的錯誤會被Promise.prototype.catch捕獲,所以我們通過這種方式捕獲錯誤,這包括一些不支持unhandledrejection事件的環境中promisede polyfill實現。
new Promise(function(resolve, reject) {
throw 'Uncaught Exception!';
}).catch(function(e) {
console.log(e); // Uncaught Exception!
});
封裝XMLHttpRequest&fetch | 覆寫請求接口對象
// 覆寫XMLHttpRequest API
if(!window.XMLHttpRequest) return;
var xmlhttp = window.XMLHttpRequest;
var _oldSend = xmlhttp.prototype.send;
var _handleEvent = function (event) {
if (event && event.currentTarget && event.currentTarget.status !== 200) {
report(event)
}
}
xmlhttp.prototype.send = function () {
if (this['addEventListener']) {
this['addEventListener']('error', _handleEvent);
this['addEventListener']('load', _handleEvent);
this['addEventListener']('abort', _handleEvent);
this['addEventListener']('close', _handleEvent);
} else {
var _oldStateChange = this['onreadystatechange'];
this['onreadystatechange'] = function (event) {
if (this.readyState === 4) {
_handleEvent(event);
}
_oldStateChange && _oldStateChange.apply(this, arguments);
};
}
return _oldSend.apply(this, arguments);
}
// 覆寫fetch API
if (!window.fetch) return;
var _oldFetch = window.fetch;
window.fetch = function() {
return _oldFetch
.apply(this, arguments)
.then(function(res){
if (!res.ok) {
// True if status is HTTP 2xx
report(res)
}
return res;
})
.catch(function(error){
report(res)
});
}
日誌上報的方式
- 異步請求上報, 後端提供接口,或者直接發到日誌服務器
-
img請求上報, url參數帶上錯誤信息
eg:(new Image()).src = 'http://baidu.com/tesjk?r=tksjk'
注意跨源腳本異常
當加載自不同域的腳本中發生語法錯誤時,爲避免信息泄露,語法錯誤的細節將不會報告,而代之簡單的 "Script error."
由於同源策略影響,瀏覽器限制跨源腳本的錯誤訪問,這樣跨源腳本錯誤報錯信息如下圖:
在H5的規定中,只要滿足下面倆個條件,是允許獲取跨源腳本的錯誤信息的。
- 客戶端在script標籤上增加crossorigin屬性;
- 服務端設置js資源響應頭Access-Control-Origin:*(或者是域名)。
擴展閱讀
業界已經有的監控平臺
- Sentry開源
- 阿里的ARMS
- fundebug
- FrontJS
幾個異常監控的問題
- 如何保證大家提交的代碼是符合預期的? 如何瞭解前端項目的運行是否正常,是否存在錯誤?
代碼質量體系控制和錯誤監控以及性能分析
-
如果用戶使用網頁,發現白屏,現在聯繫上了你們,你們會向他詢問什麼信息呢?先想一下爲什麼會白屏?
我們以用戶訪問頁面的過程爲順序,大致排查一下
- 用戶沒打開網絡
- DNS域名劫持
- http劫持
- cdn或是其他資源文件訪問出錯
- 服務器錯誤
- 前端代碼錯誤
- 前端兼容性問題
- 用戶操作出錯
通過以上可能發生錯誤的環節,我們需要向用戶手機一下以下的用戶信息
- 當前的網絡狀態
- 運營商
- 地理位置
- 訪問時間
- 客戶端的版本(如果是通過客戶端訪問)
- 系統版本
- 瀏覽器信息
- 設備分辨率
- 頁面的來源
- 用戶的賬號信息
- 通過performance API收集用戶各個頁面訪問流程所消耗的時間
- 收集用戶js代碼報錯的信息
- 如果我們使用了腳本代碼壓縮,然而我們又不想將sourcemap文件發佈到線上,我們怎麼捕獲到錯誤的具體信息?
- CSS文件中也存在引用資源,@font-face, background-image ...等這些請求錯誤該如何進行錯誤捕獲?
總結
-
Web規範中相關前端異常
- DOM處理異常
- ECMAScript處理異常
-
異常按照捕獲方式分類
- 運行時異常
- 資源加載異常
- 異步請求異常
- Promise異常
-
異常的捕獲方式
-
try-catch (ES提供基本的錯誤捕獲語法)
- 只能捕獲同步代碼的異常
回調setTimeoutpromise
-
window.onerror = cb (DOM0)
- img
- script
- link
- window.addEventListener('error', cb, true) (DOM2)
- window.addEventListener("unhandledrejection", cb) (DOM4)
- Promise.then().catch(cb)
- 封裝XMLHttpRequest&fetch | 覆寫請求接口對象
-
注意點:跨源腳本異常的捕獲
-
日誌上報的方式
- 異步請求上報
- new img上報
-
擴展閱讀
- 業界已有的異常監控平臺
- 幾個跟異常監控有關的問題