收集請求數據-前端監控之數據收集篇

前端監控中的一項重要數據,就是請求數據html

第一,咱們先要清楚,對於請求,咱們一般須要哪些信息

下圖描述了咱們對於請求數據一般有什麼須要 前端

對於瀏覽器而言, 主要有xmlHttpRequest和fetch兩個請求api(ws暫未考慮),

那麼咱們如何才能作到對業務代碼無侵入式的數據收集呢?json

方法很明確, 就是從新原生的api,在關鍵方法上進行作一層代理小程序

下面, 咱們就從這兩個api的角度, 逐步分析如何實現api

xmlHttpRequest

上圖能夠發現, 主要方法就是open和send, 主要事件爲onreadystatechange, 咱們只須要對這三個屬性進行包裝,就能夠輕鬆獲取xhr相關的消息

通常封裝的方法以下promise

// 首先將須要封裝對象的原屬性保存一個副本,用於代理以後調用
let xhr_open = XMLHttpRequest.prototype.open;
let xhr_send = XMLHttpRequest.prototype.send;
// 第二步,將原對象屬性替換成代理對象 
XMLHttpRequest.prototype.open = function (...args) {
                // 在這裏,咱們加入本身的獲取邏輯
                xhr_open.apply(this, args);
            };
 XMLHttpRequest.prototype.send = function (data) {
                // 在這裏,咱們加入本身的獲取邏輯
                xhr_send.apply(this, args);
} 
複製代碼

那麼onreadystatechange事件如何處理呢瀏覽器

注意點: 該事件的監聽須要在send方法發送以前 安全

咱們能夠在封裝send方法時,加入該事件的監聽服務器

XMLHttpRequest.prototype.send = function (data) {
                // 添加 readystatechange 事件
                this.addEventListener('readystatechange', function () {
                   // 對請求結果作響應的處理
                });

                xhr_send.call(this, data);
            };
複製代碼

封裝方法已經完成了, 那咱們該如何獲取到第一張圖 描述的信息呢?app

① request信息

open方法的參數列表是固定的, 依次是method,url, async, username, password

在open代理過程當中,獲取便可

XMLHttpRequest.prototype.open = function (...args) {
                this.__monitor_xhr = {
                   method: args[0],
                   url: args[1]
                }
                xhr_open.apply(this, args);
            };
複製代碼

上面代碼中,咱們在當前xhr對象上寫入了一個新的屬性,用於保存咱們獲取到的信息。 請求body的數據,咱們在下面的分析中獲取

② reponse 和 timeline信息

這時候,咱們就須要對請求結果進行處理,獲取咱們想要的數據

XMLHttpRequest.prototype.send = function (data) {
                // 記錄請求開始時間,用於計算耗時
                const startTime = new Date();

                // 添加 readystatechange 事件
                this.addEventListener('readystatechange', function () {
                 
                        try {
                            if (this.readyState === XMLHttpRequest.DONE) {
                                // 請求結束時間
                                const endTime = new Date();
                                // 請求耗時
                                this.__monitor.duration = (endTime - startTime) ;
                               // 請求body
                               this.__monitor.req_body = data;
                               // 獲取response header、body等信息

                            }
                        } catch (err) {
                           
                        }
                    }
                });

               xhr_send.call(this, data);
            };
複製代碼

上述response header信息,能夠經過

xml.getAllResponseHeaders()
複製代碼

獲取, body信息能夠經過如下方法獲取

function getBody (xhrObject) {
    var body = void 0;

    // IE 11 sometimes throws when trying to access a large responses:
    // https://connect.microsoft.com/IE/Feedback/Details/1053110
    // gte IE10 will support responseType
    try {
        switch (xhrObject.responseType) {
            case 'json':
            case 'arraybuffer':
            case 'blob':
            {
                body = xhrObject.response;
                break;
            }
            case 'document':
            {
                body = xhrObject.responseXML;
                break;
            }
            case 'text':
            case '':
            {
                body = xhrObject.responseText;
                break;
            }
            default:
            {
                body = '';
            }
        }
        // When requesting binary data, IE6-9 will throw an exception
        // on any attempt to access responseText (#11426)
        if (!body && typeof xhrObject.responseText === "string" ) {
            body = xhrObject.responseText;
        }
    } catch (err) {
        body = 'monitor: Error accessing response.';
    }
    return body
}
複製代碼

fetch

fetch因爲api和xhr有很大差別, fetch返回了promise對象, 這種狀況的封裝

首先,咱們仍是須要對fetch加個代理, 方式相似xhr

// 首先保存原先的fetch 引用
let origFetch = window.fetch
window.fetch =function(fn, t) {
                   
                    // 這邊執行咱們的數據收集工做

                    
                    return origFetch.apply(this, args)
                };
複製代碼

獲取request 和response信息

window.fetch =function(fn, t) {
                   
                    var args = new Array(arguments.length);
                    for (var i = 0; i < args.length; ++i) {
                        args[i] = arguments[i];
                    }

                    var p = null;
                     // 因爲fetch的參數列表更靈活, 因此須要對應的處理
                    if (typeof Request !== 'undefined' && args[0] instanceof Request) {
                        p = args[0].clone().text().then(function (body) {
                            return utils.extendsObjects({}, pluckFetchFields(args[0]), {
                                body: body
                            });
                        });
                    } else {
                        p = Promise.resolve(utils.extendsObjects({}, pluckFetchFields(args[1]), {
                            url: '' + args[0],
                            body: (args[1] || {}).body
                        }));
                    }

                    var fetchData = {
                        method: '',
                        url: '',
                        status_code: null,
                        start_time: new Date().getTime(),
                        request:{
                            headers: {},
                            body: ''
                        },
                        response:{
                            headers: {},
                            body: ''
                        },
                        timeline:{
                            dns:0,
                            connect:0,
                            response:0,
                            request: 0,
                            duration: 0
                        }
                    };

                    // 此處默認加一個then,對結果進行收集處理
                    return origFetch.apply(this, args).then(function(response) {
                        fetchData.status_code = response.status;
                        fetchData.duration = new Date().getTime() - fetchData.start_time
                        fetchData.timeline.duration = fetchData.duration
                        p.then(function(req) {
                            fetchData.method = req.method
                            fetchData.url = req.url
                            utils.objectMerge(fetchData.request, {mode: req.mode, referrer: req.referrer, credentials: req.credentials, headers: req.headers, body: req.body})
                            var clonedText = null;
                            try {
                               
                                clonedText = response.clone().text();
                            } catch (err) {
                                // safari has a bug where cloning can fail
                                clonedText = Promise.resolve('Monitor fetch error: ' + err.message);
                            }
                            clonedText.then(function(body) {
                                fetchData.response.body = body
                                fetchData.response.headers = makeObjectFromHeaders(response.headers)

                                // 將數據發送到服務器
                                _reportToServer(fetchData)
                            })
                        })
                        
                        return response;
                    });
                };
複製代碼

以上內容,咱們還缺乏了一步,那就是第一張圖中的timeline數據, 該部分將和性能數據一塊兒分析

第二,對於這些數據,咱們又用來作些什麼

1. 請求數據反映了用戶軌跡的一部分

2. 請求信息能夠幫助咱們定位問題

3. 經過分析請求的成功率,及時發現服務端問題

第三, 注意點

  1. 安全, 咱們應該對涉及用戶隱私的數據進行脫敏傳輸 或 不傳輸

  2. 對於response的數據, 咱們應該有須要的截取, 而不是一股腦的傳輸,防止無效數據太多

以上內容只是對瀏覽器請求數據收集和使用作了個大概的分享,除了瀏覽器外, 還有一些,如小程序的請求獲取,會在小程序信息收集中分享

閱讀原文

相關文章
相關標籤/搜索