JSTracker – 淘寶前端監控平臺
基本上伺服器端的程式碼都是處於 7×24 小時的實時監控狀態的,一旦有任何異常對應的開發同學就馬上收到報警,並且第一時間處理。 但是對於前端來說,往往是實際使用者那裡的指令碼報錯後才知道頁面出現異常,這時候已經是故障了。
為了讓前端也能和後端一樣,需要將線上的 JavaScript 程式碼監控起來,當使用者端瀏覽器出現異前端第一時間被通知到。於是便有了淘寶前端的監控平臺:JSTracker。
採集哪些資料
在解決怎麼採集之前,還需要解決採集哪些資料,哪些資料是有用的?
主要原則就是避開使用者敏感欄位,採集瀏覽器版本、作業系統版本、報錯的 msg 資訊等。
JavaScript 異常的時候捕獲異常
主動捕獲異常方案主要是 onError 和 addEventListener,
onError 在 IE6 開始就支援了,所以 JSTracker 的主動採集是使用的 onError。
onError 可以採集到 file、line、col 等資訊,但是實際情況中卻大部分收集到的是 script error。
原因是瀏覽器的同源性策略(CORS),在高階瀏覽器中如果瀏覽器捕獲到了錯誤資訊,如果 JS 檔案所在的域名(如:g.alicdn.com)和當前的頁面地址(如:www.taobao.com)是跨域的,那麼瀏覽器會把 onError 中的 msg 替換為 script error,其餘欄位也會做替換。
webkit 的原始碼:
Script error 這個問題也是目前大家最詬病的一個問題:當報警發生之後,我們只知道有問題,但是很難知道哪裡出了問題。
其實是有解決方案的:
- 首先 JavaScript 請求的 http 返回頭上需要加上一個 Access-Control-Allow-Origin 頭。
- 在引入 JavaScript 檔案的時候需要在 script 標籤上新增 crossorigin 屬性,在加這個屬性前請一定確保上一條已經完成,否則 JavaScript 檔案不會執行!!!。
1 |
<script src="http://somremotesite.example/script.js" crossorigin></script> |
這個解決方式看起來完美,但是要做的改造實在是太多了,目前還是主動上報為主。
主動上報異常
onError 的方案會採集到全面的瀏覽器報錯,但是太全了,出了剛才的 script error 問題,還會採集到。
- ISP 在頁面中注入的指令碼報錯(我們的前端質量很高的,但是被這些牛氓們一弄,啥質量都沒了)。
- 外掛問題(亂七八糟的外掛也是一樣的令人討厭,注入奇怪的東西在頁面中,引起報錯)。
為了讓採集到的錯誤更有價值,需要暴露介面給讓前端門自己在程式碼中上報異常(作為一名合格的程式碼工程師,一定要有拋異常的習慣呢)。
現在我們提供了自定義上報的介面,沒錯就是這麼簡單,你不用擔心 JSTracker 指令碼有沒有載入,只要在你的程式碼中直接用就行了。
1 2 3 4 5 6 7 8 |
window.JSTracker2 = window.JSTracker2 || []; try{ //your code }catch(e){ JSTracker2.push({ msg: "xx_api_failed" }); } |
自己上報的錯誤對於定位也是很容易的:
一些細節問題
在程式碼中使用 try catch 是否對效能有影響
try catch 對效能的影響微乎其微,但是一些用法會讓效能受很大的影響, 參考這個實驗:http://taobaofed.org/blog/2015/10/28/try-catch-runing-problem/
總結下來就是:在 try 語句塊中不要定義太多的變數,最好是隻寫一個函式呼叫,避免 try 執行中變數拷貝造成的效能損耗。類似的不只是 try,定義 function 也是一樣的。
採集到的資料如何發出去
JSTracker 的資料傳送了非常大,這塊都交給後端幫我們處理,採集部分只需要關注準確無誤的把資料傳送出去。
最初的簡單的傳送方案,直接用 GET 請求,將引數拼接在 URL 後面:
1 2 |
var url = 'xxx'; new Image().src = url; |
後來發現這樣傳送有一定概率丟失資料,當瀏覽器回收記憶體的時候這個請求是發不出去的,所以要將這個變數 hold住:
1 2 3 4 5 6 7 |
var win = window; var n = 'jsFeImage_' + _make_rnd(), img = win[n] = new Image(); img.onload = img.onerror = function () { win[n] = null; }; img.src = src; |
隨機數造成的資料丟失
我們為了防止快取,經常會用毫秒的時間作為隨機數(如:+new Date()),但是在極端情況下可能1ms就會發出兩條 log,這樣第二條 log 就會丟失。
1 2 3 |
var _make_rnd = function(){ return (+new Date()) + '.r' + Math.floor(Math.random() * 1000); }; |
360瀏覽器識別
為了能更好的統計這個瀏覽器,需要識別360SE、360EE。眾所周知 3Q 大戰之後360的 UA 就和 Chrome 的一樣,所以我們要用一些特別的方法識別它們。目前為止能夠識別的方案,這個方案會隨時更新,適應360的變化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
var is360 = function(){ try { if(/UBrowser/i.test(navigator.userAgent)){ return ''; } if (typeof window.scrollMaxX !== 'undefined') { return ''; } var _track = 'track' in document.createElement('track'); var webstoreKeysLength = window.chrome && window.chrome.webstore ? Object.keys(window.chrome.webstore).length : 0; if (window.clientInformation && window.clientInformation.languages && window.clientInformation.languages.length > 2) { return ''; } if (_track) { return webstoreKeysLength > 1 ? ' QIHU 360 EE' : ' QIHU 360 SE'; } return ''; }catch(e){ return ''; } }(); |