Automatic reporting of function errors by AST

Time:2020-11-27

preface

Some people around me asked me how to implement automatic error monitoringFunction automatically adds error trapping。 Today, let’s talk about how technology works. Let’s talk about the principle first: when the code is compiled, the loader of Babel is used to hijack all function expressions. And then use itAst (abstract syntax tree)Modify the function node and wrap try / catch in the outer layer of the function. Then use the SDK in catch to capture and report the error information at runtime. If you are interested in compiling and packaging, this article is for you.

This paper involves the following knowledge points:

  • [x] AST
  • [x] NPM package development
  • [x] Babel
  • [x] Babel plugin
  • [x] Webpack loader

Achieve results

BeforeDevelopment environment:

var fn = function(){
  console.log('hello');
}

AfterOnline environment:

var fn = function(){
+  try {
    console.log('hello');
+  } catch (error) {
+// SDK error reporting
+    ErrorCapture(error);
+  }
}

What is Babel?

BabelIs a JS compiler, mainly used to convert ECMAScript 2015 + code into backward compatible JavaScript syntax, so that it can run in current and old versions of the browser or other environments.
In short, it isFrom one source code to anotherEditor for! Here’s what Babel can do for you:

  • grammatical transformation
  • Add missing features to the target environment by Polyfill (via@babel/polyfillModule)
  • Code MODS
  • other

Babel can be run in three stages. Please bear in mind: parsing, transformation and generation, which will be used later.

In this article, we will write an NPM package of Babel plugin forCompile timeModify the code.

Building Babel plugin environment

Here we useyeomanandgenerator-babel-pluginTo build the scaffold code for the plug-in. Installation:

$ npm i -g yo
$ npm i -g generator-babel-plugin

Then create a new folder:

$ mkdir babel-plugin-function-try-actch
$ cd babel-plugin-function-try-actch

The development project for generating NPM package:

$ yo babel-plugin

Automatic reporting of function errors by AST
At this time, the project structure is as follows:

babel-plugin-function-try-catch
├─.babelrc
├─.gitignore
├─.npmignore
├─.travis.yml
├─README.md
├─package-lock.json
├─package.json
├─test
|  ├─index.js
|  ├─fixtures
|  |    ├─example
|  |    |    ├─.babelrc
|  |    |    ├─actual.js
|  |    |    └expected.js
├─src
|  └index.js
├─lib
|  └index.js

This is our Babel plugin calledbabel-loader-function-try-catchIn order to facilitate the reading of the article, we collectively referred to asplugin)。

So far, the NPM package environment has been set up,Code address

Debug the ast of plugin

development tool

As mentioned earlier in this paper, Babel runs in three stages: parsing, transformation and generation. In each stage, Babel officially provides the core lib:

  • babel-core。 Babel’s core library provides the ability to compile and transform code.
  • babel-types。 Provides the type of AST tree node.
  • babel-template。 It can convert ordinary string into ast, providing more convenient use

staypluginToolkit for root directory installation:

npm i @babel/core @babel/parser babel-traverse @babel/template babel-types -S

openpluginSRC of/ index.js Editor:

const parser = require("@babel/parser");

//Let's start by defining a simple function
let source = `var fn = function (n) {
  console.log(111)
}`;

//It resolves to ast
let ast = parser.parse(source, {
  sourceType: "module",
  plugins: ["dynamicImport"]
});

//Print it to see if it is normal
console.log(ast);

Terminal executionnode src/index.jsThe following results will be printed:
Automatic reporting of function errors by AST
This is the ast corresponding to the FN function,The first step of parsing is completed!

Get the ast of the current node

Then we usebabel-traverseTo traverse the corresponding ast node, we want to find all function expressions that can be written inFunctionExpressionChinese:

openpluginSRC of/ index.js Editor:

const parser = require("@babel/parser");
const traverse = require("babel-traverse").default;

//Source code to be modified
let source = `var fn = function() {
  console.log(111)
}`;

//1. Analysis
let ast = parser.parse(source, {
  sourceType: "module",
  plugins: ["dynamicImport"]
});

//2. Traversal
+ traverse(ast, {
+Functionexpression (path, state) {// function node
+     // do some stuff
+   },
+ });

All function representations go toFunctionExpressionThen we can modify it in it.
Where parameterspathUsed to access the current node informationpath.nodeYou can also access the parent node like the DOM treepath.parent

Modify ast of current node

Well, the next thing to do isFunctionExpressionThen put it into the try function, and add the code segment of error reporting SDK in the catch.

Get function body internal code

The function defined above is

var fn = function() {
  console.log(111)
}

So the code block inside the function isconsole.log(111), you can usepathGet the ast information of this code as follows:

const parser = require("@babel/parser");
const traverse = require("babel-traverse").default;

//Source code to be modified
let source = `var fn = function(n) {
  console.log(111)
}`;

//1. Analysis
let ast = parser.parse(source, {
  sourceType: "module",
  plugins: ["dynamicImport"]
});

//2. Traversal
traverse(ast, {
  Function expression (path, state) {// the function expression will enter the current method
+// get the current node information of the function
+    var node = path.node,
+        params = node.params,
+        blockStatement = node.body,
+        isGenerator = node.generator,
+        isAsync = node.async;

+// you can try printing to see the results
+    console.log(node, params, blockStatement);
  },
});

Terminal executionnode src/index.jsYou can print the ast node information of the current function.

Creating a try / catch node (two steps)

Creating a new node may be a little bit rusty, but I have summarized my personal experience for you (for reference only). First, you need to know what the declaration of the newly added code segment is, and then use the@babel-typesJust create it.

Step 1:

So how do we know what its expression declaration type is? Here we canuseastexplorerFind its type expression in AST
Automatic reporting of function errors by AST
As shown in the above screenshot, the type of try / catch in AST isTryStatement

Step 2:

Then go@babel-typesFind the corresponding method in the official document and create it according to the API document.
Automatic reporting of function errors by AST
As shown in the document, create a try / catch mode to uset.tryStatement(block, handler, finalizer)

Create a new ast node in one sentence summary: use ast explorer to find the type of code you want to generate, and then according to the type in the@babel-typesDocument search corresponding use method can be used!

To create a try / catch, you only need to uset. Trystatement (try code block, catch code block)That’s fine.

  • Try code blockRepresents the function code block in try, that is, the code in the original function bodyconsole.log(111)Can be used directlypath.node.bodyobtain;
  • Catch code blockRepresents the catch code block, that is, we want to modify the SDK for error collection and reportingErrorCapture(error)You can use @ Babel / template to generate.

The code is as follows:

const parser = require("@babel/parser");
const traverse = require("babel-traverse").default;
const t = require("babel-types");
const template = require("@babel/template");

//0. Define a function to be processed (mock)
let source = `var fn = function() {
  console.log(111)
}`;

//1. Analysis
let ast = parser.parse(source, {
  sourceType: "module",
  plugins: ["dynamicImport"]
});

//2. Traversal
traverse(ast, {
  Functionexpression (path, state) {// function node
    var node = path.node,
        params = node.params,
        blockStatement =  node.body , // function internal code, put the function internal code block into the try node
        isGenerator = node.generator,
        isAsync = node.async;

+// create the code in the catch node
+    var catchStatement = template.statement(`ErrorCapture(error)`)();
+    var catchClause = t.catchClause(t.identifier('error'),
+          t.blockStatement(
+            [catchStatement] //  catchBody
+          )
+        );
+// create the ast of try / catch
+    var tryStatement = t.tryStatement(blockStatement, catchClause);
  }
});

Create a new function node and define thetry/catchInsert the function body:

const parser = require("@babel/parser");
const traverse = require("babel-traverse").default;
const t = require("babel-types");
const template = require("@babel/template");

//0. Define a function to be processed (mock)
let source = `var fn = function() {
  console.log(111)
}`;

//1. Analysis
let ast = parser.parse(source, {
  sourceType: "module",
  plugins: ["dynamicImport"]
});

//2. Traversal
traverse(ast, {
  Functionexpression (path, state) {// function node
      var node = path.node,
          params = node.params,
          blockStatement =  node.body , // function internal code, put the function internal code block into the try node
          isGenerator = node.generator,
          isAsync = node.async;

      //Create the code in the catch node
      var catchStatement = template.statement(`ErrorCapture(error)`)();
      var catchClause = t.catchClause(t.identifier('error'),
            t.blockStatement(
              [catchStatement] //  catchBody
            )
          );
      //Create ast of try / catch
      var tryStatement = t.tryStatement(blockStatement, catchClause);

+// create a new node
+    var func = t.functionExpression(node.id, params, t.BlockStatement([tryStatement]), isGenerator, isAsync);
+// print to see if it is successful
+     console.log ('the current node is:', func);
+     console.log ('the self node under the current node is:', func.body );
  }
});

At this time, the above code is executed at the terminalnode src/index.js
Automatic reporting of function errors by AST
As you can see, we have created a try function (trystatement) in a function expression body.
Finally, we need to replace the original function node:

const parser = require("@babel/parser");
const traverse = require("babel-traverse").default;
const t = require("babel-types");
const template = require("@babel/template");

//0. Define a function to be processed (mock)
let source = `var fn = function() {...

//1. Analysis
let ast = parser.parse(source, {...

//2. Traversal
traverse(ast, {
  Functionexpression (path, state) {// function node
      var node = path.node,
          params = node.params,
          blockStatement =  node.body , // function internal code, put the function internal code block into the try node
          isGenerator = node.generator,
          isAsync = node.async;

      //Create the code in the catch node
      var catchStatement = template.statement(`ErrorCapture(error)`)();
      var catchClause = t.catchClause(t.identifier('error'),...

      //Create ast of try / catch
      var tryStatement = t.tryStatement(blockStatement, catchClause);
      //Create a new node
      var func = t.functionExpression(node.id, params, t.BlockStatement([tryStatement]), isGenerator, isAsync);
      
+// replace the original node
+    path.replaceWith(func);
  }
});

+// convert the newly generated AST to the source code:
+ return core.transformFromAstSync(ast, null, {
+// file: configse babel.config.js Otherwise, Polyfill will be injected, making debugging difficult
+ }).code;

“A loader is a node module exporting a function”, that is to say, a loader is an exposed node module. Since it is a node module, it can be written as follows:

module.exports = function() {
    //  ...
};

Edit SRC again/ index.js The following is a screenshot:
Automatic reporting of function errors by AST

Boundary condition treatment

We don’t need to add try / catch to all functions, so we have to deal with some boundary conditions.

  • 1. If there is a try catch package
  • 2. Prevent circle loops
  • 3. Only statements, such as () = > 0, need to try catch
  • 4. If the function content is less than how many lines

If you meet the above conditions, you will return it!
Automatic reporting of function errors by AST
The code is as follows:

if (blockStatement.body && t.isTryStatement(blockStatement.body[0])
  || !t.isBlockStatement(blockStatement) && !t.isExpressionStatement(blockStatement)
  || blockStatement.body && blockStatement.body.length <= LIMIT_LINE) {
  return;
}

Finally, we posted it toNPM platformuse.
Automatic reporting of function errors by AST
Because the length is too long to read, this article especially omits the local debugging process, so you need to debug. Please move to [add error report for function automatically by ast] about local development and debugging of NPM package] ().

How to use it

npm install babel-plugin-function-try-catch

Webpack configuration

rules: [{
  test: /\.js$/,
  exclude: /node_modules/,
  use: [
+   "babel-plugin-function-try-catch",
    "babel-loader",
  ]
}]

The effect is shown in the following figure:
Automatic reporting of function errors by AST
Automatic reporting of function errors by AST

last

For the local debugging of NPM package, see the next partLocal development and debugging of NPM package

More ast related, please pay attention to share later, thank you.

Reference:

Please click

Babel plug-in manual Click

Recommended Today

Summary of recent use of gin

Recently, a new project is developed by using gin. Some problems are encountered in the process. To sum up, as a note, I hope it can help you. Cross domain problems Middleware: func Cors() gin.HandlerFunc { return func(c *gin.Context) { //Here you can use * or the domain name you specify c.Header(“Access-Control-Allow-Origin”, “*”) //Allow header […]