More articles
Preface
These days, there has been a lot of excitement to learn about front-end monitoring, but after looking at a lot of data, I found that there are no detailed articles about front-end monitoring, they are all about general, on the contrary, there are many existing front-end monitoring tools.
In order to learn more about the technical principles of front-end monitoring, I have been consulting the relevant data these days.Now I'm going to write an article detailing front-end monitoring to summarize my research over the past few days (and so on).
//Front End Monitoring Process Data Collection-->Data Update-->Server-side Processing-->Database Storage-->Data Monitoring Visualization Platform
However, this article only talks about the two steps of data collection and data reporting in monitoring, and the follow-up process requires the reader to explore and explore by himself (which is also a pleasure).
data acquisition
Performance Data Acquisition
Let's first learn about the Web API window.performance .
The PerformanceInterface provides access to performance-related information on the current page as part of the High Resolution Time API, which also incorporates the Performance Timeline API, Navigation Timing API, User Timing API, and Resource Timing API.
The property timing of this API contains the start and end times of each phase of page loading.
To make it easier for you to understand the meaning of timing's attributes, I know that I found a friend who wrote an introduction to timing, which I would like to reproduce here.
timing: { // Timestamp at the end of a page unload on the same browser.If there is no previous page, this value will be the same as fetchStart. navigationStart: 1543806782096, // Timestamp of the last page unload event thrown.If there is no previous page, this value returns 0. unloadEventStart: 1543806782523, // Corresponding to unloadEventStart, the timestamp at the end of unload event processing.If there is no previous page, this value returns 0. unloadEventEnd: 1543806782523, // Timestamp at the beginning of the first HTTP redirection.This value returns 0 if there is no redirection, or if there is a different source in the redirection. redirectStart: 0, // The timestamp of the last HTTP redirection (that is, the time when the last bit of the HTTP response was received directly). // If there is no redirection or 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 point in time occurs before any application cache is checked. fetchStart: 1543806782096, // UNIX timestamp at the beginning of DNS domain name query. //If a persistent connection is used, or this information is stored on a cache or local resource, this value will match fetchStart. domainLookupStart: 1543806782096, // DNS domain name query completion time. //If a local cache (i.e. no DNS query) or persistent connection is used, it is equal to the fetchStart value domainLookupEnd: 1543806782096, // Timestamp for end of HTTP (TCP) domain name query. //If a persistent connection is used, or this information is stored on a cache or local resource, this value will match fetchStart. connectStart: 1543806782099, // HTTP (TCP) returns the timestamp at which the connection between the browser and the server was established. // If a persistent connection is established, the return value is equal to the value of the fetchStart property.Connection establishment refers to the complete completion of all handshakes and authentication processes. connectEnd: 1543806782227, // HTTPS returns the timestamp of the browser's handshake when it begins a secure link with the server.Returns 0 if the current page does not require a secure connection. secureConnectionStart: 1543806782162, // Returns the timestamp of the browser when it makes an HTTP request to the server (or when it starts reading the local cache). requestStart: 1543806782241, // Returns the timestamp of the first byte that the browser receives from the server (or reads from the local cache). //If the transport layer fails after starting the request and the connection is restarted, this property will be counted as the corresponding start time for the new request. responseStart: 1543806782516, // Returns the last byte the browser received from the server (or read from the local cache, or read from local resources) //(If the HTTP connection was closed before then, the time stamp when it was closed is returned). responseEnd: 1543806782537, // The timestamp of the beginning of the parsing of the DOM structure of the current Web page (that is, when the Document.readyState property becomes "load" and the corresponding readystatechange event is triggered). domLoading: 1543806782573, // The timestamp of the end of parsing the current page DOM structure and the start of loading embedded resources when the Document.readyState property becomes interactive and the corresponding readystatechange event triggers. domInteractive: 1543806783203, // When the parser sends the DOMContentLoaded event, the timestamp when 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 execution order. domContentLoadedEventEnd: 1543806783216, // The current document parsing is complete, that is, the timestamp when the Document.readyState becomes'complete'and the corresponding readystatechange is triggered domComplete: 1543806783796, // Timestamp when the load event was sent.If this event has not yet been sent, its value will be 0. loadEventStart: 1543806783796, // When the load event ends, the timestamp when the load event completes.If this event has not yet been sent or completed, its value will be 0. loadEventEnd: 1543806783802 }
From the above data, we can get some useful time
// Redirection time-consuming redirect: timing.redirectEnd - timing.redirectStart, // White Screen Time whiteScreen: timing.responseStart - timing.navigationStart, // DOM Rendering Time-consuming dom: timing.domComplete - timing.domLoading, // Page Loading Time-consuming load: timing.loadEventEnd - timing.navigationStart, // Page Uninstall Time-consuming unload: timing.unloadEventEnd - timing.unloadEventStart, // Request Time-consuming request: timing.responseEnd - timing.requestStart, // Get Performance Information Current Time time: new Date().getTime(),
In a few moments, you can see how well the first screen of a page loads.
In addition, using the window.performance.getEntriesByType('resource') method, we can also get the load time of related resources (js, css, img...), which will return all the resources currently loaded on the page.
It generally includes the following types
- sciprt
- link
- img
- css
- fetch
- other
- xmlhttprequest
We only need the following information
// Name of the resource name: item.name, // Resource Loading Time-consuming duration: item.duration.toFixed(2), // Resource Size size: item.transferSize, // Protocols used for resources protocol: item.nextHopProtocol,
Now, write a few lines of code to collect the data.
// Collect performance information const getPerformance = () => { if (!window.performance) return const timing = window.performance.timing const performance = { // Redirection time-consuming redirect: timing.redirectEnd - timing.redirectStart, // White Screen Time whiteScreen: timing.responseStart - timing.navigationStart, // DOM Rendering Time-consuming dom: timing.domComplete - timing.domLoading, // Page Loading Time-consuming load: timing.loadEventEnd - timing.navigationStart, // Page Uninstall Time-consuming unload: timing.unloadEventEnd - timing.unloadEventStart, // Request Time-consuming request: timing.responseEnd - timing.requestStart, // Get Performance Information Current Time time: new Date().getTime(), } return performance } // Get resource information const getResources = () => { if (!window.performance) return const data = window.performance.getEntriesByType('resource') const resource = { xmlhttprequest: [], css: [], other: [], script: [], img: [], link: [], fetch: [], // Current time to get resource information 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-consuming duration: item.duration.toFixed(2), // Resource Size size: item.transferSize, // Protocols used for resources protocol: item.nextHopProtocol, }) }) return resource }
Summary
By interpreting performance and resource information, we can see that there are several reasons for the slow page loading:
- Too many resources
- Network speed is too slow
- Too many DOM elements
In addition to the slow speed of the user network, we can not, there are two other reasons that can be solved. There are already many performance-optimized articles and books on the network, and we are interested in finding out the information on our own.
Anomaly data collection
There are three types of exceptions/errors you can catch by now, as you can see from your data searches over the past few days.
- Resource Load Error
- js execution error
- promise error
1 Use addEventListener('error', callback, true) to catch resource load failure errors during capture phase.
2 Use window.onerror to catch js errors.
3 Use addEventListener('unhandledrejection', callback) to catch promise errors, but there is no information such as the number of rows and columns where errors occur, only relevant error information can be thrown manually.
We can build an error array variable errors, when an error occurs, add information about the error to the array, and then report it uniformly at a certain stage. See the code for details.
// Capture resource load failure error 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', // When the error occurred time: new Date().getTime(), }) } }, true) // Listen for js errors 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, // When the error occurred time: new Date().getTime(), }) } // The disadvantage of listening for promise errors is that row count data is not available addEventListener('unhandledrejection', e => { monitor.errors.push({ type: 'promise', msg: (e.reason && e.reason.msg) || e.reason || '', // When the error occurred time: new Date().getTime(), }) })
Summary
By collecting errors, you can learn the type and number of site exceptions, so you can make adjustments to reduce them.
Complete code and DEMO will be released at the end of the article, so you can copy the code (HTML file) and test it locally.
Data Update
Performance data reporting
Performance data can be reported after the page is loaded, with minimal impact on page performance.
window.onload = () => { // Get performance and resource information when the browser is idle // 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) } }
Of course, you can also set a timer to circulate the report.However, it is better to make a comparison and re-report each time to avoid duplicate reporting of the same data.
Exception data reporting
The code I provided in DEMO is to collect all exceptions using an errors array and report them uniformly at a certain stage (delayed reporting).
In fact, you can also report an error when it occurs (instant report).This avoids the problem of losing anomalous data when the report is not triggered after the anomaly has been collected and the user has turned off the web page.
// Listen for js errors 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, // When the error occurred time: new Date().getTime(), } // Instant Update axios.post({ url: 'xxx', data, }) }
extend
SPA
The window.performance API has a disadvantage that when SPA switches routes, the window.performance.timing data is not updated.
So we need to find another way to count how long the switch routes to load complete.
For example, with Vue, one possible way is to execute the vm.$nextTick function in the component's beforeCreate hook to get the full rendering time of the component when switching routes.
beforeCreate() { const time = new Date().getTime() this.$nextTick(() => { this.$store.commit('setPageLoadedTime', new Date().getTime() - time) }) }
In addition to performance and exception monitoring, we can actually do more.
User Information Collection
navigator
Use window.navigator to collect user's device information, operating system, browser information...
UV(Unique visitor)
Refers to the natural person who visits and browses this web page through the Internet.A computer client accessing your website is a visitor.The same client is calculated only once within 00:00-24:00.Multiple visits to the same visitor within a day only count one UV.
When a user visits a website, a random string + time date can be generated and saved locally.When a request occurs on a Web page (regenerated if it exceeds 24 hours that day), these parameters are passed to the back end, which uses the information to generate UV statistics reports.
PV(Page View)
That is, page views or clicks, each time a user visits each page in the site, a PV is recorded.The cumulative number of times a user visits the same page, which measures the number of pages visited by site users.
Page dwell time
Traditional Web Site
When a user enters page A, he or she picks up the time when he or she enters the page through a background request.After 10 minutes, the user enters page B, at which point the background can tell by the parameters carried by the interface that the user stayed on page A for 10 minutes.
SPA
Router can be used to get the user's dwell time. Take Vue for example, and use the router.beforeEach destroyed hook function to get the user's dwell time on the routing component.
Browse Depth
Using the document.documentElement.scrollTop property and the screen height, you can determine whether the user has finished browsing the site content.
Page Jump Source
The document.referrer property lets you know from which website the user jumped.
Summary
By analyzing the user data, we can know the user's browsing habits, hobbies and so on. It's horrible to think about, there is no privacy at all.
DEMO
<!DOCTYPE html> <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"> <link rel="stylesheet" href="test.css"> <title>Document</title> </head> <body> <button class="btn1">Exception Test Button 1</button> <button class="btn2">Exception Test Button 2</button> <button class="btn3">Exception Test Button 3</button> <img src="https://avatars3.githubusercontent.com/u/22117876?s=460&v=4" alt=""> <img src="test.png" alt=""> <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, // Browser User Agent Information userAgent: navigator.userAgent, // Language of browser user interface language: navigator.language, }, // Add errors 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 monitor object reset() { window.performance && window.performance.clearResourceTimings() monitor.performance = getPerformance() monitor.resources = getResources() monitor.errors = [] }, // Empty error information clearError() { monitor.errors = [] }, // Upload monitoring data upload() { // Custom Upload // axios.post({ // url: monitor.url, // data: { // performance, // resources, // errors, // user, // } // }) }, // Set data upload address setURL(url) { monitor.url = url }, } // Get performance information const getPerformance = () => { if (!window.performance) return const timing = window.performance.timing const performance = { // Redirection time-consuming redirect: timing.redirectEnd - timing.redirectStart, // White Screen Time whiteScreen: timing.responseStart - timing.navigationStart, // DOM Rendering Time-consuming dom: timing.domComplete - timing.domLoading, // Page Loading Time-consuming load: timing.loadEventEnd - timing.navigationStart, // Page Uninstall Time-consuming unload: timing.unloadEventEnd - timing.unloadEventStart, // Request Time-consuming request: timing.responseEnd - timing.requestStart, // Get Performance Information Current Time time: new Date().getTime(), } return performance } // Get resource information const getResources = () => { if (!window.performance) return const data = window.performance.getEntriesByType('resource') const resource = { xmlhttprequest: [], css: [], other: [], script: [], img: [], link: [], fetch: [], // Current time to get resource information 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-consuming duration: item.duration.toFixed(2), // Resource Size size: item.transferSize, // Protocols used for resources protocol: item.nextHopProtocol, }) }) return resource } window.onload = () => { // Get performance and resource information at browser idle time 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 load failure error 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', // When the error occurred time: new Date().getTime(), }) console.log('All error messages') console.log(monitor.errors) } }, true) // Listen for js errors window.onerror = function(msg, url, row, col, error) { monitor.errors.push({ type: 'javascript', // Error Type row: row, // Number of lines of code in case of errors col: col, // Number of code columns in case of errors msg: error && error.stack? error.stack : msg, // error message url: url, // wrong file time: new Date().getTime(), // When the error occurred }) console.log('All error messages') console.log(monitor.errors) } // The disadvantage of listening for promise errors is that row count data is not available addEventListener('unhandledrejection', e => { monitor.errors.push({ type: 'promise', msg: (e.reason && e.reason.msg) || e.reason || '', // When the error occurred time: new Date().getTime(), }) console.log('All error messages') console.log(monitor.errors) }) return monitor } const monitor = monitorInit() </script> <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 manually thrown error') } </script> </body> </html>
Reference material
https://fex.baidu.com/blog/20...
https://github.com/wangweiang...