Front-end monitoring includes performance monitoring and error monitoring. Monitoring is divided into two parts: data collection and data reporting. This article is mainly about how to collect and report data.

The data collection

Performance Data Collection

Performance data collection requires the window.performance API.

The Performance interface, which is part of the High Resolution Time API, can obtain performance-related information in the current page. It also integrates Performance Timeline API, Navigation Timing API, User Timing API and Resource Timing API.

Can be seen from the MDN document window. Performance. The timing contains the page loads at various stages of start and end time.

These attributes need to be viewed in conjunction with the following figure to better understand:

In order to make it easier for everyone to understand the meaning of various attributes of Timing, I found a netizen’s introduction to Timing in Zhihu and reprint it here.

timing: {
        // The timestamp at the end of unload for a page on the same browser. If there is no previous page, this value will be the same as fetchStart.
	navigationStart: 1543806782096.// Timestamp when the last page Unload event was thrown. If there is no previous page, this value returns 0.
	unloadEventStart: 1543806782523.// Timestamp when the unload event finished processing corresponding to unloadEventStart. If there is no previous page, this value returns 0.
	unloadEventEnd: 1543806782523.// The timestamp when the first HTTP redirect started. If there is no redirection, or a different source in the redirection, this value returns 0.
	redirectStart: 0.// The timestamp when the last HTTP redirection was completed (that is, when the last bit of the HTTP response was received directly).
	// If there is no redirection, or if there is a different source in the redirection, this value returns 0.
	redirectEnd: 0.// The browser is ready to use an HTTP request to fetch the timestamp of the document. This is before any application caches are checked.
	fetchStart: 1543806782096.// The UNIX timestamp when the DNS domain name query started.
        // If a persistent connection is used, or if the information is stored in a cache or local resource, this value will be the same as fetchStart.
	domainLookupStart: 1543806782096.// The time when the DNS domain name query is complete.
	// Equal to the fetchStart value if local caching (that is, no DNS query) or persistent connection is used
	domainLookupEnd: 1543806782096.// HTTP (TCP) Indicates the timestamp when the domain name query ends.
        // If a persistent connection is used, or if the information is stored in a cache or local resource, this value will be the same as fetchStart.
	connectStart: 1543806782099.// HTTP (TCP) returns the timestamp when the connection between the browser and the server was established.
        // If the connection is persistent, the return value is the same as the value of the fetchStart property. Connection establishment means that all handshaking and authentication processes are completed.
	connectEnd: 1543806782227.// HTTPS returns the timestamp when the handshake between the browser and the server started the secure link. Return 0 if the current page does not require a secure connection.
	secureConnectionStart: 1543806782162.// Return the timestamp when the browser made an HTTP request to the server (or started reading the local cache).
	requestStart: 1543806782241.// Return the timestamp when the browser received the first byte from the server (or read it from the local cache).
        // If the transport layer fails after starting the request and the connection is reopened, this property will be counted as the corresponding initiation time for the new request.
	responseStart: 1543806782516.// Return when the browser received the last byte from the server (either read from the local cache or read from the local resource)
        // If the HTTP connection was closed before then, return the timestamp when it was closed.
	responseEnd: 1543806782537.// The timestamp when the current page DOM structure starts parsing (that is, when the document. readyState property changes to "loading" and the corresponding ReadyStatechange event is triggered)
	domLoading: 1543806782573.// The timestamp when the current page DOM structure ends parsing and the embedded resources start loading (that is, when the document. readyState property changes to "interactive" and the corresponding ReadyStatechange event is triggered).
	domInteractive: 1543806783203.// The timestamp when the parser sends the DOMContentLoaded event, which means that all scripts that need to be executed have been parsed.
	domContentLoadedEventStart: 1543806783203.// Timestamp when all scripts that need to be executed immediately have been executed, regardless of order.
	domContentLoadedEventEnd: 1543806783216.// The timestamp when the current Document is parsed. document. readyState becomes 'complete' and the corresponding readyStatechange is triggered
	domComplete: 1543806783796.// Load the timestamp when the event was sent. If the event has not been sent, it will have a value of 0.
	loadEventStart: 1543806783796.// The timestamp when the load event ends. If the event has not been sent or completed, it will have a value of 0.
	loadEventEnd: 1543806783802
}
Copy the code

From the above data, we can get several useful time

// Redirection takes time
redirect: timing.redirectEnd - timing.redirectStart,
// DOM rendering takes time
dom: timing.domComplete - timing.domLoading,
// Page loading time
load: timing.loadEventEnd - timing.navigationStart,
// Page uninstallation time
unload: timing.unloadEventEnd - timing.unloadEventStart,
// The request is time consuming
request: timing.responseEnd - timing.requestStart,
// The current time when performance information is obtained
time: new Date().getTime(),
Copy the code

Another important time is the white-screen time, which is the time from typing in the web address to the beginning of the page.

To get the white-screen time, place the following script in front of .

<script>
    whiteScreen = new Date() - performance.timing.navigationStart
    DomLoading and navigationStart are also available
    whiteScreen = performance.timing.domLoading - performance.timing.navigationStart
</script>
Copy the code

Through these times, you can know how well the page first screen loading performance.

In addition, through the window. The performance. GetEntriesByType (‘ resource ‘) this method, we can get the related resources (js, CSS, img…). It returns all the resources currently loaded on the page.

It generally includes the following types:

  • sciprt
  • link
  • img
  • css
  • fetch
  • other
  • xmlhttprequest

All we need is the following information:

// Name of the resource
name: item.name,
// Resource loading time
duration: item.duration.toFixed(2),
// Resource size
size: item.transferSize,
// Resource protocol
protocol: item.nextHopProtocol,
Copy the code

Now, write a few lines of code to collect this data.

// Collect performance information
const getPerformance = () = > {
    if (!window.performance) return
    const timing = window.performance.timing
    const performance = {
        // Redirection takes time
        redirect: timing.redirectEnd - timing.redirectStart,
        // White screen time
        whiteScreen: whiteScreen,
        // DOM rendering takes time
        dom: timing.domComplete - timing.domLoading,
        // Page loading time
        load: timing.loadEventEnd - timing.navigationStart,
        // Page uninstallation time
        unload: timing.unloadEventEnd - timing.unloadEventStart,
        // The request is time consuming
        request: timing.responseEnd - timing.requestStart,
        // The current time when performance information is obtained
        time: new Date().getTime(),
    }

    return performance
}

// Obtain resource information
const getResources = () = > {
    if (!window.performance) return
    const data = window.performance.getEntriesByType('resource')
    const resource = {
        xmlhttprequest: [].css: [].other: [].script: [].img: [].link: [].fetch: [].// The current time when resource information is obtained
        time: new Date().getTime(),
    }

    data.forEach(item= > {
        const arry = resource[item.initiatorType]
        arry && arry.push({
            // Name of the resource
            name: item.name,
            // Resource loading time
            duration: item.duration.toFixed(2),
            // Resource size
            size: item.transferSize,
            // Resource protocol
            protocol: item.nextHopProtocol,
        })
    })

    return resource
}
Copy the code

summary

Reading the performance and resource information, we can determine the following reasons for slow page loading:

  1. Excessive resources
  2. Speed too slow
  3. Too many DOM elements

In addition to the slow speed of users, we have no way to solve the other two reasons. There are many articles and books on performance optimization on the Internet, and you can find the information if you are interested.

PS: There are other reasons for slow page loading, such as not using on-demand loading, not using CDN, etc. However, the emphasis here is only on the interpretation of performance and resource information to get the cause.

Error data collection

There are three types of errors that can be caught:

  1. Resource loading error. PassedaddEventListener('error', callback, true)Catch a resource load failure error during the capture phase.
  2. Js execution error, passwindow.onerrorCatch JS errors.
  3. Promise error, passedaddEventListener('unhandledrejection', callback)Catch a PROMISE error, but there is no information about the number of rows, the number of columns, and so on. You can only manually throw the error message.

We can create an array variable errors to add information about errors to the array and report them at a certain stage. See the code below for details:

// Capture resource loading failure js CSS img...
addEventListener('error'.e= > {
    const target = e.target
    if(target ! =window) {
        monitor.errors.push({
            type: target.localName,
            url: target.src || target.href,
            msg: (target.src || target.href) + ' is load error'.// The time when the error occurred
            time: new Date().getTime(),
        })
    }
}, true)

// Listen to js error
window.onerror = function(msg, url, row, col, error) {
    monitor.errors.push({
        type: 'javascript'.row: row,
        col: col,
        msg: error && error.stack? error.stack : msg,
        url: url,
        // The time when the error occurred
        time: new Date().getTime(),
    })
}

// The drawback of listening to promise errors is that the number of rows can not be fetched
addEventListener('unhandledrejection'.e= > {
    monitor.errors.push({
        type: 'promise'.msg: (e.reason && e.reason.msg) || e.reason || ' '.// The time when the error occurred
        time: new Date().getTime(),
    })
})
Copy the code

summary

By collecting errors, you can learn about the types and numbers of errors that occur on your site and make adjustments accordingly to reduce the number of errors that occur. The full code and DEMO will be released at the end of this article, so you can copy the code (HTML file) and test it locally.

The data reported

Reporting Performance Data

The performance data can be reported after the page is loaded to minimize the impact on the page performance.

window.onload = () = > {
    // Obtain performance and resource information during idle browser time
    // https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
    if (window.requestIdleCallback) {
        window.requestIdleCallback(() = > {
            monitor.performance = getPerformance()
            monitor.resources = getResources()
        })
    } else {
        setTimeout(() = > {
            monitor.performance = getPerformance()
            monitor.resources = getResources()
        }, 0)}}Copy the code

Of course, you can also set a timer, circular report. However, it is better to do a comparison to report again each time to avoid the same data repeated report.

Error data report

The code I provided in the DEMO uses an errors array to collect all errors and report them uniformly at some stage (delayed reporting).

Instead, you can report errors as they occur. This avoids the “error collection, but the delayed report has not triggered, the user has closed the page, resulting in the loss of error data” problem.

// Listen to js error
window.onerror = function(msg, url, row, col, error) {
    const data = {
        type: 'javascript'.row: row,
        col: col,
        msg: error && error.stack? error.stack : msg,
        url: url,
        // The time when the error occurred
        time: new Date().getTime(),
    }
    
    // Report immediately
    axios.post({ url: 'xxx', data, })
}
Copy the code

As prompted by users, navigator.sendbeacon () can be used for reporting.

window.addEventListener('unload', logData, false);

function logData() {
    navigator.sendBeacon("/log", analyticsData);
}
Copy the code

Its technical features are:

Using the sendBeacon() method enables the user agent (browser) to asynchronously send data to the server when it has the opportunity, without delaying the uninstallation of the page or affecting the loading performance of the next navigation. This solves all the problems with submitting analysis data: the data is reliable, transmitted asynchronously and does not affect the next page loading.

extension

SPA

Window. The performance API is flawed, the SPA routing, switching window. Performance. The timing of the data is not updated. So we need to figure out another way to count the time between switching routes and loading completion. In Vue’s case, one possible approach is to switch routes by using the global pre-guard beforeEach of the routes to get the start time, and executing the VM.$nextTick function in the mounted hook of the component to get the render finish time of the component.

router.beforeEach((to, from, next) = > {
	store.commit('setPageLoadedStartTime'.new Date()})Copy the code
mounted() {
	this.$nextTick(() = > {
		this.$store.commit('setPageLoadedTime'.new Date() - this.$store.state.pageLoadedStartTime)
	})
}
Copy the code

In addition to performance and error monitoring, we can actually collect more information.

Collecting User Information

navigator

Use window.Navigator to collect the user’s device information, operating system, browser information…

UV (Unique Visitor)

Refers to the visitors who browse this web page through the Internet. The same device access is only counted once between 00:00 and 24:00. Only one UV is counted for multiple visits by the same visitor in one day.

When the user visits the website, a random string + time and date can be generated and saved locally. These parameters are passed to the back end, which uses the information to generate UV statistics reports when a request is made to the web page (if more than 24 hours of the day has passed).

PV (Page View)

That is, page views or clicks, each time the user visits each page in the website is recorded 1 PV. The number of times a user visits the same page, a measure of the number of pages visited by a site’s users.

Page stay time

Traditional web site

When the user enters page A, the background request takes the user’s time to enter the page. After 10 minutes, the user enters page B. At this time, the background can judge that the user has stayed in page A for 10 minutes by the parameters conveyed by the interface.

SPA

You can use router to obtain the user’s stay time. Take Vue as an example. The router-beforeeach and Destroyed hook functions are used to obtain the user’s stay time on the route component.

Browse the depth

Through the document. The documentElement. ScrollTop attributes, and height of the screen, you can determine whether the user browsing the web content.

Page Jump Source

The document.referrer property lets you know which site the user is jumping from.

summary

By analyzing user data, we can learn about users’ browsing habits, hobbies, and so on, which is really scary. There is no privacy at all.

DEMO

<! DOCTYPEhtml>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script>
        function monitorInit() {
            const monitor = {
                // Data upload address
                url: ' '.// Performance information
                performance: {},
                // Resource information
                resources: {},
                // Error message
                errors: [].// User information
                user: {
                    // Screen width
                    screen: screen.width,
                    // Screen height
                    height: screen.height,
                    // Browser platform
                    platform: navigator.platform,
                    // User agent information for the browser
                    userAgent: navigator.userAgent,
                    // The language of the browser user interface
                    language: navigator.language,
                },
                // Add the error manually
                addError(error) {
                    const obj = {}
                    const { type, msg, url, row, col } = error
                    if (type) obj.type = type
                    if (msg) obj.msg = msg
                    if (url) obj.url = url
                    if (row) obj.row = row
                    if (col) obj.col = col
                    obj.time = new Date().getTime()
                    monitor.errors.push(obj)
                },
                // Reset the Monitor object
                reset() {
                    window.performance && window.performance.clearResourceTimings()
                    monitor.performance = getPerformance()
                    monitor.resources = getResources()
                    monitor.errors = []
                },
                // Clear the error message
                clearError() {
                    monitor.errors = []
                },
                // Upload monitoring data
                upload() {
                    // Custom upload
                    // axios.post({
                    // url: monitor.url,
                    // data: {
                    // performance,
                    // resources,
                    // errors,
                    // user,
                    / /}
                    // })
                },
                // Set the data upload address
                setURL(url) {
                    monitor.url = url
                },
            }

            // Obtain performance information
            const getPerformance = () = > {
                if (!window.performance) return
                const timing = window.performance.timing
                const performance = {
                    // Redirection takes time
                    redirect: timing.redirectEnd - timing.redirectStart,
                    // White screen time
                    whiteScreen: whiteScreen,
                    // DOM rendering takes time
                    dom: timing.domComplete - timing.domLoading,
                    // Page loading time
                    load: timing.loadEventEnd - timing.navigationStart,
                    // Page uninstallation time
                    unload: timing.unloadEventEnd - timing.unloadEventStart,
                    // The request is time consuming
                    request: timing.responseEnd - timing.requestStart,
                    // The current time when performance information is obtained
                    time: new Date().getTime(),
                }

                return performance
            }

            // Obtain resource information
            const getResources = () = > {
                if (!window.performance) return
                const data = window.performance.getEntriesByType('resource')
                const resource = {
                    xmlhttprequest: [].css: [].other: [].script: [].img: [].link: [].fetch: [].// The current time when resource information is obtained
                    time: new Date().getTime(),
                }

                data.forEach(item= > {
                    const arry = resource[item.initiatorType]
                    arry && arry.push({
                        // Name of the resource
                        name: item.name,
                        // Resource loading time
                        duration: item.duration.toFixed(2),
                        // Resource size
                        size: item.transferSize,
                        // Resource protocol
                        protocol: item.nextHopProtocol,
                    })
                })

                return resource
            }

            window.onload = () = > {
                / / in a browser free time for performance and resource information on https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
                if (window.requestIdleCallback) {
                    window.requestIdleCallback(() = > {
                        monitor.performance = getPerformance()
                        monitor.resources = getResources()
                        console.log('Page Performance Information')
                        console.log(monitor.performance)
                        console.log('Page Resource Information')
                        console.log(monitor.resources)
                    })
                } else {
                    setTimeout(() = > {
                        monitor.performance = getPerformance()
                        monitor.resources = getResources()
                        console.log('Page Performance Information')
                        console.log(monitor.performance)
                        console.log('Page Resource Information')
                        console.log(monitor.resources)
                    }, 0)}}// Capture resource loading failure js CSS img...
            addEventListener('error'.e= > {
                const target = e.target
                if(target ! =window) {
                    monitor.errors.push({
                        type: target.localName,
                        url: target.src || target.href,
                        msg: (target.src || target.href) + ' is load error'.// The time when the error occurred
                        time: new Date().getTime(),
                    })

                    console.log('All error messages')
                    console.log(monitor.errors)
                }
            }, true)

            // Listen to js error
            window.onerror = function(msg, url, row, col, error) {
                monitor.errors.push({
                    type: 'javascript'.// Error type
                    row: row, // The number of lines of code when an error occurred
                    col: col, // The number of code columns when an error occurred
                    msg: error && error.stack? error.stack : msg, // Error message
                    url: url, // Error file
                    time: new Date().getTime(), // The time when the error occurred
                })

                console.log('All error messages')
                console.log(monitor.errors)
            }

            // The drawback of listening to promise errors is that the number of rows can not be fetched
            addEventListener('unhandledrejection'.e= > {
                monitor.errors.push({
                    type: 'promise'.msg: (e.reason && e.reason.msg) || e.reason || ' '.// The time when the error occurred
                    time: new Date().getTime(),
                })

                console.log('All error messages')
                console.log(monitor.errors)
            })

            return monitor
        }

        const monitor = monitorInit()
    </script>
    <link rel="stylesheet" href="test.css">
    <title>Document</title>
</head>
<body>
    <button class="btn1">Error test button 1</button>
    <button class="btn2">Error test button 2</button>
    <button class="btn3">Error test button 3</button>
    <img src="https://avatars3.githubusercontent.com/u/22117876?s=460&v=4" alt="">
    <img src="test.png" alt="">
<script src="192.168.10.15 / test. Js"></script>
<script>
document.querySelector('.btn1').onclick = () = > {
    setTimeout(() = > {
        console.log(button)
    }, 0)}document.querySelector('.btn2').onclick = () = > {
    new Promise((resolve, reject) = > {
        reject({
            msg: 'test.js promise is error'})})}document.querySelector('.btn3').onclick = () = > {
    throw ('This is a manual throw error.')}</script>
</body>
</html>
Copy the code

The resources

  • 7 days to build a front-end performance monitoring system
  • zanePerfor