The last article briefly looked at some of the common front-end exceptions, and their respective scenarios. This is the second article on front-end monitoring, which focuses on the general need to use those technologies. The next article is about completing an actual SDK
Why do front-end monitoring
Quickly locate online problems, optimize the online product experience, and catch some customer problems that cannot be reproduced due to special circumstances
The functionality we’re going to implement
- 1) Back-end interface abnormality monitoring
For example, an interface error occurs500
or503
- 2) Front-end page display error (resource file abnormal monitoring)
Page image or somethingjs
Loading failed or resources could not be found404
For example, an image is not supported across domains503
- 3) Front-end code errors Monitor errors caused by internal code logic errors of a function
- 4) Support closed browser can still collect error information
- 5) support
source-map
Locate source code errors - 6) Support custom description of interface error messages
Simple prototypes you draw yourself
Modao. Cc/app / 791 e27d…
These requirements are enough to meet the basic needs of front-end monitoring, when people if they want to do complex words, but also need the most UV indicators, we will not do here, in fact, these are very simple, just add some data dimension indicators
1.) Handle the error part of the business code
You have to know the difference before you start
/* * @param {String} : error message * @param url{String} : error page url * @param line{Number} : error line */
window.onerror = function(msg, url, line){
return true;
}
Copy the code
Features:
- Can catch JS execution errors, can not catch tag elements with SRC loading errors.
- Parameter corresponds to 5 values (error message, file, row, column, error message)
- Return true in the function body prevents the exception message from being printed to the console
//event{} Error object
window.addEventListener("error".function(event){
event.preventDefault();
},true);
Copy the code
Features:
- Is the capture state (the third parameter is
true
) can be capturedjs
Execution errors can also be caught withsrc
Error loading tag element for. - Is the bubble state (the third parameter is
false
) can be capturedjs
Execution error, cannot catch withsrc
Error loading tag element for. - Parameter corresponds to 1 value, exception events, error messages are inside
- Function in vivo
preventDefault
You can prevent exception messages from being printed to the console
Exception caught
The network resource type is abnormal
// Business code type is abnormal
So:
Because window.addeventListener (“error”) can not only listen for code level exceptions, but also monitor static resource exceptions, so we must use window.addeventListener (“error”) to do the processing
The exception caught by window.addeventListener looks like this,
How to monitor front-end interfaces
Since we are making SDK, of course, we need to consider the compatibility of SDK technology. However, whether the front-end uses Vue, React or Angular, the unified data request part is ajax. No matter what third party library is used, the bottom layer is ajax, so we need to monitor the abnormality of interface level. We just need to intercept the Ajax request
Ajax-hook, a library that intercepts Ajax requests
Ajax-hook is a nifty library for intercepting the browser’s XMLHttpRequest, allowing it to process the XMLHttpRequest object before it initiates the request and after it receives the response. It allows you to do some low-level pre-processing of requests and responses.
The installation
yarn add ajax-hook
The general usage is as follows:
import {proxy} from "ajax-hook"
function onAddError() {
// ...
// Store abnormal data
// Remember to store local cache first and then report according to your own reporting rules
// Write only pseudo code, the next article concrete implementation
saveError();
}
function saveError(){
}
proxy({
//XMLHttpRequest is entered before the request is initiated
onRequest: (config, handler) = > {
handler.next(config);
},
//XMLHttpRequest is entered when an error occurs in the request, such as timeout; Note that HTTP status code errors are not included,
// A 404 is still considered successful
onError: (err, handler) = > {
onAddError(err); // The storage is abnormal
handler.next(err)
},
//XMLHttpRequest is entered after the request succeeds. The response contains the request parameters and response result
onResponse: (response, handler) = > {
onAddError(err); // The storage is abnormal
handler.next(response)
}
})
Copy the code
The exception caught looks like this
We now block all web requests made with XMLHttpRequest in the browser! Before the request is initiated, the onRequest hook is first entered, and handler.next(config) is called to continue the request. If the request succeeds, the onResponse hook is entered, and if the request fails, onError is entered
If you need to catch WebSocket exceptions, you need to throw an exception in WebSocket onError or rewrite the WebSocket class. Other similar WebWorker and postMessage exceptions also need to do exception listening. Or when they do, throw a custom exception event in the actual exception function, and the SDK catches the event
Another is that the front end is now basically using promises, so it is necessary to do independent monitoring of the exceptions of promises
window.addEventListener("unhandledrejection".event= > {
console.log(`error: ${event}`);
});
Copy the code
2.) Abnormal data transfer part
Now that the function of the exception monitoring part is complete, what we need to do is to report the captured exception to the database, how to ensure that the browser closed, stored exception data can also report success?
navigator.sendBeacon
The navigator.sendbeacon () method can be used to asynchronously transfer a small amount of data to a Web server over HTTP.
- Data is reliable and browser shutdown requests can still be sent
- Asynchronous execution does not affect the next page load
- It does not delay page unloading or affect the loading performance of the next navigation
API
Using a simple
Browser compatibility is optimistic
use
// Browser uninstall event
window.addEventListener('unload',logData,false);
function logData(){
Error message reported
navigator.sendBeacon("http://npmhook/api/sendLog", {/ / parameters
});
}
Copy the code
If the browser does not support navigator. SendBeacon, send the data by creating an Image tag
const img = new Image();
img.onload =() = >{}
img.src = `http://npmhook/api/sendLog? Data = 'data' `;
Copy the code
3.) Data to be collected
{
userId:"Account ID ID".url:"Current page URL".browser:"Owning browser".version:"Browser Version".system:"Owning System".referer:"Entry page".jsPath:"Abnormal JS file path".errorObj:"Exception error message JSON string".connection:"Network Connection condition".// Additional arguments passed in
}
Copy the code
4.) SDK writing (concise version)
import {proxy} from "ajax-hook";
/*** exception class */
function HookErrorSdk(config){
this.api="http://127.0.0.1:4002";
this.formObj=config.formObj||{};
this.userId=config.userId;
this.appkey=config.appkey;
this.httpCodeMap=config.httpCodeMap||{},
this.AppErrorList=[];
this.connection=navigator.connection
|| navigator.mozConnection
|| navigator.webkitConnection;
/* Send reported data * @param param object Error object */
this.ToString=function(param){
return JSON.stringify(param);
}
/* Assemble ajax exceptions * @param Event Object error object */
this.getAjaxError=function(event){
/ /... Handle custom errors with httpCodeMap
return {
jsPath:event.filename,
errorObj:JSON.stringify({
message:event.error.message,
stack:event.error.stack
})
}
}
* @param Event Object Error object * @param Type String Error type */
this.getErrorObj=function(event,type){
const errObj=type==="ajax"?
this.getAjaxError(event)
:this.getCodeError(event);
return Object.assign({
userId:this.userId,
url:document.location.href,
browser:navigator.userAgent,
version:navigator.appVersion,
system:navigator.platform,
referer:document.referrer,
connection:this.connection.type
},errObj)
}
/* Collect exceptions * @param Event Object Error object */
this.getCodeError=function(event){
return {
jsPath:event.filename,
errorObj:this.ToString({
message:event.error.message,
stack:event.error.stack
})
}
}
/* Register listener * @param sdkLisConfig object listener configuration */
this.addListeners=function(sdkLisConfig){
// Register the corresponding listener events according to the returned listener configuration
window.addEventListener("error".function(event){
this.addError(event,"js");
event.preventDefault();
},true);
window.addEventListener('unload'.() = >{
// When uninstalling, you need to report the data to it
this.ajax();
});
window.addEventListener("unhandledrejection".event= > {
this.addError(err,"js");
});
}
/* Register ajax proxy interceptors */
this.initAjaxProxy=function(){
proxy({
// Enter before the request is initiated
onRequest: (config, handler) = > {
handler.next(config);
},
// Enter when an error occurs, such as a timeout; Note that HTTP status code errors such as 404 are not included, and the request is still considered successful
onError: (err, handler) = > {
this.addError(err,"ajax");
handler.next(err)
},
// Enter after the request succeeds
onResponse: (response, handler) = > {
this.addError(response,"ajax");
handler.next(response)
}
})
}
/* Register SDK * @param config object error object * @param success function * @param fail function */
this.initSdk=function(config,success,fail){
let xmlHttp = window.XMLHttpRequest ?
new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP');
xmlHttp.open("post".this.api+"/api/login".true);
xmlHttp.setRequestHeader('Content-Type'.'application/json; charset=UTF-8');
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
let sdkLisConfig = JSON.parse(xmlHttp.responseText);
success(sdkLisConfig);
} else{ fail(xmlHttp.responseText); }}; xmlHttp.send(this.ToString({appkey:config.appkey}));
}
* @param config object * @param () function succeeds * @param () function fails */
this.initSdk(config,(sdkLisConfig) = >{
// Registration succeeded
this.addListeners();
this.AppErrorList=localStorage.getItem("AppErrorList")?
JSON.parse(localStorage.getItem("AppErrorList")) : [];let timeObj=setInterval(() = > {
if(this.AppErrorList.length>0) {this.postError(this.AppErrorList[0]);
}
}, config.timeNum||5000);
window.addEventListener('unload'.() = >{
clearTimeout(timeObj);
},false);
},(error) = >{
// Error failed
return new Error('Registration exception${error}`)})}* @param param object * @param () function * @param () function Error object */
HookErrorSdk.prototype.ajax=function(param,success, fail){
if(navigator.sendBeacon&&typeof(navigator.sendBeacon)==="function"){
navigator.sendBeacon(this.api+"/api/sendLog".this.ToString(param)).then((res) = >{
success(res)
}).catch((res) = >{
fail(res)
})
}else{
let img = new Image();
img.onload =function (res){
success(res);
}
img.onerror=function(res){
fail(res);
}
img.src = this.api+`? data=`+this.ToString(param); }}@param Param object Error * @param type String Error type */
HookErrorSdk.prototype.addError=function(event,type){
let error=this.getAxaxError(event,type);
this.AppErrorList.push(Object.assign(error,this.formObj));
localStorage.setItem("AppErrorList".this.ToString(this.AppErrorList));
}
/* Final error reported * @param object Error message object */
HookErrorSdk.prototype.postError=function(obj){
this.ajax(obj,() = >{
this.AppErrorList.shift();
localStorage.setItem("AppErrorList".this.ToString(this.AppErrorList));
},() = >{
return new Error("Error collection service exception")})}Copy the code
Hookerrorsdk.js should include these features internally
1. Register the SDK based on the appKey, obtain the permission to use the SDK, and obtain the error rule to be reported
2. Use window.addeventListener (‘error’) to enable global resource files and business code exception listeners (WebSocket, WebWorker,postMessage)
3. Use window.addeventListener (‘unhandledrejection’) to enable Promise exception monitoring
4. Use Ajax-hook to intercept Ajax to catch interface exception monitoring
5. Use localStorage to store abnormal data to avoid data loss
6. Run setInterval to periodically detect exceptions
7. Use navigator.sendBeacon or create an image to report abnormal data
// my final SDK, 'Hook' is my own prefix
import HookErrorSdk from 'HookErrorSdk';
const hookSdk=new HookErrorSdk({
userId:"88888888".//appkey
appKey: 'hook-aa-g8g4yc2d'.// Customize HTTP Error code. If the error code is not configured, the error message returned by the interface is displayed
httpCodeMap: {801:"Token failure"},// Additional arguments passed in
formObj:{}
})
Copy the code
5.) The background resolves the abnormal part
So far the front-end exception capture part of the technical points are good, then we collect the exception, how to locate the source location of the exception? Resource exception we can directly according to the name of the resource can obviously see the exception, so this does not need to consider the location of the source code, and the interface exception is the same, according to the interface URL and parameters can be located
The most important thing is to restore the exception of the business code. Previously, we captured the exception of the business code through window.addEventListener as follows
There is an anomaly in the figure,stack
Object, usingstack
We can base it onstacktracey
This library gets the specific code exception location, and then locates the source location
source-map.js
Used to parse sourcemAP files
stacktracey.js
The stack used to parse error messages
The end of the
Now that the whole process has been determined and the rough coding structure has been completed, the next article will be about how to optimize it into a usable SDK. Finally, follow me and see you in the next article!
The last
Welcome to pay attention to my personal public number [front-end], regularly share the use of dry goods, the original is not easy to use, please like thank you!