如何處理前端異常監控?


為什么要進行異常處理?

很多異常是不可控的,比如資源加載異常,ajax請求異常等,會影響最終的呈現效果,做好異常處理,有大致以下幾點好處:

  • 1.增強用戶體驗;
  • 2.快速定位問題原因,及時發現問題。特別是移動端,機型、系統等不一樣,有了異常處理並上報,定位快;
  • 3.完善前端監控系統方案。

需要處理哪些異常?

  • JS語法錯誤、代碼異常
  • ajax請求異常
  • 靜態資源加載異常
  • promise異常
  • iframe異常
  • 跨域 script error
  • 崩潰和卡頓

 異常處理的方式

try-catch

 try-catch只能捕獲到同步運行時錯誤,無法捕獲語法錯誤和異步錯誤。

示例:運行時錯誤(能捕獲)

try {
    error;
} catch(e) {
    console.log('捕獲到錯誤了');
    console.log(e);
}

 示例:語法錯誤(不能捕獲)

try {
    var error = 'err;  // 少一個單引號
} catch(e) {
    console.log('捕獲不到錯誤了');
    console.log(e);
}

上面紅色標記的錯誤大致意思為:無效或者意外的標記。但是這種語法錯誤會直接拋出來,使后面的程序代碼無法運行,直接崩潰,一般在編碼的時候就能發現這類錯誤。

示例:異步錯誤(不能捕獲)

try {
    setTimeout(function() {
        error; // 異步錯誤,沒有定義
    })
} catch(e) {
    console.log('捕獲不到錯誤了');
    console.log(e);
}

window.onerror

 當JS運行發生錯誤時,window會觸發一個errorEvent接口的error事件並執行window.onerror()。

window.onerror比try-catch強一些,在try-catch的基礎上,它可以捕獲異步錯誤。

/**
 * @param {String} message 錯誤信息
 * @param {String} resource 出錯文件
 * @param {Number} row 行號
 * @param {Number} col 列號
 * @param {Object} error 錯誤詳細信息error對象
 * */
window.onerror = function(message,resource,row,col,error) {
    console.log('捕獲到錯誤信息');
    console.log({message,resource,row,col,error});
    return true;
}

示例:異步錯誤(能捕獲)

setTimeout(function() {
    error; // 異步錯誤,沒有定義
})

注意:

1.window.onerror也是不能捕獲語法錯誤的;

2.window.onerror也不能捕獲網絡請求異常情況,如靜態資源異常、接口異常等都是不行的;

3.特別注意的是,window.onerror函數在返回true的時候,異常才不會向上拋出,否則,控制台還是會顯示Uncaught Error: xxxxx

如下示例,我們讓window.onerror函數沒有返回true.

/**
 * @param {String} message 錯誤信息
 * @param {String} resource 出錯文件
 * @param {Number} row 行號
 * @param {Number} col 列號
 * @param {Object} error 錯誤詳細信息error對象
 * */
window.onerror = function(message,resource,row,col,error) {
    console.log('捕獲到錯誤信息');
    console.log({message,resource,row,col,error});
}
    
setTimeout(function() {
    error; // 異步錯誤,沒有定義
})

 小結:從上面兩種捕獲錯誤的方式來看,window.onerror()函數主要用來捕獲意料之外的錯誤,而try-catch主要是捕獲可預見情況下的特定錯誤。

window.addEventListener

 window.onerror函數不能捕獲靜態資源加載失敗的異常情況,當資源(圖片或腳本)加載失敗,加載資源的元素會觸發一個Event接口的error事件,並執行該元素上的onerror()處理函數,這些error事件不會向上冒泡到window上,但是可以被window.addEventListener捕獲。

<script type="text/javascript">
window.addEventListener('error',function(error){
    console.log('捕獲到異常錯誤了');
    console.log(error);
},true)
</script>
<img src="./images/error.jpg"/>

由於網絡請求異常不會事件冒泡,因此需要在捕獲階段將其捕捉到才行,這種方式雖然可以捕獲到網絡請求異常,但是無法判斷HTTP的狀態碼是404還是其他的如500等,所有還需要配合服務端日志進行排查分析才可以。

 unhandledrejection監聽UnCaught Promise Error

在很多時候我們使用Promise的時候忘記了寫catch,那么可以在全局增加一個unhandledrejection的監聽,用來全局監聽UnCaught Promise Error,使用方式如下:

window.addEventListener("unhandledrejection", function(e){
  e.preventDefault(); // 去掉控制台的異常顯示
  console.log('捕獲到異常:', e);
  return true;
});
Promise.reject('promise error');

VUE errorHandler

Vue.config.errorHandler = (err, vm, info) => {
  console.error('通過vue errorHandler捕獲的錯誤');
  console.error(err);
  console.error(vm);
  console.error(info);
}

如果在組件渲染時出現運行錯誤,錯誤將會被傳遞至全局 Vue.config.errorHandler 配置函數 (如果已設置)。利用這個鈎子函數來配合錯誤跟蹤服務是個不錯的主意。比如 Sentry,它為 Vue 提供了官方集成

iframe異常

iframe的異常捕獲需要借助window.onerror:

<iframe src="./a.html" frameborder="0"></iframe>
<script type="text/javascript">
/**
 * @param {String} message 錯誤信息
 * @param {String} resource 出錯文件
 * @param {Number} row 行號
 * @param {Number} col 列號
 * @param {Object} error 錯誤詳細信息error對象
 * */
window.frames[0].onerror = function(message,resource,row,col,error) {
    console.log('捕獲到錯誤信息');
    console.log({message,resource,row,col,error});
    return true;
}
</script>

我在a.html中添加了一句JS代碼如下:

<script type="text/javascript">
    var a= '1; // 缺少引號
</script>

那么在父窗口捕獲到的錯誤是:

Script error

 出現Script error的情況,基本上是跨域問題。例如我們的工程中的靜態資源使用CDN,我們引入的CDN方式可能是有不同的域名,如果沒有進行額外的配置,就會出現Script error。

解決思路:跨源資源共享機制(CORS),為 script 標簽添加 crossOrigin 屬性:

<script src="http://localhost:8081/index.js" crossorigin></script>

或者動態去添加 js 腳本:

const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);

特別注意,服務器端需要設置:Access-Control-Allow-Origin

解決 Script Error 的另類思路:改寫 EventTarget 的 addEventListener 方法;對傳入的 listener 進行包裝,返回包裝過的 listener,對其執行進行 try-catch;瀏覽器不會對 try-catch 起來的異常進行跨域攔截,所以 catch 到的時候,是有堆棧信息的;重新 throw 出來異常的時候,執行的是同域代碼,所以 window.onerror 捕獲的時候不會丟失堆棧信息;利用包裝 addEventListener,我們還可以達到「擴展堆棧」的效果:

(() => {
   const originAddEventListener = EventTarget.prototype.addEventListener;
   EventTarget.prototype.addEventListener = function (type, listener, options) {
   // 捕獲添加事件時的堆棧
   const addStack = new Error(`Event (${type})`).stack;
   const wrappedListener = function (...args) {
       try {
         return listener.apply(this, args);
       }
       catch (err) {
        // 異常發生時,擴展堆棧
        err.stack += '\n' + addStack;
         throw err;
       }
     }
     return originAddEventListener.call(this, type, wrappedListener, options);
   }
})()

 崩潰和卡頓

卡頓也就是網頁暫時響應比較慢, JS 可能無法及時執行。但是網頁崩潰可以利用 window 對象的 load 和 beforeunload 事件實現了網頁崩潰的監控。

window.addEventListener('load', function () {
    sessionStorage.setItem('good_exit', 'pending');
    setInterval(function () {
        sessionStorage.setItem('time_before_crash', new Date().toString());
    }, 1000);
  });

  window.addEventListener('beforeunload', function () {
    sessionStorage.setItem('good_exit', 'true');
  });

  if(sessionStorage.getItem('good_exit') &&
    sessionStorage.getItem('good_exit') !== 'true') {
    /*
        insert crash logging code here
    */
    alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
  }

錯誤上報

 通過 Ajax 發送數據 因為 Ajax 請求本身也有可能會發生異常,而且有可能會引發跨域問題,一般情況下更推薦使用動態創建 img 標簽的形式進行上報。

動態創建 img 標簽的形式:

function report(error) {
  let reportUrl = 'http://jartto.wang/report';
  new Image().src = `${reportUrl}?logs=${error}`;
}

收集異常信息量太多,怎么辦?實際中,我們不得不考慮這樣一種情況:如果你的網站訪問量很大,那么一個必然的錯誤發送的信息就有很多條,這時候,我們需要設置采集率,從而減緩服務器的壓力:

Reporter.send = function(data) {
  // 只采集 30%
  if(Math.random() < 0.3) {
    send(data)      // 上報錯誤信息
  }
}

采集率應該通過實際情況來設定,隨機數,或者某些用戶特征都是不錯的選擇。

也可以通過Sentry來實現前端的異常監控及上報。

總結

  • 可疑區域增加 Try-Catch
  • 全局監控 JS 異常 window.onerror
  • 全局監控靜態資源異常 window.addEventListener
  • 捕獲沒有 Catch 的 Promise 異常:unhandledrejection
  • VUE errorHandler
  • 監控網頁崩潰:window 對象的 load 和 beforeunload
  • 跨域 crossOrigin 解決

參考地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM