前端異常監控、上報及js壓縮程式碼定位

Keely發表於2018-08-03

最近在研究前端異常監控的問題,對查詢的資料做了整理彙總,總體如下

一、前端異常監控方式

1. window.onerror 異常處理

window.onerror 無論是非同步還是非非同步錯誤,onerror 都能捕獲到執行時錯誤。

window.onerror = function (msg, url, row, col, error) {
    console.log('我知道錯誤了');
    console.log({
        msg,  url,  row, col, error
    })
    return true;
};
複製程式碼

注意:

  • 1)window.onerror 函式只有在返回 true 的時候,異常才不會向上丟擲,否則即使是知道異常的發生控制檯還是會顯示 Uncaught Error: xxxxx。
  • 2)window.onerror 是無法捕獲到網路異常的錯誤。由於網路請求異常不會事件冒泡,因此必須在捕獲階段將其捕捉到才行,但是這種方式雖然可以捕捉到網路請求的異常,但是無法判斷 HTTP 的狀態是 404 還是其他比如 500 。還需要配合服務端日誌才進行排查分析才可以。
window.addEventListener('error', (msg, url, row, col, error) => {
    console.log('我知道錯誤了');
    console.log(
        msg, url, row, col, error
    );
    return true;
}, true);
複製程式碼

2. Promise錯誤

Promise 例項丟擲異常而你沒有用 catch 去捕獲的話,onerror 或 try-catch 也無能為力,無法捕捉到錯誤。 如果用到很多 Promise 例項的話,特別是你在一些基於 promise 的非同步庫比如 axios 等一定要小心,因為你不知道什麼時候這些非同步請求會丟擲異常而你並沒有處理它,所以你最好新增一個 Promise 全域性異常捕獲事件 unhandledrejection。

window.addEventListener("unhandledrejection", function(e){
    e.preventDefault()
    console.log('我知道 promise 的錯誤了');
    console.log(e.reason);
    return true;
});
複製程式碼

3.iframe 錯誤

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

1) 如果你的 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>
複製程式碼

2)如果你嵌入的 iframe 頁面和你的主站不是同個域名的,但是 iframe 內容不屬於第三方

可以通過與 iframe 通訊的方式將異常資訊拋給主站接收。與 iframe 通訊的方式有很多,常用的如: postMessage,hash 或者 name欄位跨域等等

3)如果是非同域且網站不受自己控制的話,除了通過控制檯看到詳細的錯誤資訊外,沒辦法捕獲

二、監控上報

監控拿到報錯資訊之後,接下來就需要將捕捉到的錯誤資訊傳送到資訊收集平臺上,常用的傳送形式主要有兩種:

  • 通過 Ajax 傳送資料
  • 動態建立 img 標籤的形式
function error(msg,url,line){
   var REPORT_URL = "xxxx/cgi"; // 收集上報資料的資訊
   var m =[msg, url, line, navigator.userAgent, +new Date];// 收集錯誤資訊,發生錯誤的指令碼檔案網路地址,使用者代理資訊,時間
   var url = REPORT_URL + m.join('||');// 組裝錯誤上報資訊內容URL
   var img = new Image;
   img.onload = img.onerror = function(){
      img = null;
   };
   img.src = url;// 傳送資料到後臺cgi
}
// 監聽錯誤上報
window.onerror = function(msg,url,line){
   error(msg,url,line);
}
複製程式碼

三、JS程式碼壓縮後,如何定位

該部分原文出處:https://github.com/joeyguo/blog/issues/14

示例:

1.原始碼(存在錯誤)

function test() {
    noerror // <- 報錯
}

test();
複製程式碼

2.經 webpack 打包壓縮後產生如下程式碼

!function(n){function r(e){if(t[e])return t[e].exports;var o=t[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var t={};r.m=n,r.c=t,r.i=function(n){return n},r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e})},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,"a",t),t},r.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},r.p="",r(r.s=0)}([function(n,r){function t(){noerror}t()}]);
複製程式碼

3.程式碼如期報錯,並上報相關資訊

{ msg: 'Uncaught ReferenceError: noerror is not defined',
  url: 'http://127.0.0.1:8077/main.min.js',
  row: '1',
  col: '515' }
複製程式碼

此時,錯誤資訊中行列數為 1 和 515。 結合壓縮後的程式碼,肉眼觀察很難定位出具體問題。

方案一:將壓縮程式碼中分號變成換行

uglifyjs 有一個叫 semicolons 配置引數,設定為 false 時,會將壓縮程式碼中的分號替換為換行符,提高程式碼可讀性, 如

!function(n){function r(e){if(t[e])return t[e].exports
var o=t[e]={i:e,l:!1,exports:{}}
return n[e].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var t={}
r.m=n,r.c=t,r.i=function(n){return n},r.d=function(n,t,e){r.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:e})},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n}
return r.d(t,"a",t),t},r.o=function(n,r){return Object.prototype.hasOwnProperty.call(n,r)},r.p="",r(r.s=0)}([function(n,r){function t(){noerror}t()}])
複製程式碼

此時,錯誤資訊中行列數為 5 和 137,查詢起來比普通壓縮方便不少。但仍會出現一行中有很多程式碼,不容易定位的問題。

方案二:js 程式碼半壓縮 · 保留空格和換行

uglifyjs 的另一配置引數 beautify 設定為 true 時,最終程式碼將呈現壓縮後進行格式化的效果(保留空格和換行),如

!function(n) {
    // ...
    // ...
}([ function(n, r) {
    function t() {
        noerror;
    }
    t();
} ]);
複製程式碼

此時,錯誤資訊中行列數為 32 和 9,能夠快速定位到具體位置,進而對應到原始碼。但由於增加了換行和空格,所以檔案大小有所增加。

方案三:SourceMap 快速定位

SourceMap 是一個資訊檔案,儲存著原始檔的資訊及原始檔與處理後檔案的對映關係。 在定位壓縮程式碼的報錯時,可以通過錯誤資訊的行列數與對應的 SourceMap 檔案,處理後得到原始檔的具體錯誤資訊。

SourceMap 檔案中的 sourcesContent 欄位對應原始碼內容,不希望將 SourceMap 檔案釋出到外網上,而是將其儲存到指令碼錯誤處理平臺上,只用在處理指令碼錯誤中。 通過 SourceMap 檔案可以得到原始檔的具體錯誤資訊,結合 sourcesContent 上原始檔的內容進行視覺化展示,讓報錯資訊一目瞭然!

sourceMap

方案四:開源方案 sentry

sentry 是一個實時的錯誤日誌追蹤和聚合平臺,包含了上面 sourcemap 方案,並支援更多功能,如:錯誤呼叫棧,log 資訊,issue管理,多專案,多使用者,提供多種語言客戶端等,具體介紹可以檢視 getsentry/sentry,sentry.io,這裡暫不展開。

sentry

參考文章:

  1. 跨域,你需要知道的全在這裡
  2. 指令碼錯誤量極致優化-讓指令碼錯誤一目瞭然
  3. 前端程式碼異常監控實戰

相關文章