JavaScript Web page exception capture

1. General classification of anomalies

Generally, the exceptions we want to catch are classified as follows:

  • Grammar mistakes

    • Onerror event code block andSyntax error code blockNot together, as in trycatche
    • Or the same code block, butSyntax error code blockAsynchronous execution

    In all of these cases, you can catch syntax errors with onError

    setTimeout(() => { 
        eval('function()') 
    }, 1000); 
    // Uncaught SyntaxError: Function statements require a function name
    Copy the code
  • Reference errors, type errors, URI errors, scope errors, and so on

    In the case of non-try catch wraps, onError can be used to catch synchronous and asynchronous errors

    console.log(a) // Uncaught ReferenceError: A is not defined array.test () // call test that does not exist on Array, undefined, execute as a function, and raise TypeError // Uncaught TypeError: Array.test is not a function new Array(12221312312) // Uncaught RangeError: Invalid array length decodeURI('%') // Uncaught URIError: URI malformedCopy the code
  • try{} catch{}

    If a try block fails, it can only be caught in a catch. However, if there is an asynchronous error code in the try block, the catch cannot be caught and will be caught by onError

    Try {setTimeout(() => {console.log('a', a) // can be caught by onError}, 1000)} Catch (e) {console.log('e', e) } // Uncaught ReferenceError: a is not definedCopy the code
  • Promise throws an error

    • No catch is set
    Let p = new Promise((resolve, reject) => {reject(1)}Copy the code
    • Error reported in catch no catch
    ; (async function xx() { try { throw 1 } catch(e) { console.log('a', A) // This error can be caught using unhandledrejection}})() // Uncaught ReferenceError: A is not definedCopy the code

    In both cases, you can listen to unHandledrejection to catch errors

    window.addEventListener('unhandledrejection', function(e) { // e.preventDefault(); // Prevent exceptions from throwing console.log(' Caught exception unhandledrejection: ', e)})Copy the code
  • Static resource loading failed. Procedure

    • Adds an onError event to the resource
    // html // <img src="" alt="" id="imgID"> // js let imgID = document.getElementById('imgID') imgID.onerror = function(e)  { console.log('img load error :>> ', e); } // Note: onError needs to be defined before the image path is set to catch the load failure imgid. SRC = 'http://xxx.png'Copy the code
    • Static resource network request failure events do not bubble and need to be caught in the capture phase

    In Chrome and FF, you can listen for error events by bubbling to catch resource loading failures

    / / note: This is emitted along with the onError event above, Window. onerror window.addEventListener('error', Error => {console.log('addEventListener catches exception: ', error)}, true)Copy the code
  • Web collapse

    • Once the page is loaded, a logo is buried to indicate that it is loading
    // If the page crashes, the second time the page returns, the current flag is read. If the value exists and is true, the page exits properly. If the value is not true, the page may have crashed. If (localStorage.getitem ('good_exit') && localStorage.getitem ('good_exit')! == 'true') {localstorage. getItem('time_before_crash') } window.addeventListener ('load', function () {localstorage.setitem ('good_exit', 'pending'); SetInterval (function () {localstorage.setitem ('time_before_crash', new Date().tostring ()); }, 10000); }) window.addeventListener ('beforeunload', function () {// After normal web page exit, will be buried flag, set to true, SetItem ('good_exit', 'true'); })Copy the code

    The above is in the second time to enter the page to know the web crash, so what method can be reported after the web crash

    • You can use the Service Worker for monitoring

    Its life cycle is page-independent (it can exit if the associated page is not closed, or start if there is no associated page)

    2. Send a message to the worker every 10 seconds. The message contains: {type field: active indicates normal active; exit indicates normal exit time field: After the page is about to exit, send type: 3. A message receiving event is registered inside the worker. When the message sent from the receiving page is received with type active, it means normal active; when the internal time value is updated and type exit is received, it means normal exit. The worker internally maintains a status object, including the time, which is sent to the page and checked every 15 seconds. If the time does not change from the last time, the page may crash (pay attention to the case where time is 0).Copy the code
  • Script Error

    Error messages for cross-domain scripts are only displayed as Script errors because they are protected. This is resolved as follows

    Access-control-allow-origin: <script crossorigin SRC ="http://other-domain.js"></script> Set it to * or the current domain nameCopy the code
  • iframe Error

    <iframe src="./test.html"></iframe>
    <script> window.frames[0].onerror = function(msg, source, lineno, colno,error) { 
        console.log('frames onerror :>> ', msg,source,lineno,colno,error) 
    } </script>
    Copy the code
  • Vue’s own try catch handles the error so we can’t catch it

    Capture using the errorHandler method provided by Vue

    Vue. Config. errorHandler = function(err, vm, info) {let {message, // error message name, // error name script, // error script URL line, // Abnormal row number column, // abnormal column number stack // Abnormal stack information} = err // VM indicates the Vue instance that throws an exception. // Info indicates the Vue specific error information. Log ('vue Err, VM, info :>> ', err, vm, info)}Copy the code

So catch errors summed up:

// The catch error in try catch can be caught when webpack is packed. // 2, error window.addEventListener('error', Error => {let data = {} // let {colno, lineno, message, filename, error Stack} = error / / not all browsers support colno parameters let col = colno | | (window. The event & & window. Event. ErrorCharacter) | | 0 data. The url = url data.line = line data.col = col if (!! Stack){// Use data.msg = stack.tostring ()}else if (!! Arguments.callee){// try to get stack information from callee let ext = [] let f = arguments.callee.caller, While (f && (--c>0)) {ext.push(f.tostring ()) if (f === f.call) {break // if there are rings} f = f.call} ext = ext.join(",") data.msg = ext} console.log('3, addEventListener error >>> error: // 3, unhandledrejection window. AddEventListener ('unhandledrejection', function(e) { // e.preventDefault(); // Prevent the exception from raising console.log('4, promise unhandledrejection: ', e)}) // 4, errorHandler Vue. Config. ErrorHandler = function(err, vm, Info) {// vm is the Vue instance that throws an exception // info is the Vue specific error message, For example, the life cycle of the error hook let {message, // exception message name, // exception name script, // exception script URL line, // exception line number column, Log ('2, vue errorHandler :>> ', err, VM, info)}Copy the code

Error log reporting

Now that the exception has been caught, how do we handle it, how do we report it, and what do we need to report?

Classification of log

1. General log classification level

Log, DEBUG, INFO, WARN, errorCopy the code

2. Use the log reporting type for different scenarios

Log: records process information debug: records key debugging information INFO: records service function points and whether they succeed or fail. Warn: page warning information Error: page error or service exception informationCopy the code

3. Log report Information Provides information

1. User ID, session, and user name 2. Current error information 3. Information that can be used to reproduce and infer the current errorCopy the code

4. Log reporting policy

1. Set a cache quantity for upload, and report upon arrival. Because you cannot report an error every time it occurs, it will affect the user's network. 2. Each log burial point has its own reporting level. A general configuration area is required to configure the current log reporting level so that each buried point can determine the log level to be reported. If the log level is smaller than the specified value, the log cannot be reported. 3. If the local cache temporarily stores too much data, delete the previous dataCopy the code

After reporting, the next step is to collect, analyze, classify and display logs on the server side. Based on BADJS, we build a complete log parsing system

3. Log reporting

Badjs service installation

1, preparatory work

In order to build quickly, we used Docker for installation

Note: If Docker is used in Windows, docker Desktop must be installed

  • Mysql install docker install mysql

    Note: after mysql is installed, you need to download it from the db directory in the badjs-web project. Use create. SQL to initialize web-related databases

  • Mongodb installation (password cannot be set) Docker installation mongo

2. Project installation

Github clone project to local

git clone https://github.com/BetterJS/badjs-installer
Copy the code

Subproject downloads and dependent installations

// Download the badjs-acceptor, badjs-MQ, badjs-storage, and badjs-Web projects yarn clone // Install each project depends on YARN InstallCopy the code

3. Modify the configuration items

  • Modify badjs – acceptor project in the project. The debug. Json/project. The json

    Note: This is where the log is reported. The url property that needs to be set when the client initializes badjs-Report is the service address here

    http://{badjs-acceptor:port}/badjs

    // Change the port attribute from 80 to 8083; {"port": 8083} {"port": 8083}}Copy the code
  • Modify badjs – web project in the project. The debug. Json/project. The json

    {"mysql" : {"url" : "mysql://root:123456@localhost:3306/badjs"}}Copy the code

4. Start the project

yarn start

Check the badjs-web startup port and visit http://localhost:port to see the log backend management service page

Badjs modules

1. Badjs-acceptor Accepts logs reported by clients

Badjs-acceptor receives a log report and sends it to badjs-MQ package.json. Configure the Dispatcher attribute to indicate the request information to BADJs-MQ, such as the request port (10001).Copy the code

2. Badjs-mq message queue to ensure that messages are received in an orderly and stable manner

Badjs-mq accepts badjs-acceptor requests from package.json. Configure the acceptor accept attribute to represent the interface configured to receive messages, such as port (10001). Badjs-storage package.json configure the dispatcher distribution attribute to represent the information requested to badjs-storage: for example, port (10000)Copy the code

3. Badjs-storage storage module

Badjs-storage: receives a request from badjs-MQ and writes it to mongodb package.json. Configure the acceptor accept attribute to indicate the interface configured to receive the message, such as port (10000).Copy the code

Badjs-web log background management system

Package. json configure acceptor accept attributes. Badjs-acceptor request ports package.json Configure storage attributes. Mysql database properties, mysql database properties, mysql database properties, etcCopy the code

5. Report logs to badjs-Report

Badjs-report overrides window.onError to catch errors

1, install,

yarn add badjs-report
Copy the code

2. Initialize

Import badjs from 'badjs-report' badjs.init({// Id of the item must be configured: 1 // This ID is the ID of the applied project after the web startup. The reported logs are identified by this ID. Service module URL: 'http://badjs-acceptor address after startup ', // where logs are reported // Select uIN: 123, // specify user ID (this plug-in reads QQ uin by default) delay: Ignore: [/Script error/ I], // ignores an error, does not report random: 1, // sampling report, can be set between 0 and 1. 1 indicates 100% reporting (default: 1). // Number of repeat: 5, // Number of times the same error is not reported; OnReport: function(id, errObj) {}, // Callback after reporting log. Where id is the id and errorObj is the error object submitted: Function (url) {}, // overwrite the original reporting method, which used the new Image() form to report, can be changed to the desired reporting method, such as using the INTERNAL structure of the POST URL ext: {}, // extension properties, back-end extension processing properties. // Enable offline logging (default: false) offlineLogExp: 5 // Offline valid time (default: last 5 days)})Copy the code

3. Manually report

A, badjs.report('error MSG ') b, badjs.report({MSG: 'error MSG ', // error message target: 'error. ColNum: 2 // colNum: 2})Copy the code

4. Delayed reporting

The staging

Badjs. push('error MSG ') badjs.push({MSG: 'error MSG ', // error message to be reported target: 'error. ColNum: 2 // colNum: 2})Copy the code

Immediately inform

Badjs. report({MSG: 'error MSG ', // error message target: 'error. Js ', // error file rowNum: 1, // error line colNum: 2 // Error columns})Copy the code

5. Report offline logs

badjs.reportOfflineLog()
Copy the code

6. Project application

Import BJ_REPORT from 'badjs-report' import Vue from 'Vue' // Enumeration of environment ID let ENV_ID_ENUM = {DEV: 1, SIT: 2, UAT: 3, PRO: 4 } let curEnv = '', Origin = window.location.origin if (origine.indexof ('dev.xxx.com') > -1) {// http://dev.xxx.com // DEV curEnv = 'DEV' } else if (origine.indexof ('sit.xxx.com') > -1) {// http://sit.xxx.com // SIT curEnv = 'SIT'} else if (Origin. IndexOf ('uat.xxx.com') > -1) {// http://uat.xxx.com // UAT environment curEnv = 'UAT'} else if (origin. IndexOf (' m.xxx.com ') > 1) {/ / http://pro.xxx.com / / PRO environment curEnv = 'PRO'} let envID = ENV_ID_ENUM [curEnv] | | ENV_ID_ENUM.DEV // Initialization log reporting plug-in bj_report. init({id: envID, // No id will be reported, url: 'http://{badjs-acceptor:port}/badjs'}) // When initializing the project, a global VM instance can be exposed, Rootvm = new Vue({}) // initialize the project // Initialize the listener exception init(window.rootVM) function init(rootInstance) { Vue. Config. errorHandler = function(err, curInstance, info) {// vm for Vue instance // info for Vue specific error information, Let {message, // error message // name, // error name // script, // error script URL line, // error line number column, } = err log(message, stack, line, column, curInstance) console.log('vue errorHandler :>> ', err, curInstance, info) } window.addEventListener( 'error', e => { let { colno, lineno, message, Filename} = e log(message, filename, Lineno, colno, rootInstance) console.log('addEventListener error >>> error: ', e) }, true ) window.addEventListener('unhandledrejection', function(e) { // e.preventDefault(); // Prevent exceptions from throwing up let {reason} = e log(json.stringify (reason), '', '', '', '', RootInstance) console.log('Promise exception unhandledrejection: ', e) }) } function log(msg, target, rowNum, colNums, vminstance) { let msgs = `***[${msg}]***`, State = (vminstance && vminstance $store && vminstance. $store. State) | | {} / / user information if (state. The userInfo) {let { PhoneNumber, userId} = state.userInfo MSGS += '***[phone:${phoneNumber}--userId:${userId}]***'} fullPath } = (vminstance && vminstance.$route) || {} msgs += `***[router-name: ${name} -- router-fullpath: ${fullPath}]***` BJ_REPORT.report({ msg: msgs, target, rowNum, colNums }) }Copy the code