How to realize front-end monitoring
Front-end monitoring project, our primary purpose is to detect errors in the page and report them. Generally, we can think of error sources mainly include JavaScript errors, static resource errors, Promise exceptions, Ajax request exceptions, as well as exceptions of Vue, React and other frameworks.
JavaScript abnormal
Static resource exceptions and JavaScript exceptions both throw errors, so we can catch them via addEventListener(‘error’) or window.onerror. However, the addEventListener does not get the detailed stack information returned by JavaScript exceptions, so we can simply catch them with window.onError. However, static resource exception Error events do not bubble up to the window, so we can only use addEventListener to block static resource errors in the capture phase rather than the bubble phase.
This makes it clear that static resource exceptions are handled by addEventListener(Handler, true) and JavaScript errors are handled by window.onerror. JavaScript exceptions are caught by both, so we also need to strip this type out during the addEventListener capture phase to make it through the bubble phase.
Reserving only resource loading errors can be done as follows:
window.addEventListener('error'.(event) = >{
let target = event.target
var isElementTarget = target instanceof HTMLScriptElement || target instanceof HTMLLinkElement || target instanceof HTMLImageElement;
if(isElementTarget){
...
}
Copy the code
Ajax abnormal
We all know that Ajax is implemented by constructing an instance of XMLHttpRequest. A typical POST request looks like this
let xhr = new XMLHttpRequest();
xhr.open("POST", url, true); xhr.setRequestHeader(..) ; xhr.send(JSON.stringify(defaults.data));// Send takes the request body;
xhr.onreadystatechange = function () {
if (xmlhttp.readyState==4 && xmlhttp.status==200)
success(JSON.parse(xhr.responseText))
}
Copy the code
You can see that a full XHR request calls the.open(),.send() methods on the prototype, so we just need to rewrite the function to add a listener and execute the old method at the end
let origin = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
this.addEventListener('error', _handleEvent);
this.addEventListener('load', _handleEvent);
this.addEventListener('abort', _handleEvent);
return origin.apply(this.arguments)};Copy the code
Hash to heavy
Imagine you have a piece of code in your project,
var obj = {}
while(obj.num++){
...
}
Copy the code
Then the monitoring system will continuously report exactly the same information, so hash removal is necessary. We add hash and amount fields inside the error object, then simply call the MD5 library, enter the error description + the error URL and print a short summary hash. If there is an object in the current error queue with the hash value, add one to its amount, and otherwise queue the object.
Performance testing
The Performance Timing API is used for Performance detection. Performance is an object of window.
var performance = {
timing: {
navigationStart: 1441112691935.// The time when the callback of the load event completes
loadEventEnd: 1441112693215}};Copy the code
Performance. Timing Has a large number of load times inside the object, and subtracting the load times of some content can be obtained
Traffic statistics
Visitors generally have PV, UV, IP three kinds, we mainly focus on the first two kinds — PV means Page Visitor, Page click once count a visit; UV stands for Unique Visitor, where the same client counts only one visit in a day. As you can see, PV is relatively simple, while UV requires browser identification — we use localStorage to generate a random string mark_uv to identify the user when the user first enters the page, and a mark_time to record its generation time. If date.now () is found to be the day after mark_time in one of the fires, we generate a brand new mark_UV again. PV and UV statistics are thus completed.
Class structure design
Identify current needs:
- Page error exists
ajaxError, jsError, promiseError, resourceError, vueError
And they have common data and methods, so they are designed as classes - Error message queue, error data reporting is the above page error shared data area and method, page error and a large number of similar fields belong to their own, so design a base class
Base
And base classstatic
Method to implement - There is no complex relationship between performance and device listening
Design classes:
To sum up, an entry class is required to receive the configuration and construct the above three, so the Monitor class is designed to uniformly register the timing of monitoring errors and triggering reporting, as shown in the UML diagram below
class Monitor {
constructor(options) {
// If the configuration exists, register to listen for such errors
options.ajaxError && new AjaxError(options).registerError();
options.jsError && newjsError(options).registerError(); .// Data is reported when the page is closed
window.addEventListener("beforeunload".() = > {
newPerfomance(options).record(); }); }}export default Monitor;
Copy the code
Packaging and publishing
Clear function calls and split files
First of all, the point of webpack is to make it easier for script tags to be imported directly into index.min.js. For front-end projects that support ES6 or AMD specifications, we can use import, require directly. So:
- If THE ES6, AMD specification is introduced, no additional processing is required
- If script tags are introduced, additional interfaces need to be exposed to Global, so the Webpack entry file needs to be split
To achieve the latter, we create a separate entry.js file to expose the external interface. This does not affect the former global variable and creates an entry point for webpack packaging:
// entry.js
import Monitor from './index'
window.Monitor = Monitor;
Copy the code
Webpack packaging
To generate the index.min.js file, we first add a quick call script for package.json
"scripts": {
"pack": "npx webpack --config ./monitor/webpack.config.js"
},
Copy the code
Then write webpack.config.js
const path = require("path");
module.exports = {
// Compress the script in build mode
mode: "production".entry: "./monitor/entry.js".output: {
// filename: specifies the name of the packed JS file
filename: "index.min.js".//path: specifies where to put the packed files
path: path.resolve(__dirname, ""),},...Copy the code
Finally, execute the NPM run pack script to get the packaged single JS file, which can be easily referenced through the CDN:
<script src="< https://cdn.vansin.top/jssdk-0.1.2.min.js >"></script>
Copy the code
NPM release
Once packaged, we can publish to NPM,
- Let’s start with the root directory
package.json
.
{
"name": "byte-monitor-jssdk"./ / name
"version": "0.1.0 from"."private": false."description": "Monitor errors in front-end"./ / description
"main": "./monitor/dist/index.js"."readmeFilename": "./monitor/README.md"."dependencies": {
"js-md5": "^ 0.7.3"},...Copy the code
The main field is important — it’s used to reference the NPM package to find the entry file from node_modules, so just specify the entry index.js in the directory
- NPM packages have the concept of versioning, but
index.min.js
There is no version record information of its own, and because NPM mirroring can have delays, packages that are released will not be downloaded immediately, so it is important to identify issues and differences between versions – we resolve this by carrying the version number in the file name when packaging, for exampleindex.min.js
Name toJSSDK - 0.1.2. Min. Js
const {version} = require('.. /package.json')
module.exports = {
...
output: {
filename: `jssdk-${version}.min.js`,},Copy the code
- create
.npmignore
Files, configuration and Github.gitignore
Similarly, the specified file is ignored when publishing - write
README.md
And then after logging innpm publish
Completes the