搭建一個前端監控系統,不再錯過BUG

藍色的秋風發表於2017-12-18

前言

還記得在我上一家公司中,某一大佬做了一個監控系統,牛逼哄哄,挺想研究他到底是怎麼搞出來的。當然我們也不是拍拍腦袋幹活的人,總不能人家咋幹我們就咋幹。下面先就介紹下,這樣的平臺到底有啥好處。

背景

首先我們為什麼要做前端系統呢,先看下面這張表,可以很顯然的看出,前端的效能對於產品的價值提升還是蠻有幫助的,但是這些資訊如果我們能實時的採集到,並且實施以監控,讓整個產品在產品線上一直保持高效的運作,這才是我們的目的。

效能 收益
Google 延遲 400ms 搜尋量下降 0.59%
Bing 延遲 2s 收入下降 4.3%
Yahoo 延遲 400ms 流量下降 5-9%
Mozilla 頁面開啟減少 2.2s 下載量提升 15.4%
Netflix 開啟 Gzip 效能提升 13.25% 頻寬減少50%

其次,也有利於我們釋出的產品,能夠及時發現我們的錯誤。如果一個產品在新的迭代中,發生不可描述的錯誤。

https://user-gold-cdn.xitu.io/2017/12/17/16062ce3510887ef?w=211&h=218&f=gif&s=948250

對!就是不可描述。我們總不可能等待使用者的反饋投訴,到那個時候黃花菜都涼了。

開始

基於以上我們就開始搭建一個前端監簡易控平臺。(雖然現在市面上有很多這樣的系統比如ELK,但是還是忍不住自己擼一個)

只能是簡易了。

https://user-gold-cdn.xitu.io/2017/12/17/16062ce34e8a7e9f?w=280&h=155&f=gif&s=675245

兄弟們原諒我,只能幫你們到這裡了。

接下來請看。

https://user-gold-cdn.xitu.io/2017/12/17/16062ce34f90726b?w=430&h=238&f=png&s=7208

以上是我們需要做的一些事情。

收集資訊

要做監控系統,首先我們得有一個物件。我們監控的物件!物件!物件!物件。

我在我的系統寫了一個這樣的頁面,

<body>
    <div>2</div>
    <div>2</div>
    <div>2</div>
    <div>2</div>
    <div>2</div>
    <div>2</div> 
</body>
複製程式碼

沒錯這就是我們要監控的頁面。這個.....真不是我懶。

https://user-gold-cdn.xitu.io/2017/12/17/16062ce34a300fd3?w=214&h=216&f=gif&s=988503

然後接下來我一共設計了3塊資料

  • 頁面載入時間
  • 統計使用者使用裝置
  • 錯誤量的統計

頁面載入時間

window.logInfo = {};  //統計頁面載入時間
window.logInfo.openTime = performance.timing.navigationStart;
window.logInfo.whiteScreenTime = +new Date() - window.logInfo.openTime;
document.addEventListener('DOMContentLoaded',function (event) {
  window.logInfo.readyTime = +new Date() - window.logInfo.openTime;
});
window.onload = function () {
  window.logInfo.allloadTime = +new Date() - window.logInfo.openTime;
  window.logInfo.nowTime = new Date().getTime();
  var timname = {
    whiteScreenTime: '白屏時間',
    readyTime: '使用者可操作時間',
    allloadTime: '總下載時間',
    mobile: '使用裝置',
    nowTime: '時間',
  };
  var logStr = '';
  for (var i in timname) {
    console.warn(timname[i] + ':' + window.logInfo[i] + 'ms');
    if (i === 'mobile') {
      logStr += '&' + i + '=' + window.logInfo[i];
    } else {
      logStr += '&' + i + '=' + window.logInfo[i];
    }

  }
  (new Image()).src = '/action?' + logStr;
};
複製程式碼

統計使用者使用裝置

window.logInfo.mobile = mobileType();
function mobileType() {
  var u = navigator.userAgent, app = navigator.appVersion;
  var type =  {// 移動終端瀏覽器版本資訊
    ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios終端
    iPad: u.indexOf('iPad') > -1, //是否iPad
    android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android終端或者uc瀏覽器
    iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, //是否為iPhone或者QQHD瀏覽器
    trident: u.indexOf('Trident') > -1, //IE核心
    presto: u.indexOf('Presto') > -1, //opera核心
    webKit: u.indexOf('AppleWebKit') > -1, //蘋果、谷歌核心
    gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐核心
    mobile: !!u.match(/AppleWebKit.*Mobile/i) || !!u.match(/MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/), //是否為移動終端
    webApp: u.indexOf('Safari') == -1 //是否web應該程式,沒有頭部與底部
  };
  var lists = Object.keys(type);
  for(var i = 0; i < lists.length; i++) {
    if(type[lists[i]]) {
      return lists[i];
    }
  }  
}
複製程式碼

錯誤量的統計

window.onload = function () {
        window.logInfo.allloadTime = +new Date() - window.logInfo.openTime;
        window.logInfo.nowTime = new Date().getTime();
        var timname = {
            whiteScreenTime: '白屏時間',
            readyTime: '使用者可操作時間',
            allloadTime: '總下載時間',
            mobile: '使用裝置',
            nowTime: '時間',
        };
        var logStr = '';
        for (var i in timname) {
            console.warn(timname[i] + ':' + window.logInfo[i] + 'ms');
            if (i === 'mobile') {
                logStr += '&' + i + '=' + window.logInfo[i];
            } else {
                logStr += '&' + i + '=' + window.logInfo[i];
            }
            
        }
        (new Image()).src = '/action?' + logStr;
    };
      
    var defaults = {
        msg:'',  // 錯誤的具體資訊
        url:'',  // 錯誤所在的url
        line:'', // 錯誤所在的行
        col:'',  // 錯誤所在的列
        nowTime: '',// 時間
    };
    window.onerror = function(msg,url,line,col,error) {
        col = col || (window.event && window.event.errorCharacter) || 0;

        defaults.url = url;
        defaults.line = line;
        defaults.col =  col;
        defaults.nowTime = new Date().getTime();

        if (error && error.stack){
            // 如果瀏覽器有堆疊資訊,直接使用
            defaults.msg = error.stack.toString();

        }else if (arguments.callee){
            // 嘗試通過callee拿堆疊資訊
            var ext = [];
            var fn = arguments.callee.caller;
            var floor = 3;  
            while (fn && (--floor>0)) {
                ext.push(fn.toString());
                if (fn  === fn.caller) {
                    break;
                }
                fn = fn.caller;
            }
            ext = ext.join(",");
            defaults.msg = error.stack.toString();
        }
        var str = ''
        for(var i in defaults) {
            // console.log(i,defaults[i]);
            if(defaults[i] === null || defaults[i] === undefined) {
                defaults[i] = 'null'; 
            }
            str += '&'+ i + '=' + defaults[i].toString();
        }
        srt = str.replace('&', '').replace('\n','').replace(/\s/g, '');
        (new Image()).src = '/error?' + srt;
    }
複製程式碼

以上就是收集資料的全部,通過傳送/action請求或者是/error請求,這些都是可以自定義的,我講的只是整個過程是如何實現的。

然後通過我的的一個後臺express.js把所有的請求處理並都記錄下來,記錄好後的資料是這樣子的。

user_ip=127.0.0.1&whiteScreenTime=185&readyTime=192&allloadTime=208&mobile=webKit&nowTime=1513071388941
複製程式碼

資料處理

這裡我是通過自己寫的一段指令碼進行解析,parse.js,這裡不具體講解,看原始碼即可。我展現下解析好的資料。

我以cvs的資料格式儲存,因為後面圖表的需要,我也支援json格式方式匯出,只不過後面就需要你自己來配置視覺化的介面了。

資料是這樣的。

charts/csvData/2017-12-16time.csv

時間,白屏時間,使用者可操作時間,總下載時間
1513427051482,137,137,153
1513427065080,470,471,507
1513427080040,127,127,143
1513428714345,274,275,323
1513428733583,267,268,317
1513428743167,268,268,317
1513428754796,276,276,328
複製程式碼

資料展示

這裡我用的是highcharts.js

具體的配置我不進行講解,可以自己到官網進行檢視。

下面是視覺化的圖表,顯示的是每天各個時間段的資訊。

https://user-gold-cdn.xitu.io/2017/12/17/16062ce35649b7e7?w=861&h=1595&f=png&s=83479

介面可能不是特別美觀,還請見諒。

https://user-gold-cdn.xitu.io/2017/12/17/16062ce3539eb419?w=237&h=234&f=gif&s=561618

環境

node >= 6.0.0

redis >= 2.6.0

在這裡我說明下,因為如果這個部署線上上環境的時候,如果每次記錄都進行記錄的話,會消耗大量的記憶體,所以我架設了一層redis,為了防止大流量的衝擊,然後可以每隔一段時間進行儲存。

const express = require('express');
const performance = require('./lib/performance.js');
const app = express();
const router = express.Router();
router.get('/', function (req, res, next) {
  req.url = './index.html';
  next();
});
app.use(router);
app.use(performance({
    time: 10, // 秒為單位
    originalDir: './originalData', // 資料的目錄
    errorDir: './errorData' // 報錯的目錄
}))
app.use(express.static('./'));
const server = app.listen(3000)
複製程式碼

這裡可以設定預設的時間,我這裡以10秒為單位,為了demo的效果起見。一般我採用的是一分鐘進行一次儲存。

程式碼地址:github.com/hua1995116/…

如有好的建議以及優化的方案,還請各位在Issues上提給我。

進階(一個利用監控平臺的實戰慄子)

我利用這個平臺對我的一個專案進行了監控。如果你只是純粹玩的話,還請只閱讀上面的原系統地址,可以忽視我這一段,畢竟我這個系統還不夠完善。

線上demo:www.qiufengh.com/#/

監控demo:qiufengh.com:8080/

專案: github.com/hua1995116/…

在這裡我設定了每過1分鐘記錄一次日誌。

// 監控引入
app.use(performance({
    time: 60, // 秒為單位
    originalDir: './originalData', // 資料的目錄
    errorDir: './errorData' // 報錯的目錄
}))
複製程式碼

以及每隔10分鐘進行一次解析。

function setPrase() {
    setInterval(function(){
        parseData();
      }, 1000 * 60 * 10);
}
複製程式碼

https://user-gold-cdn.xitu.io/2017/12/17/16062ce389311f6b?w=205&h=205&f=gif&s=29900


2017-12-20

本文只是提供一個思路,如果想要專業的,可以使用ELK之類專業的統計分析工具。

相關文章