An important data in front-end monitoring is request data

First, we need to be clear about what information we typically need for a request

The following figure describes what we typically need for request data

So how can we do non-intrusive data collection of business code?

The approach is clear: re-native apis and a layer of proxies over key methods

Now, let’s analyze how to implement it step by step from the perspective of these two apis

xmlHttpRequest

The general method of encapsulation is as follows

// First save a copy of the original properties of the object that need to be encapsulated for later invocation by the proxy
let xhr_open = XMLHttpRequest.prototype.open;
let xhr_send = XMLHttpRequest.prototype.send;
// Step 2, replace the original object properties with proxy objects
XMLHttpRequest.prototype.open = function (. args) {
                // Here we add our own fetch logic
                xhr_open.apply(this, args);
            };
 XMLHttpRequest.prototype.send = function (data) {
                // Here we add our own fetch logic
                xhr_send.apply(this, args);
} 
Copy the code

So what about the onReadyStatechange event

Note: This event must be listened on before the SEND method is sent

We can listen for this event when encapsulating the SEND method

 XMLHttpRequest.prototype.send = function (data) {
                // Add the readyStatechange event
                this.addEventListener('readystatechange'.function () {
                   // The result of the request is processed in response
                });

                xhr_send.call(this, data);
            };
Copy the code

Now that the encapsulation method is complete, how do we capture the information described in the first diagram?

(1) the request information

The open method has a fixed list of arguments: Method, URL, Async, username, password

In the open proxy process, you can get it

XMLHttpRequest.prototype.open = function (. args) {
                this.__monitor_xhr = {
                   method: args[0].url: args[1]
                }
                xhr_open.apply(this, args);
            };
Copy the code

In the code above, we write a new attribute on the current XHR object to hold the information we get. Request the body data, which we get in the analysis below

2. Reponse and Timeline

At this point, we need to process the request results to get the data we want

XMLHttpRequest.prototype.send = function (data) {
                // Record the start time of the request for time calculation
                const startTime = new Date(a);// Add the readyStatechange event
                this.addEventListener('readystatechange'.function () {
                 
                        try {
                            if (this.readyState === XMLHttpRequest.DONE) {
                                // Request end time
                                const endTime = new Date(a);// Request time
                                this.__monitor.duration = (endTime - startTime) ;
                               / / request body
                               this.__monitor.req_body = data;
                               // Get response header, body and other information}}catch (err) {
                           
                        }
                    }
                });

               xhr_send.call(this, data);
            };
Copy the code

The response header information above can be passed

xml.getAllResponseHeaders()
Copy the code

The body information can be obtained in the following ways

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
}
Copy the code

fetch

Fetch because the API and XHR are quite different, FETCH returns a promise object, a encapsulation of this situation

First, we still need to add an agent to fetch, similar to XHR

// First save the original fetch reference
let origFetch = window.fetch
window.fetch =function(fn, t) {
                   
                    // Perform our data collection on this side

                    
                    return origFetch.apply(this, args)
                };
Copy the code

Get request and Response information

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;
                     // Since the parameter list of FETCH is more flexible, the corresponding processing is required
                    if (typeofRequest ! = ='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 is added by default to collect results
                    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)

                                // Send data to the server
                                _reportToServer(fetchData)
                            })
                        })
                        
                        return response;
                    });
                };
Copy the code

The missing step is the timeline data in the first figure, which will be analyzed along with the performance data

Second, what do we do with this data

1. Request data reflects a portion of the user’s trajectory

2. Requesting information can help us locate problems

3. Locate server problems by analyzing the success rate of requests

Third, pay attention

  1. Security, we should be involved in user privacy data desensitization transmission or not transmission

  2. For the data of response, we should intercept the data as needed instead of transferring it all at once to prevent too much invalid data

The above content is just a general sharing of browser request data collection and usage. In addition to browsers, some, such as applets request fetch, are shared in applets information collection

Read the original