web前端監控的三個方面探討


一. js錯誤監控方式

1. 主動判斷

我們在一些運算之后,得到一個期望的結果,然而結果不是我們想要的

// test.js
function calc(){
  // code...
  return val;
}
if(calc() !== "someVal"){
  Reporter.send({
    position: "test.js::<Function>calc"
    msg: "calc error"
  });
}

這種屬於邏輯錯誤/狀態錯誤的反饋,在接口 status 判斷中用的比較多。

2. try..catch 捕獲

判斷一個代碼段中存在的錯誤:

try {
  init();
  // code...
} catch(e){
  Reporter.send(format(e));
}

以 init 為程序的入口,代碼中所有同步執行出現的錯誤都會被捕獲,這種方式也可以很好的避免程序剛跑起來就掛。

3. window.onerror

捕獲全局錯誤:

window.onerror = function() {
  var errInfo = format(arguments);
  Reporter.send(errInfo);
  return true;
};

在上面的函數中返回 return true,錯誤便不會暴露到控制台中。下面是它的參數信息:

/**
 * @param {String}  errorMessage   錯誤信息
 * @param {String}  scriptURI      出錯的文件
 * @param {Long}    lineNumber     出錯代碼的行號
 * @param {Long}    columnNumber   出錯代碼的列號
 * @param {Object}  errorObj       錯誤的詳細信息,Anything
 */
window.onerror = function(errorMessage, scriptURI, lineNumber,columnNumber,errorObj) { 
    // code..
}

window.onerror 算是一種特別暴力的容錯手段,try..catch 也是如此,他們底層的實現就是利用 C/C++ 中的 goto 語句實現,一旦發現錯誤,不管目前的堆棧有多深,不管代碼運行到了何處,直接跑到頂層或者 try..catch 捕獲的那一層,這種一腳踢開錯誤的處理方式並不是很好。

 

關於 window.onerror 還有兩點需要值得注意

  1. 對於 onerror 這種全局捕獲,最好寫在所有 JS 腳本的前面,因為你無法保證你寫的代碼是否出錯,如果寫在后面,一旦發生錯誤的話是不會被 onerror 捕獲到的。
  2. 另外 onerror 是無法捕獲到網絡異常的錯誤。

當我們遇到 <img src="./404.png"> 報 404 網絡請求異常的時候,onerror 是無法幫助我們捕獲到異常的。

<script>  window.onerror = function (msg, url, row, col, error) {  console.log('我知道異步錯誤了');  console.log({  msg, url, row, col, error  })  return true;  }; </script> <img src="./404.png">

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

<script> window.addEventListener('error', (msg, url, row, col, error) => {  console.log('我知道 404 錯誤了');  console.log(  msg, url, row, col, error  );  return true; }, true); </script> <img src="./404.png" alt="">

這點知識還是需要知道,要不然用戶訪問網站,圖片 CDN 無法服務,圖片加載不出來而開發人員沒有察覺就尷尬了。

 

Promise 錯誤

通過 Promise 可以幫助我們解決異步回調地獄的問題,但是一旦 Promise 實例拋出異常而你沒有用 catch 去捕獲的話,onerror 或 try-catch 也無能為力,無法捕捉到錯誤。

window.addEventListener('error', (msg, url, row, col, error) => { console.log('我感知不到 promise 錯誤'); console.log( msg, url, row, col, error ); }, true); Promise.reject('promise error'); new Promise((resolve, reject) => { reject('promise error'); }); new Promise((resolve) => { resolve(); }).then(() => { throw 'promise error' });

雖然在寫 Promise 實例的時候養成最后寫上 catch 函數是個好習慣,但是代碼寫多了就容易糊塗,忘記寫 catch。

所以如果你的應用用到很多的 Promise 實例的話,特別是你在一些基於 promise 的異步庫比如 axios 等一定要小心,因為你不知道什么時候這些異步請求會拋出異常而你並沒有處理它,所以你最好添加一個 Promise 全局異常捕獲事件 unhandledrejection。

window.addEventListener("unhandledrejection", function(e){ e.preventDefault() console.log('我知道 promise 的錯誤了'); console.log(e.reason); return true; }); Promise.reject('promise error'); new Promise((resolve, reject) => { reject('promise error'); }); new Promise((resolve) => { resolve(); }).then(() => { throw 'promise error' });

 

 

window.onerror 能否捕獲 iframe 的錯誤

當你的頁面有使用 iframe 的時候,你需要對你引入的 iframe 做異常監控的處理,否則一旦你引入的 iframe 頁面出現了問題,你的主站顯示不出來,而你卻渾然不知。

首先需要強調,父窗口直接使用 window.onerror 是無法直接捕獲,如果你想要捕獲 iframe 的異常的話,有分好幾種情況。

如果你的 iframe 頁面和你的主站是同域名的話,直接給 iframe 添加 onerror 事件即可。

<iframe src="./iframe.html" frameborder="0"></iframe> <script>  window.frames[0].onerror = function (msg, url, row, col, error) {  console.log('我知道 iframe 的錯誤了,也知道錯誤信息');  console.log({  msg, url, row, col, error  })  return true;  }; </script>

讀者可以通過 npm run iframe 查看效果:

如果你嵌入的 iframe 頁面和你的主站不是同個域名的,但是 iframe 內容不屬於第三方,是你可以控制的,那么可以通過與 iframe 通信的方式將異常信息拋給主站接收。與 iframe 通信的方式有很多,常用的如:postMessage,hash 或者 name 字段跨域等等,這里就不展開了,感興趣的話可以看:跨域,你需要知道的全在這里

如果是非同域且網站不受自己控制的話,除了通過控制台看到詳細的錯誤信息外,沒辦法捕獲,這是出於安全性的考慮,你引入了一個百度首頁,人家頁面報出的錯誤憑啥讓你去監控呢,這會引出很多安全性的問題。

 

 

 

異常上報方式

監控拿到報錯信息之后,接下來就需要將捕捉到的錯誤信息發送到信息收集平台上,常用的發送形式主要有兩種:

  1. 通過 Ajax 發送數據
  2. 動態創建 img 標簽的形式

實例 - 動態創建 img 標簽進行上報

function report(error) { var reportUrl = 'http://xxxx/report'; new Image().src = reportUrl + 'error=' + error; }


收集異常信息量太多,怎么辦

如果你的網站訪問量很大,假如網頁的 PV 有 1kw,那么一個必然的錯誤發送的信息就有 1kw 條,我們可以給網站設置一個采集率:

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

 

 

 

二. 接口請求時長

    /**
     * 攔截接口請求,上報接口信息
     */
    //統一攔截ajax請求
    var start_time = 0,
        gap_time = 0;  //計算請求延時
    window.addEventListener('ajaxReadyStateChange', function (e) {
        var xhr = e.detail,
            status = xhr.status,
            readyState = xhr.readyState,
            responseText = xhr.responseText;

        /**
         * 計算請求延時
         */ 
        if(readyState == 1){
            start_time = (new Date()).getTime();
        }
        if(readyState == 4){
            gap_time = (new Date()).getTime() - start_time;
        }
        /**
         * 上報請求信息
         * @Author   smy
         * @DateTime 2018-06-27T16:32:30+0800
         */
        if(readyState == 4){
            httpReport(gap_time, status, xhr.responseURL)
        }

    })

 

三. 頁面加載時長

參見:https://blog.oldj.net/2012/01/09/measuring-the-user-latency/

 

--------------------------------------------------------------

已知問題:

  1. ios蘋果不支持crossorigin=“anonymous”, 只支持crossorigin="use-credentials",cdn 不支持credentials。

  2. ios 會對客戶端調用webview方法找不到進行報錯到window.onerror, 當上報的值是不存在的對象屬性時,報錯內容為global code,比如這時上報error.stack, 此時error為null,應上報message。

 

---------------------------------------------------------------

參考:

https://www.cnblogs.com/hustskyking/p/fe-monitor.html

http://web.jobbole.com/93684/

https://github.com/happylindz/blog/issues/5


免責聲明!

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



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