SoFunction
Updated on 2025-03-01

How to collect exception data by building front-end monitoring of JavaScript architecture

Preface

In the first two articles, we introduced why the front-end should have a monitoring system and the overall steps to build front-end monitoring. I believe you have already understood why and What are the front-end monitoring. Next we solve the problem of how to implement How.

If you don't understand front-end monitoring, it is recommended to read the first two articles:

  • Why can't the front-end be without a monitoring system?
  • Overall construction steps for front-end monitoring

In this article, we introduce how the front-end collects data, starting with collecting abnormal data.

What is exception data?

Exception data refers to the execution exception or loading exception triggered by the front-end during the operation of the page. At this time, the browser will throw an error message.

For example, if your front-end code uses an undeclared variable, the console will print a red error, telling you the reason for the error. Or if the interface request error occurs, the exception can also be found in the network panel. Is it an exception sent by the request or an exception responding to the interface.

In our actual development scenario, the exceptions caught by the front-end are mainly divided into two major categories, interface exceptions and front-end exceptions. Let’s take a look at how to catch these two major categories of exceptions.

Interface exception

The interface exception must be triggered at the time of request. Most of the front-end requests are currently usedaxiosinitiated, so just get the exception that may occur in axios.

If you use the Promise method, use.catchcapture:

axios
  .post('/test')
  .then((res) => {
    (res);
  })
  .catch((err) => {
    // err is the error object caught    handleError(err);
  });

If you use async/await, usetry..catch..capture:

async () => {
  try {
    let res = await ('/test');
    (res);
  } catch (err) {
    // err is the error object caught    handleError(err);
  }
};

When an exception is caught, it is handed over tohandleErrorFunction processing, this function will process the received exception and callReport interfacePass the exception data to the server to complete the collection.

The exception catch we wrote above is logically fine, but in practice, you will find the first hurdle:There are so many pages, does it take a layer of catch for each request?

Yes, if we are developing a new project, it is understandable to stipulate that each request must be covered with a layer of catch at the beginning, but if we are connecting to front-end monitoring in an existing project of not small size, it is obviously unrealistic to catch each page or each request at this time.

Therefore, in order to minimize access costs and reduce intrusion, we use the second solution:Catch exceptions in axios interceptor

For front-end projects, in order to handle requests uniformly, such as 401 jumps or global error prompts, they will write an axios instance globally, add an interceptor to this instance, and then pour this instance into other pages directly for use, such as:

// Global request: src/request/const instance = ({
  baseURL: ''
  timeout: 15000,
  headers: {
    'Content-Type': 'application/json',
  },
})
export default instance

Then initiate the request on the specific page:

// a page: src/page/import http from '@/src/request/';
async () => {
  let res = await ('/test');
  (res);
};

In this way, we find that each page's request will go to a global axios instance, so we only need to catch the exception at the global request location, and we do not need to catch it on each page, so the access cost will be greatly reduced.

According to this plan, we willsrc/request/This file is implemented manually.

Catch exceptions in the interceptor

First we add a response interceptor for axios:

// Response Interceptor(
  (response) => {
    return ;
  },
  (error) => {
    // An exception will come here    if () {
      let response = ;
      if ( >= 400) {
        handleError(response);
      }
    } else {
      handleError(null);
    }
    return (error);
  },
);

The second parameter of the response interceptor is a function executed when an error occurs, and the parameter is an exception. We must first determine whether it exists, if it exists, it means that the interface is responsive, that is, the interface is connected, but the error is returned; if it does not exist, it means that the interface is not connected, the request is suspended, and most of the time it is crashed.

If there is a response, first obtain the status code and determine when the exception needs to be collected based on the status code. The above judgment method is simple and crude. As long as the status code is greater than 400, it is considered an exception, and the response data is obtained and the reporting logic is executed.

If there is no response, it can be regarded as an interface timeout exception. Pass a pass when calling the exception handler functionnullJust do it.

Front-end exception

Above we introduced how to catch interface exceptions in the axios interceptor. In this part, we will introduce how to catch front-end exceptions.

The most common way to catch exceptions in front-end code is to use try..catch.. Any synchronous code block can be placed intryIn the block, catch will be executed as soon as an exception occurs:

try {
  //Arbitrary synchronous code} catch (err) {
  (err);
}

The above says "arbitrary synchronous code" instead of "arbitrary code", mainly because the ordinary Promise writing method try..catch.. cannot be captured, and can only be used..catch()Capture, such as:

try {
  (new Error('What happened?')).catch((err) => ('1:', err));
} catch (err) {
  ('2:', err);
}

Throw this code into the browser and the print result is:

1: Error: Something went wrong

It's obvious that just .catch caught the exception. However, like the logic of the interface exception above, there is no problem in handling the current page exception in this way. However, from the perspective of the entire application, this way of catching exceptions is highly invasive and has high access cost, so our idea is still global capture.

It is also relatively simple to catch js exceptions globally, just use ('error'):

// js error capture('error', (error) => {
  // error is an exception to js});

Why not?

Many friends here have questions, why not use itWhere is the global monitoring? ('error') andWhat's the difference?

First of all, the functions of these two functions are basically the same, and both can catch js exceptions globally. However, there is a type of exception called resource loading exception, which refers to exceptions caused by non-existent images, js, css and other static resources in the code, such as:

const loadCss = ()=> {
  let link = ('link')
   = 'text/css'
   = 'stylesheet'
   = '/'
  ('head')[10].append(link)
}
render() {
  return <div>
    <img src='./'/>
    <button onClick={loadCss}>Loading style<button/>
  </div>
}

In the above code/andIt does not exist. After JS execution, it will definitely report an error that cannot be found in the resource. However, by default, the global listening functions on the above two window objects cannot listen to such exceptions.

Because the resource loading exception will only be triggered on the current element and the exception will not bubble to the window, listening to exceptions on the window cannot be caught. So what should I do?

If you are familiar with DOM events, you will understand that since the bubbling stage cannot be monitored, it will definitely be monitored during the capture stage.

The method is to giveThe function specifies the third parameter, it is very simpletrue, indicating that the listening function will be executed in the capture stage, so that the resource loading exception can be listened to.

// Global monitoring during capture stage(
  'error',
  (error) => {
    if ( != window) {
      (, );
    }
    handleError(error);
  },
  true,
);

The above method can easily monitor image loading exceptions, which is why it is more recommendedThe reason. But remember, set the third parameter totrue, listen for event capture, and you can catch JS exceptions and resource loading exceptions globally.

Special attention is required.The Promise exception cannot be caught either. No matter what()Or how to writeasync/awaitThe writing method cannot be captured when an exception occurs.

Therefore, we also need to listen to a globallyunhandledrejectionFunction to catch unhandled Promise exceptions.

// promise error capture('unhandledrejection', (error) => {
  // The reason for printing exception  ();
  handleError(error);
  // Prevent the console from printing  ();
});

unhandledrejectionEvents will have an exception in Promise and are not specifiedcatchtriggered when the program is equivalent to a global Promise exception guarantee solution. This function will catch the Promise exception that unexpectedly occurs at runtime, which is very useful for us to troubleshoot.

By default, when an exception occurs in Promise and is not caught, an exception is printed on the console. If we want to prevent exception printing, we can use the above()method.

Exception handling function

Previously, we called an exception handling function when catching an exceptionhandleError, All exceptions and reporting logic are handled in this function, and we will implement this function next.

const handleError = (error: any, type: 1 | 2) {
  if(type == 1) {
    // Handle interface exceptions  }
  if(type == 2) {
    // Handle front-end exceptions  }
}

In order to distinguish exception types, the function adds a second parameter type to indicate whether the current exception belongs to the front-end or interface. Use as follows in different scenarios:

  • Handling front-end exceptions:handleError(error, 1)
  • Handling interface exceptions:handleError(error, 2)

Handle interface exceptions

To handle interface exceptions, we need to parse the error parameter we get and then retrieve the required data. The data fields generally required for interface exceptions are as follows:

  • code: http status code
  • url: Interface request address
  • method: Interface request method
  • params: Interface request parameters
  • error: Interface error message

These fields can be obtained in the error parameter as follows:

const handleError = (error: any, type: 1 | 2) {
  if(type == 1) {
    // At this time, the error response, its config field contains the request information    let { url, method, params, data } = 
    let err_data = {
       url, method,
       params: { query: params, body: data },
       error: ?.message || (),
    })
  }
}

config objectparamsRepresents the query parameter requested by GET,dataRepresents the body parameter of the POST request, so when I process the parameters, I merge these two parameters into one and use a property params to represent it.

params: { query: params, body: data }

There is anothererrorThe attribute indicates the error message. This method of obtaining it should be obtained according to the format of your interface return. To avoid obtaining the extra-long error information that may be returned by the interface, it is mostly because the interface is not processed, which may cause the data writing to be failed. It must be stipulated in advance with the background.

Handle front-end exceptions

Most of the front-end exceptions are js exceptions, and the exception corresponds to jsErrorBefore processing an object, let’s first look at what types of Errors are:

  • ReferenceError: Quote error
  • RangeError: Exceeded the valid range
  • TypeError: Type error
  • URIError: URI parsing error

These types of exception reference objects areError, so you can get it like this:

const handleError = (error: any, type: 1 | 2) {
  if(type == 2) {
    let err_data = null
    // Monitor whether the error is the standard type    if(error instanceof Error) {
      let { name, message } = error
      err_data = {
        type: name,
        error: message
      }
    } else {
      err_data = {
        type: 'other',
        error: (error)
      }
    }
  }
}

In the above judgment, first determine whether the exception isErrorExamples of . In fact, most code exceptions are standard JS Errors, but let's make a judgment here. If so, directly obtain the exception type and exception information. If not, set the exception type tootherJust do it.

Let's write any exception code to see the captured results:

function test() {
  ('ccc');
}
test();

Then the captured exception is like this:

const handleError = (error: any) => {
  if (error instanceof Error) {
    let { name, message } = error;
    (name, message);
    // Print result: TypeError is not a function  }
};

Get environmental data

Getting environment data means that whether it is an interface exception or a front-end exception, in addition to the data of the exception itself, we also need some other information to help us locate where something went wrong faster and more accurately.

We call this type of data "environmental data", which is the environment in which the exception is triggered. For example, who triggered the error on which page. With these, we can immediately find the source of the error and then resolve the error based on the exception information.

Environmental data includes at least the following:

  • app: The name/identification of the application
  • env: Application environment, generally development, testing, production
  • version: The version number of the application
  • user_id: User ID that triggered the exception
  • user_name: The user name that triggered the exception
  • page_route: Exceptional page routing
  • page_title:Exception page name

appandversionAll are application configurations, which can determine which version of the application exception appears. I suggest getting these two fields directlyNextnameandversionAttributes, just modify the version number in time when the application is upgraded.

The remaining fields need to be obtained according to the framework configuration. Below I will introduce how to obtain them in Vue and React respectively.

In Vue

When obtaining user information in Vue, you usually get it directly from Vuex. If your user information is not stored in Vuex, the same is true for obtaining it from localStorage.

If in Vuex, you can do this:

import store from '@/store'; // vuex export directorylet user_info = ;
let user_id = user_info.id;
let user_name = user_info.name;

User information exists in status management, and page routing information is generally invue-routerdefined in. The front-end routing address can be obtained directly from vue-router, and the page name can be configured inmetaIn, such as:

{
  path: '/test',
  name: 'test',
  meta: {
    title: 'Test Page'
  },
  component: () => import('@/views/test/')
},

After this configuration, it is simple to obtain the current page route and page name:

 = new Vue({...})
let route = vm.$route
let page_route = 
let page_title = 

In the last step, we will obtain the current environment. The current environment uses an environment variableVUE_APP_ENVIndicates that there are three values:

  • dev: Development environment
  • test: Test environment
  • pro: Production environment

Then create three new environment files in the root directory and write to the environment variables:

  • .:VUE_APP_ENV=dev
  • .:VUE_APP_ENV=test
  • .:VUE_APP_ENV=pro

Get nowenvYou can get it in the environment like this:

{
  env: .VUE_APP_ENV;
}

In the last step, when performing packaging, pass in the pattern to match the corresponding environment file:

# Test environment packaging$ num run build --mode staging
# Production environment packaging$ num run build --mode production

After obtaining the environment data and then putting in the abnormal data, we prepare the data and wait for reporting.

In React

Like Vue, user information can be obtained directly from status management. Because there is no global shortcut to obtain the current travel in React, I will also put the page information in the status management. The status management I use is Mobx, and the acquisition method is as follows:

import { TestStore } from '@/stores'; // mobx export directorylet { user_info, cur_path, cur_page_title } = TestStore;
// User information: user_info// Page information: cur_path, cur_page_title

In this way, you need to update the routing information in mobx every time you switch the page. How to do it?

Actually, it is on the root routing page (usually the home page)useEffectJust listen:

import { useLocation } from 'react-router';
import { observer, useLocalObservable } from 'mobx-react';
import { TestStore } from '@/stores';
export default observer(() => {
  const { pathname, search } = useLocation();
  const test_inst = useLocalObservable(() => TestStore);
  useEffect(() => {
    test_inst.setCurPath(pathname, search);
  }, [pathname]);
});

After obtaining user information and page information, the next step is the current environment. Passed like Vue--modeto specify the mode and load the corresponding environment variables, except that the setting method is slightly different. Most React projects may be usedcreate-react-appCreated, let’s use this as an example to introduce how to modify it.

First, openscripts/File, this is the file executed when executing npm run start. We add code to the 6th line of the beginning part:

.REACT_APP_ENV = 'dev';

Yes, the environment variable we specified isREACT_APP_ENV, because onlyREACT_The environment variables at the beginning can be read.

Then modify it againscripts/Line 48 of the file, modified as follows:

if ( >= 2 && argv[0] == '--mode') {
  switch (argv[1]) {
    case 'staging':
      .REACT_APP_ENV = 'test';
      break;
    case 'production':
      .REACT_APP_ENV = 'pro';
      break;
    default:
  }
}

Get it nowenvYou can get it in the environment like this:

{
  env: .REACT_APP_ENV;
}

Summarize

After a series of previous operations, we have obtained exception data in a relatively comprehensive way, as well as the environment data when an exception occurs. Next, we call the reporting interface and pass this data to the background to save it. It will be very convenient for us to search and track it in the future.

If you also need front-end monitoring, you might as well spend half an hour collecting abnormal data according to the method introduced in the article. I believe it will be of great help to you.

The above is the detailed content of how to collect abnormal data when building a front-end monitoring of JavaScript architecture. For more information about JavaScript front-end monitoring and collect abnormal data, please pay attention to my other related articles!