聊一聊前端的監控

今天我們來聊聊前端的監控

我們爲什麼需要前端監控 ?

爲了獲取用戶行爲以及跟蹤產品在用戶端的使用情況,並以監控數據爲基礎,指明產品優化方向

前端監控分爲三類

  1. 性能項目
  2. 數據監控
  3. 異常監控

性能監控

- 衡量前端的性能的指標是時間

那麼如何監測時間呢, 瀏覽器給我們提供了一個 API performance
來看看裏面都有什麼吧
圖片描述

它的屬性 timing 是一個PerformanceTiming 對象 , 包含了延遲相關的性能,timing裏的屬性基本都是成雙成對的 都是以xxxstart --- xxxend end跟start的時間差就是我們所需要的信息

那麼我們來捋一捋網頁打開到關閉 都有哪些時間戳記錄下來,看一張網頁加載流程圖
圖片描述

首先是navigationStart 就是地址欄開始加載 期間會執行unload 卸載上一個網頁的內容 如果有重定向(同域名),就開始重定向。

fetchStart 抓取開始 頁面開始加載
domainLookupStart dns 解析開始
domainLookupEnd dns 解析結束
connectStart tcp 握手開始
connectEnd 建立連接
requestStart 請求頁面開始
responseStart 響應開始
responseEnd 響應結束
domloading dom開始加載
domInteractive dom結構樹加載完成(src外鏈資源未完成)
domcontentLoaded($(function(){})
domComplete dom 加載完畢 外鏈資源加載完畢
loadevent (window.onload=function(){} 裏面的代碼加載完畢)

監控頁面加載時間

那麼我們開始寫個監控吧 先起一個服務

圖片描述

serve.js

let Koa = require('koa')
let Server = require('koa-static')
let path = require('path')
let app = new Koa()

app.use(Server(path.resolve(__dirname)))

app.listen(3000,function(){
    console.log('Server is running on port 3000')
})

將serve作爲靜態資源目錄 服務起在端口3000

再新建performance.js
performance.js

// 性能監控
let time = ()=>{
    let timing = performance.timing
    let data = {
        prevPage: timing.fetchStart  - timing.navigationStart , // 上一個頁面卸載到新頁面的時間
        redirect: timing.redirectEnd  - timing.redirectStart , // 重定向時長
        dns: timing.domainLookupEnd - timing.domainLookupStart, // dns 解析時長
        tcp: timing.connectEnd - timing.connectStart ,// tcp 鏈接時長
        respone: timing.responseEnd - timing.requestStart, // 響應時長
        ttfb: timing.responseStart - timing.navigationStart, // 首字節接收到 的時長
        domReady:timing.domInteractive - timing.domLoading, // dom 準備時長
        whiteScreen:timing.domLoading - timing.navigationStart, // 白屏時間
        dom:timing.domComplete - timing.domLoading, // dom解析時間
        load:timing.loadEventEnd - timing.loadEventStart,
        total:timing.loadEventEnd - timing.navigationStart
    }
    return data 
}

//  因爲檢測代碼在load裏執行   所以此時load事件未完成  檢測不到load的時間  所以我們需要設置一個定時器來監聽load完成

let timeout = (cb)=>{
    let timer;
    let check = ()=>{
        // 加載完畢後纔有performance.timing.loadEventEnd
        if(performance.timing.loadEventEnd){
            timer = null 
            cb()
        }else{
            timer = setTimeout(check,100)
        }
    }
    window.addEventListener('load',check,false)
}

let domReady = (cb)=>{
    let timer;
    let check = ()=>{
        // performance.timing.domInteractive
        if(performance.timing.domInteractive){
            timer = null 
            cb()
        }else{
            timer = setTimeout(check,100)
        }
    }
    window.addEventListener('DOMContentLoaded',check,false)
}


let _performance = {
    init(cb){
        //有可能domloaded 並未加載好 用戶就關閉網頁了  這種情況也希望監聽到
        domReady(()=>{
            let data = time()
            data.type = 'domReady'
            cb(data)
        })
        // dom完全加載完畢
        timeout(()=>{
            let data = time()
            data.type = 'loaded'
            cb(data)
        })
    }
}

// 通過_performance.init(cb)  獲取到監控數據

_performance.init((data)=>{console.log(data)})

html 引入performance.js

圖片描述
運行結果爲:
圖片描述

然後發送圖片到服務器


// 通過_performance.init(cb)  獲取到監控數據

let formatter = (data)=>{
    let arr = []
    for(key in data){
        arr.push(`${key}=${data[key]}`)
    }
    
    return arr.join('&')

}
_performance.init((data)=>{
    // 然後我們創建一張空圖片把數據發給服務器
 
    let img = new Image()
    img.src = '/p.gif?' +   formatter(data)
})


圖片描述

監控頁面資源加載情況

想要監控資源就得獲取到__proto__ 上的getEntriesByType('resource')方法
獲取到的是個數組 包含了資源請求的時間 名字type 之類的 我們所做的就是拿區我們自己需要的東西

圖片描述
performance.js

//代碼省略
  let resource = performance.getEntriesByType('resource')
    let data = resource.map(_=>{
        return {
            name:_.name,
            initatorType:_.initiatorType,
            duration:_.duration
        }
    })
    cb(data)

ajax請求監控

ajax 請求監控就簡單了
performance.js

// 爲了獲取到我們所需要的參數  我們改寫一下 XMLHttpRequest.prototype.open(method,url,isAsync)


let ajax = {
    init(cb){
        let xhr = window.XMLHttpRequest
        // 保存原來的open方法
        let oldOpen = xhr.prototype.open
        console.log(oldOpen)
        xhr.prototype.open = function(method,url,isAsync = true,...args){
          
            this.info = {method,url,isAsync,message:args}
          
            return oldOpen.apply(this,arguments)
        }
        let oldSend =  xhr.prototype.send
        xhr.prototype.send = function(value){
            let start = Date.now()
            let fn = (type) => () =>{
                this.info.time = Date.now() - start
                this.info.requestSize = value ? value.length : 0
                this.info.responeSize = this.responseText.length
                this.info.type = type
                cb(this.info)
            }
            this.addEventListener('load',fn('success'),false)
            this.addEventListener('error',fn('error'),false)
            this.addEventListener('abort',fn('abort'),false)
            return oldSend.apply(this,arguments)
        }
    }
}
ajax.init((data)=>{
    console.log(data)
})

index.html

    // ajax 請求監控  原生ajax

    var request = new XMLHttpRequest(); // 新建XMLHttpRequest對象

    request.onreadystatechange = function () { // 狀態發生變化時,函數被回調
        if (request.readyState === 4) { // 成功完成
            // 判斷響應結果:
            if (request.status === 200) {
                // 成功,通過responseText拿到響應的文本:
                
            } else {
                // 失敗,根據響應碼判斷失敗原因:
              
            }
        } else {
            // HTTP請求還在繼續...
        }
    }
    
// 發送請求:
request.open('GET', '/api/categories',true,'fet');
request.send();

結果如下
圖片描述

錯誤文件的捕獲

主要是通過window.onerror 事件來捕獲
clipboard.png

let errorCatch = {
    init(cb){
       window.addEventListener('error',function(message, source, lineno, colno, error) {
           this.info = {}
           let stack = error.stack;
           let matchUrl = stack.match(/http:\/\/[^\n]*/)[0]  // 獲取報錯路徑
        
           this.info.filename = matchUrl.match(/http:\/\/(?:\S*)\.js/) ?  matchUrl.match(/http:\/\/(?:\S*)\.js/)[0]:'html' // 獲取報錯文件;
           let [,row,col] = stack.match(/:(\d+):(\d+)/)  // 獲取報錯行 列
           this.info  = {
               message:error.message,
               name:error.name,
               filename:this.info.filename,  //有可能是html裏的js報錯
               row,
               col,    // 真實上線的時候  需要source-map 去找到真正的行跟列
           }
           cb(this.info)
        },true) 
    }
}
errorCatch.init(data=>{console.dir(data)})

clipboard.png

需要值得注意的是
promise 無法用此方法捕獲!! 需要另行監聽另外的事件
另外不同域的js文件不能用此方法捕獲詳細 具體查看https://www.jianshu.com/p/315...

監測用戶行爲

主要方式是在document上監聽事件 通過事件委託獲取事件對象 封裝info 傳給後臺

clipboard.png

clipboard.png

綜上所訴 大致就是這樣 這裏只是簡單的介紹了下 需要深入瞭解的小夥伴請自行尋找相關資料, 比如火焰圖之類的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章