Hello everyone, I am Nian nian!

This is a comprehensive design question asked last week, if it is not used buried point monitoring system, or no in-depth understanding, the basic cool.

This article will make it clear:

  1. What are the problems that the buried monitoring system is responsible for and how do you design the API?
  2. Why use img SRC to send requests, and what about sendBeacon?
  3. React/Vue error boundaries

What is buried monitoring SDK

For example, when a company launches a website, it is impossible for developers to predict what will happen when users actually use it: which pages have they viewed? What percentage of users will click ok on a popup, and what percentage will click Cancel? Is there a page crash?

Therefore, we need a burying point to monitor THE SDK for data collection and subsequent statistical analysis. With the analysis of data, can be targeted to optimize the website: PV pages do not waste a lot of manpower; Buggy pages need to be fixed, or it will cost 325.

Google Analytics is a well-known monitoring site. In addition to web, there are also SDK for iOS and Android.

You can obtain the React version of Github by replying to ReactSDK

The functional scope of buried site monitoring

Due to different business needs, most companies will develop their own buried point monitoring system, but basically they will cover these three functions:

User Behavior Monitoring

Responsible for the statistics of PV (number of page visits), UV (number of page visits) and click operations of users.

This type of statistics is the most widely used, and it is with these data that we can quantify our work.

Page Performance Monitoring

Of course, developers and testers will evaluate these data before launching, but the user environment is different from ours, maybe 3G network, maybe very old model, we need to know the performance data in actual use scenarios, such as page load time, white screen time, etc.

Error alarm monitoring

Get bad data and deal with it in a timely manner to prevent large numbers of users from being affected. In addition to globally caught error messages, there are also error alarms that are caught within the code, which need to be collected.

The following will expand on these three types from the point of view of API design.

The design of the SDK

Before we start designing, how does the SDK work

import StatisticSDK from 'StatisticSDK';
// Global initialization once
window.insSDK = new StatisticSDK('uuid-12345');


<button onClick={()= >{ window.insSDK.event('click','confirm'); . }}> Confirm</button>
Copy the code

First, mount the SDK instance to the global, and then call it in the business code. The new instance here needs to pass in an ID, because this buried monitoring system is often used by multiple businesses, so as to distinguish different data sources by ID.

First implement the instantiation part:

class StatisticSDK {
  constructor(productID){
    this.productID = productID; }}Copy the code

Data sent

Data sending is the most basic API on which the rest of the functionality is based. Typically, this scenario would use AJAX to send data, but here the SRC attribute of the image is used. There are two reasons:

  1. There is no cross-domain restriction, such as srCIPT tag, IMG tag can directly send cross-domain GET requests, without special processing.
  2. Compatibility is good, some static pages may have script disabled, then the script tag can not be used;

The SRC attribute of the IMG tag is used to concatenate the parameters behind its URL, which the server will parse after receiving.

class StatisticSDK {
  constructor(productID){
    this.productID = productID;
  }
  send(baseURL,query={}){
    query.productID = this.productID;
    let queryStr = Object.entries(query).map(([key, value]) = > `${key}=${value}`).join('&')
    let img = new Image();
    img.src = `${baseURL}?${queryStr}`}}Copy the code

The advantage of the IMG tag is that you don’t need to append it to the document, just set the SRC attribute to successfully initiate the request.

Usually the requested URL will be a 1X1px GIF image. Online articles are vague about why the image is returned as a GIF. Here are some information and tests:

  1. GIF size is the smallest among images of the same size and different formats, so choose to return a GIF, so that the loss of performance is less;
  2. If 204 is returned, the img onError event is walked to and a global error is thrown; If 200 and an empty object is returned, there will be a CORB alarm;

Of course, if you don’t care about this error, you can return an empty object, and there are tools that do that

  1. There are some buried points that need to be realistically added to the page. For example, spammers may add a hidden flag to verify that the message is open, and returning 204 or 200 empty objects will result in an obvious image placeholder
<img src="http://www.example.com/logger?event_id=1234">
Copy the code

More elegant Web Beacon

This method of marking is called a Web beacon. In addition to giFs, since 2014, browsers have been implementing special apis to do this more elegantly: navigator.sendbeacon

It’s easy to use

Navigator.sendBeacon(url,data)
Copy the code

This method has advantages over image SRC:

  1. Instead of preempting resources from the main business code, it sends when the browser is idle.
  2. In addition, when the page is uninstalled, the request can be successfully sent without blocking the page refresh and jump.

Current buried spot monitoring tools usually use sendBeacon in preference, but due to browser compatibility, the SRC pocket of the image is still required.

User Behavior Monitoring

The above implementation of the data sending API, now can be based on it to implement the user behavior monitoring API.

class StatisticSDK {
  constructor(productID){
    this.productID = productID;
  }
  // Data is sent
  send(baseURL,query={}){
    query.productID = this.productID;
      let queryStr = Object.entries(query).map(([key, value]) = > `${key}=${value}`).join('&')
      let img = new Image();
      img.src = `${baseURL}?${queryStr}`
  }
  // Custom events
  event(key, val={}) {
    let eventURL = 'http://demo/'
    this.send(eventURL,{event:key,... val}) }/ / pv exposure
  pv() {
    this.event('pv')}}Copy the code

User behavior includes custom events and PV exposure, which can also be considered as a special custom behavior event.

Page Performance Monitoring

Performance data for a page can be obtained using the Performance. Timing API, which is a timestamp in milliseconds.

It is not necessary to know all of the above, but the key data are as follows, according to which the time points of FP/DCL/Load and other key events can be calculated:

  1. Page first render time:FP(firstPaint)=domLoading-navigationStart
  2. DOM loading complete:DCL(DOMContentEventLoad)=domContentLoadedEventEnd-navigationStart
  3. Images, styles and other external chain resources are loaded:L(Load)=loadEventEnd-navigationStart

The above values can correspond to the results in the Performance panel.

Going back to the SDK, we just implement an API that uploads all performance data:

class StatisticSDK {
  constructor(productID){
    this.productID = productID;
    // Initializes the automatic invocation of performance reporting
    this.initPerformance()
  }
  // Data is sent
  send(baseURL,query={}){
    query.productID = this.productID;
      let queryStr = Object.entries(query).map(([key, value]) = > `${key}=${value}`).join('&')
      let img = new Image();
      img.src = `${baseURL}?${queryStr}`
  }
  // Performance report
  initPerformance(){
    let performanceURL = 'http://performance/'
    this.send(performanceURL,performance.timing)
  }
}
Copy the code

Also, it is called automatically in the constructor, because the performance data must be uploaded, so the user does not need to call it manually every time.

Error alarm monitoring

Error alarm monitoring is divided into JS native error and React/Vue component error handling.

JS native error

In addition to errors caught in a try catch, we also need to report errors that are not caught — listening through error events and unhandledrejection events.

error

Error events are used to listen for DOMExceptions and JS errors. Specifically, JS errors fall into the following eight categories:

  1. InternalError: an InternalError, such as a recursive stack explosion;
  2. RangeError: RangeError, such as new Array(-1);
  3. EvalError: error when using eval();
  4. ReferenceError: ReferenceError, such as using undefined variables;
  5. SyntaxError: SyntaxError, such as var a =;
  6. TypeError: TypeError, such as [1,2].split(‘.’);
  7. URIError: Invalid arguments passed to encodeURI or decodeURl(), such as decodeURI(‘%2’)
  8. Error: The base class for the above seven errors, usually thrown by the developer

In other words, any of the eight types of errors mentioned above can be detected while the code is running.

unhandledrejection

Errors thrown within a Promise cannot be caught by an error, so an unhandledrejection event is required.

Going back to the SDK implementation, the code to handle the error alarm is as follows:

class StatisticSDK {
  constructor(productID){
    this.productID = productID;
    // Initialize error monitoring
    this.initError()
  }
  // Data is sent
  send(baseURL,query={}){
    query.productID = this.productID;
      let queryStr = Object.entries(query).map(([key, value]) = > `${key}=${value}`).join('&')
      let img = new Image();
      img.src = `${baseURL}?${queryStr}`
  }
  // Custom error reporting
  error(err, etraInfo={}) {
    const errorURL = 'http://error/'
    const { message, stack } = err;
    this.send(errorURL, { message, stack, ... etraInfo}) }// Initialize error monitoring
  initError(){
    window.addEventListener('error'.event= >{
      this.error(error);
    })
    window.addEventListener('unhandledrejection'.event= >{
      this.error(new Error(event.reason), { type: 'unhandledrejection'}}})})Copy the code

Like initializing performance monitoring, initializing error monitoring must be done, so it needs to be called in the constructor. Subsequent developers can simply call the error method in the try catch of the business code.

The React/Vue component is faulty. Procedure

Mature framework libraries have error handling mechanisms, React and Vue are no exception.

React error boundaries

Error bounds are the hope that when a rendering error occurs within the application, the entire page will not crash. We give it a pocket component in advance, and can refine the granularity, only the error parts are replaced by this “pocket component”, so that the whole page does not work properly.

It is simple to use as a class component with a special life cycle that wraps around the business component.

The two life cycles are getDerivedStateFromError and componentDidCatch,

The code is as follows:

// Define error bounds
class ErrorBoundary extends React.Component {
  state = { error: null }
  static getDerivedStateFromError(error) {
    return { error }
  }
  componentDidCatch(error, errorInfo) {
    // Call the SDK instance we implemented
    insSDK.error(error, errorInfo)
  }
  render() {
    if (this.state.error) {
      return <h2>Something went wrong.</h2>
    }
    return this.props.children
  }
}
...
<ErrorBoundary>
  <BuggyCounter />
</ErrorBoundary>
Copy the code

Built an online sandbox can experience, public number background reply “error boundary demo” to obtain the address

Going back to SDK integration, in production, a component wrapped in an error boundary cannot be listened for if it throws an error internally, because the error boundary itself is equivalent to a try catch. So the reporting needs to be done inside the error boundary component. That’s the componentDidCatch life cycle in the code above.

Error bounds for Vue

Vue has a similar lifecycle to do just that: errorCaptured

Vue.component('ErrorBoundary', {
  data: () = > ({ error: null }),
  errorCaptured (err, vm, info) {
    this.error = `${err.stack}\n\nfound in ${info} of component`
    // Call our SDK and report an error message
    insSDK.error(err,info)
    return false
  },
  render (h) {
    if (this.error) {
      return h('pre', { style: { color: 'red' }}, this.error)
    }
    return this.$slots.default[0]}})... <error-boundary><buggy-counter />
</error-boundary>

Copy the code

We’ve now implemented a complete SKELETON SDK and dealt with how react/ Vue projects should be plugged in during actual development.

The actual production SDK will be more robust, but the idea is no different, and anyone interested can read the source code.

conclusion

It’s going to be a long article, but to answer this question well, you’re going to need all this knowledge.

If we want to design the SDK, we should first understand its basic usage method, and then know how to build the code framework behind it. The SDK needs to be able to handle user behavior, page performance, and error alerts. Finally, react and Vue projects usually do error boundary handling, how to access our own SDK.

If you think this article is useful to you, like the following is my biggest encouragement!

Your support is my creative motivation!