The original address

preface

Before, I investigated the front-end code script errors of the company and tried to reduce the number of JS errors. Based on my previous experience, I practiced and summarized this aspect. Now I will talk about my opinions on the abnormal monitoring of front-end code.

This paper mainly discusses the following points:

  1. How JS handles exceptions
  2. Report the way
  3. Abnormal monitoring Reports common problems

JS exception handling

For Javascript, we are only faced with exceptions, the occurrence of exceptions will not directly cause the JS engine to crash, at most will cause the current execution of the task to terminate.

  1. The current code block will be pushed into the task queue as a task, and the JS thread will constantly extract the task execution from the task queue.
  2. When an exception occurs during the execution of a task and the exception is not caught and handled, it is continuously thrown out of the call stack until the execution of the current task is terminated.
  3. The JS thread will continue to fetch the next task from the task queue and continue to execute it.
<script> error console.log(' never execute '); </script> <script> console.log(' I continue ') </script>Copy the code

Before scripting errors can be reported, exceptions need to be handled. The program needs to be aware of the occurrence of script errors before we talk about exception reporting.

Script errors generally fall into two categories: syntax errors and runtime errors.

Here are several ways to handle exception monitoring:

Try-catch exception processing

We often see try-catches in our code. By wrapping a block of code with a try-catch, when something goes wrong with the block, the catch catches the error message and the page continues to execute.

However, try-catch is limited in its ability to handle exceptions. It can only catch non-asynchronous errors at runtime. Syntax and asynchronous errors cannot be caught.

Example: runtime error

Try {error // undefined variable} catch(e) {console.log(' I know I'm wrong '); console.log(e); }Copy the code

However, syntax errors and asynchronous errors are not caught.

Example: syntax error

Try {var error = 'error'; } catch(e) {console.log(' I don't perceive errors '); console.log(e); }Copy the code

Uncaught SyntaxError: Invalid or unexpected token XXX Common syntax errors will be displayed in the editor. But such errors throw exceptions directly, often crashing the program, and are easily observed during coding.

Example: Asynchronous error

Try {setTimeout(() => {error // async error})} catch(e) {console.log(' I can't feel the error '); console.log(e); }Copy the code

You can’t sense an error unless you add a try-catch to the setTimeout function, which is a bit verbose to write.

Window. onerror exception handling

Window. onerror is slightly better at catching exceptions than try-catch. Onerror can catch run-time errors, whether asynchronous or non-asynchronous.

Example: Runtime synchronization error

/** * @param {String} MSG Error message * @param {String} URL error file * @param {Number} row Number * @param {Number} col column Number * @param */ window.onerror = function (MSG, url, row, col, error) {console.log(' I know I'm wrong '); console.log({ msg, url, row, col, error }) return true; }; error;Copy the code

Example: Asynchronous error

Window. onerror = function (MSG, url, row, col, error) {console.log(' I know the async error '); console.log({ msg, url, row, col, error }) return true; }; setTimeout(() => { error; });Copy the code

Window.onerror, however, has no control over syntax errors, so we should avoid syntax errors as much as possible when writing code, but such errors can cause the entire page to crash, which is relatively easy to detect.

In practical use, onError is mainly used to catch unexpected errors, while try-catch is used to monitor specific errors in predictable situations. The combination of onError and try-catch is more efficient.

Note that window.onerror does not raise an exception until it returns true, otherwise the console will display Uncaught Error: XXXXX even if it knows the exception occurred.

There are two more things to note about window.onerror

  1. For global capture like onError, it is best to write it at the beginning of all JS scripts, because you cannot guarantee that the code you are writing will not be wrong. If you write it later, it will not be caught by onError.
  2. In addition, onError is an error that cannot catch network exceptions.

<script> window.onerror = function (MSG, url, row, col, error) {console.log(' I know the async error '); console.log({ msg, url, row, col, error }) return true; }; </script> <img src="./404.png">Copy the code

Because network request exceptions do not bubble, they must be captured in the capture phase. However, although this method can capture network request exceptions, it cannot determine whether the HTTP status is 404 or other such as 500, so it needs to cooperate with server logs to conduct investigation and analysis.

<script> window.addEventListener('error', (MSG, url, row, col, error) => {console.log(' I know 404 is wrong '); console.log( msg, url, row, col, error ); return true; }, true); </script> <img src="./404.png" alt="">Copy the code

This knowledge still needs to know, otherwise the user visits the website, the picture CDN cannot serve, the picture cannot load out and the developer is not aware of the embarrassment.

Promise error

Promises can help with asynchronous callback hell, but once a Promise instance throws an exception and you don’t catch it, there’s nothing onError or try-catch can do to catch an error.

Window. addEventListener('error', (MSG, url, row, col, error) => {console.log(' I don't perceive a promise error'); console.log( msg, url, row, col, error ); }, true); Promise.reject('promise error'); new Promise((resolve, reject) => { reject('promise error'); }); new Promise((resolve) => { resolve(); }).then(() => { throw 'promise error' });Copy the code

While it’s a good habit to write a catch function at the end of a Promise instance, it’s easy to get confused and forget to write a catch when you write too much code.

So if your application uses a lot of Promise instances, especially if you’re using asynchronous Promise based libraries like Axios, you have to be careful because you don’t know when those asynchronous requests will throw an exception and you’re not handling it, So you’d better add a Promise global exception capture event, unHandledrejection.

Window.addeventlistener ("unhandledrejection", function(e){e.preventDefault() console.log(' I know the promise was wrong '); console.log(e.reason); return true; }); Promise.reject('promise error'); new Promise((resolve, reject) => { reject('promise error'); }); new Promise((resolve) => { resolve(); }).then(() => { throw 'promise error' });Copy the code

Of course, if your application doesn’t do Promise global exception handling, it’s likely to look something like this on the home page:

Abnormal Reporting Mode

After receiving the error information, the monitoring system needs to send the captured error information to the information collection platform. There are two main ways to send the error information:

  1. Send data through Ajax
  2. Dynamically create the form of an IMG tag

Instance – Dynamically create an IMG tag for reporting

function report(error) {
  var reportUrl = 'http://xxxx/report';
  new Image().src = reportUrl + 'error=' + error;
}
Copy the code

Monitoring Reports common problems

The following examples are all posted on my Github for readers to look up.

git clone https://github.com/happylindz/blog.git
cd blog/code/jserror/
npm install
Copy the code

What is a Script error

Because our online version is often CDN for static resources, this will cause the pages we visit frequently to come from different domain names of the file, which will easily produce Script errors if no additional configuration is made.

You can run NPM run Nocors to view the effect.

Script error is generated by the browser under the restriction of the same origin policy. The browser is concerned about security. If an exception is thrown when a page references a Script file outside a different domain name, the page has no right to know this error message.

The goal is to prevent data from leaking into insecure domains, so for a simple example,

<script src="xxxx.com/login.html"></script>
Copy the code

This HTML is the login page of the bank. If you are already logged in, the login page automatically redirects to Welcome XXX… If you have not logged in, go to Please Login… , then the error will be Welcome XXX… Is not defined, Please Login… Is not defined, according to the information, you can determine whether a user logs in to his or her account, which provides a convenient channel for intruders to determine, which is quite insecure.

After the background, then should we address the problem?

The first solution that can come to mind must be the homology strategy, which can inline JS files to HTML or put them in the same domain. Although it can solve script error problem simply and effectively, it cannot take advantage of file cache and CDN, so it is not recommended. The correct approach is to solve script error errors at their root.

Cross-source Resource Sharing Mechanism (CORS)

Start by adding the crossOrigin attribute to the script tag on the page

// http://localhost:8080/index.html <script> window.onerror = function (msg, url, row, col, Error) {console.log(' I know the error and I know the error message '); console.log({ msg, url, row, col, error }) return true; }; </script> <script src="http://localhost:8081/test.js" crossorigin></script> // http://localhost:8081/test.js setTimeout(() => { console.log(error); });Copy the code

Access-control-allow-origin: localhost:8080 when you modify the front-end code, you also need to add access-Control-allow-Origin: localhost:8080 to the back end of the response header.

const Koa = require('koa');
const path = require('path');
const cors = require('koa-cors');
const app = new Koa();

app.use(cors());
app.use(require('koa-static')(path.resolve(__dirname, './public')));

app.listen(8081, () => {
  console.log('koa app listening at 8081')
});
Copy the code

Readers can use NPM Run Cors for detailed cross-domain knowledge I won’t expand, interested can read my previous article: Cross-domain, all you need to know is here

You think this is the end of it? No, here are some Script error points you don’t see very often:

We all know that JSONP is used to fetch data across domains and that it is compatible and still used in some applications, so your project might use code like this:

/ / http://localhost:8080/index.html window. Onerror = function (MSG, url, the row, col, error) {the console. The log (' I know wrong, But don't know the error message '); console.log({ msg, url, row, col, error }) return true; }; function jsonpCallback(data) { console.log(data); } const url = 'http://localhost:8081/data? callback=jsonpCallback'; const script = document.createElement('script'); script.src = url; document.body.appendChild(script);Copy the code

Because the returned message is executed as a script file, if the returned script content fails, the error message cannot be caught.

As before, add crossOrigin to the dynamic add script and add the corresponding CORS field on the back end.

const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);
Copy the code

The reader can see the effect through NPM Run jsonp

Once you know how it works, you might think nothing of adding a crossOrigin field to every dynamically generated script, but in real life, you might be programming for many libraries, such as using jQuery, Seajs, or WebPack to load scripts asynchronously. Many libraries encapsulate the ability to load scripts asynchronously, and in the case of jQeury you might do this to trigger asynchronous scripts.

$.ajax({ url: 'http://localhost:8081/data', dataType: 'jsonp', success: (data) => { console.log(data); }})Copy the code

If one of these libraries doesn’t provide crossOrigin capability (jQuery jSONp probably does, pretend you don’t know), then you’ll have to modify the source code written by someone else, so here’s a way to hijack document.createElement. Add crossOrigin fields from the root for each dynamically generated script.

document.createElement = (function() { const fn = document.createElement.bind(document); return function(type) { const result = fn(type); if(type === 'script') { result.crossOrigin = 'anonymous'; } return result; }}) (); Window. onerror = function (MSG, url, row, col, error) {console.log(' I know the error, I know the error '); console.log({ msg, url, row, col, error }) return true; }; $.ajax({ url: 'http://localhost:8081/data', dataType: 'jsonp', success: (data) => { console.log(data); }})Copy the code

The effect is the same and can be seen by NPM run jsonpjq:

Create createElement (createElement) {createElement (createElement) {createElement (createElement) {createElement (createElement) {createElement (createElement)}}

That’s it for Script errors, but if you understand the above, almost all Script errors can be solved.

Window. onError Whether to catch iframe errors

If your page uses an iframe, you need to monitor your imported iframe for exceptions. Otherwise, if your imported iframe page has a problem, your main site will not display it, and you will not know it.

First of all, it is important to note that the parent window cannot be caught directly using window.onError. If you want to catch an iframe exception, there are several cases.

If your iframe page is in the same domain as your host, simply add onError to the iframe.

<iframe src="./iframe.html" frameborder="0"></iframe> <script> window.frames[0].onerror = function (msg, url, row, col, Error) {console.log(' I know iframe is wrong and I know the error message '); console.log({ msg, url, row, col, error }) return true; }; </script>Copy the code

NPM run iframe to see the effect:

If your embedded iframe page is not the same domain name as your host site, but the iframe content is not owned by a third party and is under your control, you can communicate with the iframe to send the exception message to the host site. There are many ways to communicate with an iframe, such as postMessage, hash, or name fields across fields

If it is not the same domain and the site is not subject to their own control, in addition to the console to see detailed error information, there is no way to capture, this is for the sake of security, you introduced a Baidu home page, home page error with what let you to monitor it, it will lead to a lot of security problems.

How does compressed code locate script exceptions

Almost all the codes on the line have been compressed, and dozens of files have been packaged into one ugly code. When we receive a is not defined, we have no idea what the variable A actually means, and the error log reported in this case is obviously invalid.

The first thought was to use Sourcemap to locate the error code. For details, see sourcemap to locate script errors

In addition, you can add several lines of space between each merged file and some corresponding comments during the packaging process. In this way, it is easy to know which file reports the error when locating the problem. Then, you can quickly locate the problem by searching some keywords.

How to collect too much exception information

If your website visits a lot, if the PV of the page has 1KW, then an inevitable error message will have 1kW, we can set a collection rate for the website:

Reporter. Send = function(data) {if(math.random () < 0.3) {send(data)}}Copy the code

This collection rate can be set by the actual situation, the method is diversified, can use a random number, can also be specific according to some characteristics of the user to determine.

The above is almost my understanding of front-end code monitoring, easy to say, but once in engineering application, it is inevitable to consider compatibility and other problems, readers can adjust through their own specific situation, front-end code anomaly monitoring plays a crucial role in the stability of our website. If the article all wrong place, also hope to correct.

Refer to the article

  • Script Error volume optimization – Monitors Script error reporting
  • Collect and monitor front-end code exception logs
  • Front-end Magic Hall – Exceptions are more than just try/catch