真的绝了,通过注释来埋点好简单!!

news2024/11/25 13:42:05

目录

回顾

开始

插件编写

功能一

功能二

功能三

合并功能

运行代码

总结


        这篇文章主要讲如何根据注释,通过babel插件自动地,给相应函数插入埋点代码,在实现埋点逻辑和业务逻辑分离的基础上,配置更加灵活 

回顾

        上篇文章也讲了向代码中插入埋点函数,只不过是给每个函数都插入埋点。给出源代码。

import "./index.css"; 

//##箭头函数 
const test1 = () => {}; 

//函数表达式 
const test2 = function () {}; 

// 函数声明 
function test3() {} 

// 类方法 
class test4 { 
    test4_0() {} 
    test4_1 = () => {}; 
    test4_2 = function () {}; 
}

给每个函数埋点,转译之后的效果:

image.png

而这篇文章想要达到的效果:

//##箭头函数 

//_tracker 
const test1 = () => {}; 

const test1_2 = () => {};

转译之后:

import _tracker from "tracker"; 

//##箭头函数 

//_tracker 
const test1 = () => { _tracker(); };
 
const test1_2 = () => {};

        代码中有两个函数,其中一个//_tracker的注释,另一个没有。转译之后只给有注释的函数添加埋点函数。
        要达到这个效果就需要读取函数上面的注释,如果注释中有//_tracker,我们就给函数添加埋点。这样做避免了僵硬的给每个函数都添加埋点的情况,让埋点更加灵活。

        下面让我们来看看怎么做 

开始

准备babel入口文件index.js

const { transformFileSync } = require("@babel/core"); 
const path = require("path"); 
const tracker = require("./babel-plugin-tracker-comment.js"); 
const pathFile = path.resolve(__dirname, "./sourceCode.js"); 

//transform ast and generate code 
const { code } = transformFileSync(pathFile, { 
    plugins: [
        [tracker, { trackerPath: "tracker", commentsTrack: "_tracker" }]
    ], 
}); 
console.log(code);

        这里我们使用transformFileSyncAPI转译源代码,并将转译之后的代码打印出来。过程中,将手写的插件作为参数传入plugins: [[tracker, { trackerPath: "tracker", commentsTrack: "_tracker"}]]。除此之外,还有插件的参数

  • trackerPath表示埋点函数的路径,插件在插入埋点函数之前会检查是否已经引入了该函数,如果没有引入就需要额外引入。
  • commentsTrack标识埋点,如果函数前的注释有这个,就说明函数需要埋点。判断的标识是动态传入的,这样比较灵活

入口文件准备好了,接下来准备下源代码文件sourceCode.js

import "./index.css"; 

//##箭头函数 
//_tracker 
const test1 = () => {}; 

const test1_2 = () => {}; 

//函数表达式 
//_tracker 
const test2 = function () {}; 

const test2_1 = function () {}; 

// 函数声明 
//_tracker 
function test3() {} 

function test3_1() {}

        这里准备了三种不同的函数类型,并且各个函数类型都有一个加了注释,另一个没加作为参照对象。就差插件了,下面写插件代码babel-plugin-tracker-comment.js

插件编写

功能一

功能实现过程中,涉及到了读取函数的注释,并且判断注释中是否有//_tracker

const leadingComments = path.get("leadingComments"); 
const paramCommentPath = hasTrackerComments(leadingComments, options.commentsTrack); 

//函数实现
const hasTrackerComments = (leadingComments, comments) => { 
    if (!leadingComments) { 
        return null; 
    } 
    if (Array.isArray(leadingComments)) { 
        const res = leadingComments.filter((item) => { 
            return item.node.value.includes(comments); 
        });
        return res[0] || null; 
    } 
    return null; 
};

        具体函数实现,接收函数前的注释,注释可能会有多个,所以需要一一判断。还接受埋点的标识。如果找到了含有注释标识的注释,就将这行注释返回。否则一律返回null,表示这个函数不需要埋点

那什么是多个注释?

这个很好理解,我们看下AST explorer就知道了

image.png


a函数,前面有4个注释,三个行注释,一个块注释。其对应的AST解析是这样的: 

image.png


        AST对象中,用leadingComments表示前面的注释,用trailingComments表示后面的注释。leadingComments中确实有4个注释,并且三个行注释,一个块注释,和代码对应上了。函数要做的就是将其中含有//_tracker的comment path对象找出来。

功能二

        判断函数确实需要埋点之后,就要开始插入埋点函数了。但在这之前,还需要做一件事,就是检查埋点函数是否引入,如果没有引入就需要额外引入了

const { addDefault } = require("@babel/helper-module-imports"); 
if (paramCommentPath) { 
    //add Import 
    const programPath = path.hub.file.path; 
    const importId = checkImport(programPath, options.trackerPath);
    state.importTackerId = importId; 
} 

//函数实现 
const checkImport = (programPath, trackPath) => { 
    let importTrackerId = ""; 
    programPath.traverse({ 
        ImportDeclaration(path) { 
            const sourceValue = path.get("source").node.value; 
            if (sourceValue === trackPath) { 
                const specifiers = path.get("specifiers.0"); 
                importTrackerId = specifiers.get("local").toString();
                path.stop(); 
            } 
        }, 
    }); 
    if (!importTrackerId) { 
        importTrackerId = addDefault(programPath, trackPath, { 
            nameHint: programPath.scope.generateUid("tracker"), 
        }).name; 
    } 
    return importTrackerId; 
};

        拿到import语句需要program节点。checkImport函数的实现就是在当前文件中,找出埋点函数的引入。寻找的过程中,用到了引入插件时传入的参数trackerPath。还用到了traverseAPI,用来遍历import语句
        如果找到了引入,就获取引入的变量。这个变量在之后埋点的时候需要。即如果引入的变量命名了tracker2,那么埋点的时候埋点函数就是tracker2了。

        如果没有引入,就插入引入。addDefault就是引入path的函数,并且会返回插入引用的变量。 

功能三

确定好了函数需要埋点,并且确定了埋点函数引入的变量,接下来就插入函数了。

if (paramCommentPath) { 
    //add Import 
    const programPath = path.hub.file.path; 
    const importId = checkImport(programPath, options.trackerPath); 
    state.importTackerId = importId; 
    insertTracker(path, state); 
} 
const insertTracker = (path, state) => { 
    const bodyPath = path.get("body"); 
    if (bodyPath.isBlockStatement()) { 
        const ast = template.statement(`${state.importTackerId}();`)(); 
        bodyPath.node.body.unshift(ast); 
    } else { 
        const ast = template.statement(`{ 
            ${state.importTackerId}(); 
            return BODY; 
        }`)({ BODY: bodyPath.node }); 
        bodyPath.replaceWith(ast); 
    } 
};

        在生成埋点函数的时候,就用到了之前获取到的埋点函数的变量importTackerId。还有在实际插入的时候,要区分函数体是一个Block,还是直接返回的值--()=>''

合并功能

三个功能都写好了,接下来将三个功能合起来,就是我们的插件代码了

const { declare } = require("@babel/helper-plugin-utils"); 
const { addDefault } = require("@babel/helper-module-imports"); 
const { template } = require("@babel/core"); 

//get comments path from leadingComments 
const hasTrackerComments = (leadingComments, comments) => { 
    if (!leadingComments) { 
        return false; 
    } 
    if (Array.isArray(leadingComments)) { 
        const res = leadingComments.filter((item) => { 
            return item.node.value.includes(comments); 
        }); 
        return res[0] || null; 
    } 
    return null; 
}; 

//insert path 
const insertTracker = (path, param, state) => { 
    const bodyPath = path.get("body"); 
    if (bodyPath.isBlockStatement()) { 
        const ast = template.statement(`${state.importTackerId}(${param});`)();
        bodyPath.node.body.unshift(ast);
    } else { 
        const ast = template.statement(`{ 
            ${state.importTackerId}(${param}); 
            return BODY; 
        }`)({ BODY: bodyPath.node }); 
        bodyPath.replaceWith(ast); 
    } 
}; 

//check if tacker func was imported 
const checkImport = (programPath, trackPath) => { 
    let importTrackerId = ""; 
    programPath.traverse({ 
        ImportDeclaration(path) { 
            const sourceValue = path.get("source").node.value; 
            if (sourceValue === trackPath) { 
                const specifiers = path.get("specifiers.0"); 
                importTrackerId = specifiers.get("local").toString(); 
                path.stop(); 
            } 
        }, 
    }); 
    if (!importTrackerId) { 
        importTrackerId = addDefault(programPath, trackPath, { 
            nameHint: programPath.scope.generateUid("tracker"), 
        }).name; 
    } 
    return importTrackerId; 
}; 

module.exports = declare((api, options) => { 
    console.log("babel-plugin-tracker-comment"); 
    return { 
        visitor: {
            "ArrowFunctionExpression|FunctionDeclaration|FunctionExpression": {
                enter(path, state) { 
                    let nodeComments = path; 
                    if (path.isExpression()) { 
                        nodeComments = path.parentPath.parentPath; 
                    } 
                    // 获取leadingComments 
                    const leadingComments = nodeComments.get("leadingComments"); 
                    const paramCommentPath = hasTrackerComments(leadingComments,options.commentsTrack); 
                    //查看作用域中是否有——trackerParam 
                    // 如果有注释,就插入函数 
                    if (paramCommentPath) { 
                        //add Import 
                        const programPath = path.hub.file.path; 
                        const importId = checkImport(programPath, options.trackerPath); 
                        state.importTackerId = importId; 
                        insertTracker(path, state); 
                    } 
                }, 
            }, 
        }, 
    }; 
});

        在获取注释的时候,代码中并不是直接获取到pathleadingComments,这是为什么?比如这串代码:

//_tracker 
const test1 = () => {};

        我们在函数中遍历得到的path是()=>{} ast的path,这个path的leadingComments其实是null,而想要获取//_tracker,我们真正需要拿到的path,是注释下面的变量声明语句。所以在代码中有判断是否为表达式,如果是,那就需要先parentPath,得到赋值表达式path,然后在parentPath,才能拿到变量声明语句

运行代码

node index.js

得到输出:

image.png

总结

        这篇文章写了如何根据函数上方的注释,选择性的给函数埋点。过程详尽,例子通俗易懂,真是不可多得的一篇好文章啊。有任何问题,欢迎金友们留言评论哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/760552.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务系列文章之 Springboot应用在k8s集群中配置的使用

Docker部署其实也可以再docker run或者dockerfile里面,将配置文件目录映射到宿主机,然后通过宿主机配置文件修改参数。 FROM docker.io/python:3.6MAINTAINER tianye # 设置容器时间 RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&am…

Windows安装Oh-My-Posh美化Powershell

Windows Terminal:https://www.microsoft.com/store/productId/9N0DX20HK701 最新Powershell下载:https://github.com/PowerShell/PowerShell/releases Oh-My-Posh官网:https://ohmyposh.dev/ Nerd字体下载:https://www.nerdfonts…

Git源代码管理方案

背景 现阶段的Git源代码管理上有一些漏洞,导致在每次上线发布的时间长、出问题,对整体产品的进度有一定的影响。 作用 新的Git源代码管理方案有以下作用: 多功能并行开发时,测试人员可以根据需求任务分配测试自己的功能&#…

MyBatis-Plus条件查询问题解决

问题描述 系统中有用户注册的功能,但是会出现重复注册的现象,代码中有做过重复校验,但是没有生效。 问题解决 首先排查数据生成时间点不同,相差时间有长有短,不是用户同时多次点击的原因,应该是用户这边…

js判断两个数组是增加还是删除

JS判断两个数组的数据&#xff0c;增加的数据以及删除的数据。 // 第一个参数是新数组&#xff0c;第二个参数是旧数 const compareArrays function(arr1, arr2 ) {let remove []let add []// 旧数据循环for (let i 0; i < arr2.length; i) {let item arr2[i];if (arr…

EvilBox---One靶机复盘

EvilBox—One靶机复盘 这个靶场学会了原来id_rsa的私钥可以直接爆破&#xff0c;利用ssh2john工具提取私钥&#xff0c;然后john直接爆破就可以了。 靶场下载地址&#xff1a;https://download.vulnhub.com/evilbox/EvilBox—One.ova 这个靶场是直接给ip地址的我们就不用扫描…

Spring Boot使用httpcomponents实现http请求

基于org.apache.httpcomponents的httpclient实现&#xff0c;其它的实现方式都行。 1. pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/…

快速实现主从表编辑,实现多个不定长从表

目录 1 前言 2 不定长表、定长表的定义 3 根据已有的电子表格制作数据库表并导入数据 3.1 订单 3.2 订单明细 3.3 客户 4 配置主从关联关系 5 继续增加一个主从关联关系 6 测试一下运行结果 7 一段代码用于实现在panel中画出字段列表面板 1 前言 实际工作中&#xff…

扑克牌检测Y8S

采用YOLOV8训练&#xff0c;得到PT模型&#xff0c;然后直接转ONNX&#xff0c;使用OPENCV的DNN&#xff0c;不需要其他依赖&#xff0c;支持C/PYTHON 扑克牌检测Y8S

数据结构(王道)——线性表的存储结构之顺序表

线性表和顺序表的关系&#xff1a; 两种实现方式&#xff1a;静态分配、动态分配 总结&#xff1a;

Win7如何合并C盘与D盘?

电脑C盘不够用&#xff0c;需要把D盘的卷删除并合并到C盘中。 整体步骤&#xff1a; 1.右击此电脑-管理-磁盘管理。 2.假如要把D盘容量合并到C盘&#xff0c;右击D盘&#xff0c;选择删除卷&#xff0c;点击“是”。此时D盘数据会被清空&#xff0c;建议先备份数据。 3.删除…

消息队列MQ入门理解

功能特性: 物联网应用 物联网设备通过微消息队列(LMQ)连接云端,双向通信,数据传输;设备数据通过消息队列(MQ)连接计算引擎,分析数据或者源数据实时高效写入到 HiTSDB / HiStore / ODPS 等。 大规模缓存同步 在商业大促活动中,如“双11”大促,各个分会场会有琳琅…

基于输出调节的一致性编队控制

参考博客&#xff1a; https://blog.csdn.net/weixin_44346182/article/details/131747082 在输出调节的基础上&#xff0c;实现了输出一致性&#xff0c;而编队控制就是没有偏移量的状态一致性&#xff0c;恰好本题的C矩阵就是系统的第一阶的状态&#xff08;位置&#xff0…

spring boot security自定义权限检查

前言 鉴权主要分为身份认证和权限控制两部分&#xff1a; 身份认证&#xff1a;检查当前用户是否合法&#xff08;比如已登录&#xff09; 权限控制&#xff1a;检查当前用户是否有访问该资源的权限 本文主要给出一个示例&#xff0c;说明如何自定义权限控制。 因为一个完整的…

增长能力模型:最大限度地发挥增长团队的潜力

作为增长领导者&#xff0c;准确评估你的组织和团队成员是一项非常重要且耗时的工作。但是如果我们计划为未来的增长领导者创造一条职业道路&#xff0c;我们就需要一个流程来为整个团队提供客观的反馈&#xff0c;这就是增长能力模型的用武之地。 增长能力模型提供了一个框架&…

argc,argv

文章目录 argc 是argument count的缩写表示传入main函数中的参数个数&#xff0c;包括这个程序本身 argv 是 argument vector的缩写表示传入main函数中的参数列表&#xff0c;其中argv[0]:程序的名字(.exe) int main(int argc, char** argv) {std::cout << "argc:…

1、环境搭建

下载visual studio,社区版基本够用了,其他版本是收费的。 下载之后安装即可。 安装完成之后要求我们选择要使用的模块。这里我们勾选如图中圈起来的模块,并选择安装。因为我这边已经安装过,所以按钮变成了“关闭” 安装完毕后,重启。 重启完成之后,打开Visual studio。…

配置JDK_IDEA

配置JDK_IDEA 1.安装JDK配置环境变量1.1.安装JDK1.2.配置JDK环境变量 2.IDEA 1.安装JDK配置环境变量 1.1.安装JDK 链接: 下载JDK 1.2.配置JDK环境变量 2.IDEA 链接: IDEA官网 自行激活

分布式运用——ELK 企业级日志分析系统

分布式运用——ELK 企业级日志分析系统 一、ELK 简介1.ELK的主要组件2.可以添加的其它组件3.为什么要使用 ELK4.完整日志系统基本特征5.ELK 的工作原理&#xff1a; 二、ELK集群部署三、ELK Elasticsearch 集群部署&#xff08;在Node1、Node2节点上操作&#xff09;1&#xff…

扩散模型学习笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 前言 &#xff08;注&#xff1a;高斯分布前加乘以项改变其方差&#xff09; 深入浅出扩散模型系列&#xff1a;DDPM架构图解&#xff08;模型架构篇&#x…