JS錯誤監控總結

前言

作好錯誤監控,將用戶使用時的錯誤日誌上報,能夠幫助咱們更快的解決一些問題。目前開源的比較好的前端監控有html

那前端監控是怎麼實現的呢?要想了解這個,須要知道前端錯誤大概分爲哪些以及如何捕獲處理。前端

前端錯誤分爲JS運行時錯誤、資源加載錯誤和接口錯誤三種。vue

1、JS運行時錯誤

JS運行時錯誤通常使用window.onerror捕獲,可是有一種特殊狀況就是promise被reject而且錯誤信息沒有被處理的時候拋出的錯誤webpack

1.1 通常狀況的JS運行時錯誤

使用window.onerror和window.addEventListener('error')捕獲。其中window.onerror含有詳細的error信息(error.stack),並且兼容性更好,因此通常JS運行時錯誤使用window.onerror捕獲處理git

window.onerror = function (msg, url, lineNo, columnNo, error) 
    { 
       // 處理error信息
    } 
 
    window.addEventListener('error', event =>  
    {  
       console.log('addEventListener error:' + event.target); 
    }, true); 
    // true表明在捕獲階段調用,false表明在冒泡階段捕獲。使用true或false均可以
例子: https://jsbin.com/lujahin/edit?html,console,output 點擊button拋出錯誤,分別被window.onerror和window.addEventListener('error')捕獲

1.2 Uncaught (in promise)

當promise被reject而且錯誤信息沒有被處理的時候,會拋出一個unhandledrejection,而且這個錯誤不會被window.onerror以及window.addEventListener('error')捕獲,須要用專門的window.addEventListener('unhandledrejection')捕獲處理github

window.addEventListener('unhandledrejection', event => 
    { 
       console.log('unhandledrejection:' + event.reason); // 捕獲後自定義處理
    });
https://developer.mozilla.org...
例子: https://jsbin.com/jofomob/edit?html,console,output 點擊button拋出unhandledrejection錯誤,而且該錯誤僅能被window.addEventListener('unhandledrejection')捕獲

1.3 console.error

一些特殊狀況下,還須要捕獲處理console.error,捕獲方式就是重寫window.console.errorweb

var consoleError = window.console.error; 
window.console.error = function () { 
    alert(JSON.stringify(arguments)); // 自定義處理
    consoleError && consoleError.apply(window, arguments); 
};
例子: https://jsbin.com/pemigew/edit?html,console,output

1.4 特別說明跨域日誌

什麼是跨域腳本error?api

https://developer.mozilla.org...
當加載自不一樣域的腳本中發生語法錯誤時,爲避免信息泄露(參見bug 363897),語法錯誤的細節將不會報告,而代之簡單的"Script error."。在某些瀏覽器中,經過在<script>使用crossorigin屬性並要求服務器發送適當的 CORS HTTP 響應頭,該行爲可被覆蓋。一個變通方案是單獨處理"Script error.",告知錯誤詳情僅能經過瀏覽器控制檯查看,沒法經過JavaScript訪問。

例子: http://sandbox.runjs.cn/show/... 請打開頁面打開控制檯。該頁面分別加載了兩個不一樣域的js腳本,配置了crossorigin的window.onerror能夠報出詳細的錯誤,沒有配置crossorigin只能報出'script error',而且沒有錯誤信息跨域

1.5 特別說明sourceMap

在線上因爲JS通常都是被壓縮或者打包(webpack)過,打包後的文件只有一行,所以報錯會出現第一行第5000列出現JS錯誤,給排查帶來困難。sourceMap存儲打包前的JS文件和打包後的JS文件之間一個映射關係,能夠根據打包後的位置快速解析出對應源文件的位置。promise

可是出於安全性考慮,線上設置sourceMap會存在不安全的問題,由於網站使用者能夠輕易的看到網站源碼,此時能夠設置.map文件只能經過公司內網訪問下降隱患

sourceMap配置devtool: 'inline-source-map'
若是使用了uglifyjs-webpack-plugin 必須把 sourceMap設置爲true
https://doc.webpack-china.org...

1.6 其它

1.6.1 sentry把全部的回調函數使用try catch封裝一層
https://github.com/getsentry/raven-js/blob/master/src/raven.js

1.6.2 vue errorHandler
https://vuejs.org/v2/api/#errorHandler
其原理也是使用try catch封裝了nextTick,$emit, watch,data等
https://github.com/vuejs/vue/blob/dev/dist/vue.runtime.js

2、資源加載錯誤

使用window.addEventListener('error')捕獲,window.onerror捕獲不到資源加載錯誤

https://jsbin.com/rigasek/edit?html,console 圖片資源加載錯誤。此時只有window.addEventListener('error')能夠捕獲到

window.onerror和window.addEventListener('error')的異同:相同點是均可以捕獲到window上的js運行時錯誤。區別是1.捕獲到的錯誤參數不一樣 2.window.addEventListener('error')能夠捕獲資源加載錯誤,可是window.onerror不能捕獲到資源加載錯誤

3、接口錯誤

全部http請求都是基於xmlHttpRequest或者fetch封裝的。因此要捕獲全局的接口錯誤,方法就是封裝xmlHttpRequest或者fetch

3.1 封裝xmlHttpRequest

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) {
          // 自定義錯誤上報 }
}
xmlhttp.prototype.send = function () {
    if (this['addEventListener']) {
        this['addEventListener']('error', _handleEvent);
        this['addEventListener']('load', _handleEvent);
        this['addEventListener']('abort', _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);
}

3.2 封裝fetch

if(!window.fetch) return;
    let _oldFetch = window.fetch;
    window.fetch = function () {
        return _oldFetch.apply(this, arguments)
        .then(res => {
            if (!res.ok) { // True if status is HTTP 2xx
                // 上報錯誤
            }
            return res;
        })
        .catch(error => {
            // 上報錯誤
            throw error;  
        })
}

結論

  1. 使用window.onerror捕獲JS運行時錯誤
  2. 使用window.addEventListener('unhandledrejection')捕獲未處理的promise reject錯誤
  3. 重寫console.error捕獲console.error錯誤
  4. 在跨域腳本上配置crossorigin="anonymous"捕獲跨域腳本錯誤
  5. window.addEventListener('error')捕獲資源加載錯誤。由於它也能捕獲js運行時錯誤,爲避免重複上報js運行時錯誤,此時只有event.srcElement inatanceof HTMLScriptElement或HTMLLinkElement或HTMLImageElement時才上報
  6. 重寫window.XMLHttpRequest和window.fetch捕獲請求錯誤

利用以上原理,簡單寫了一個JS監控,只處理了一些JS錯誤,暫時沒有作和性能相關的監控
https://github.com/Lie8466/better-js

若是發現文章有錯誤,歡迎指正。

相關文章
相關標籤/搜索