写点什么

pinpoint 插件开发之二:从零开始新建一个插件

作者:程序员欣宸
  • 2022 年 4 月 30 日
  • 本文字数:7412 字

    阅读完需:约 24 分钟

pinpoint插件开发之二:从零开始新建一个插件

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos


本篇概览

开发 pinpoint 的经验小结

  • 对初学者来说,从最基础的 pinpoint 体验,到个性化插件开发,应该是个逐步学习的过程,简单来说分为以下步骤:


  1. 对 pinpoint 整体功能、pinpoint server 和 pinpoint agent 有简单的了解;

  2. 学会编译构建 pinpoint;

  3. 对 pinpoint 插件的部署有简单的了解;

  4. 实战插件开发;


  • 所以建议您按照以下步骤来逐步实践:


  1. 《Docker下,极速体验编译pinpoint1.6.x分支》

  2. 《Docker下,极速体验pinpoint1.6.3》;

  3. 《pinpoint插件开发之一:牛刀小试,调整gson插件》;

  4. 本章,开发一个全新的插件;


  • 建议您先快速浏览上述三篇文章,然后咱们再一起动手从零开始做一个完成的插件,并在 web 应用中体验这个插件的功能;

新插件的功能

  • 新做的插件用来做什么呢?

  • 实际业务的生产环境中,常通过日志查看一些程序运行时的信息,例如把用户 id 打印到日志中,所以我打算做个插件将这些信息在 pinpoint 上显示出来;

对业务代码的侵入性

  • 为了避免这个功能导致大量修改已有业务代码(侵入性),我的方法是拦截 sl4j 日志的 info 这个方法,也就是 ch.qos.logback.classic.Logger 类的 info 方法,把 Logger.info(String str)的 str 参数在 pinpoint 输出;

特殊的约定

  • 生产环境中到处都调用了 sl4j 的 info 方法,如果全部拦截内容就太多了,所以和业务做个约定,info 方法入参的字符串,如果以**pinpoint_bizlog_**为前缀,那么我们的插件才做拦截,将日志的信息打印出来,其他的保持原样;

  • 这样只要业务执行诸如 logger.info("pinpoint_bizlog_" + "userid :" + userid);这样的代码,我们的插件就会将 userid : xxx 这样的字符串在 pinpoint 的追踪信息中显示出来;

开始吧

  • 功能已经设计好了,那我们就开始吧,给插件起个名字:bizlog

下载 pinpoint 源码

  • 目前 pinpoint 的稳定版本是 1.6.x,可以用 git clone 命令,也可以在 git 网站上将其整体打包下载下来,地址是:https://github.com/naver/pinpoint/tree/1.6.x,下载方法如下图:

导入到 ide

  • 整个 pinpoint 是一个大的 maven 工程,里面有很多小工程,所以我们用 idea 来导入;

创建新的 maven 工程

  • 在 plugins 文件夹下新建一个 bizlog 目录,里面新增一个 pom.xml 文件,内容如下:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.navercorp.pinpoint</groupId>        <artifactId>pinpoint</artifactId>        <relativePath>../..</relativePath>        <version>1.6.3-SNAPSHOT</version>    </parent>
<artifactId>pinpoint-bizlog-plugin</artifactId> <name>pinpoint-bizlog-plugin</name> <packaging>jar</packaging>
<dependencies> <dependency> <groupId>com.navercorp.pinpoint</groupId> <artifactId>pinpoint-bootstrap-core</artifactId> <scope>provided</scope> </dependency> </dependencies></project>
复制代码


  • 将自己作为 pinpoint 的子工程,依赖的是 pinpoint-bootstrap-core 这个库;

通过配置指定插件类和元信息类

  • 需要通过配置文件告诉 pinpoint 当前插件的功能类和元数据类在哪里,配置文件有两个,都放在 src/main/java/resources/META-INF/services 目录,如下图:



  • 文件名:com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin

  • 文件内容:com.navercorp.pinpoint.plugin.bizlog.BizlogPlugin

  • 文件功能:指定插件功能类

  • 文件名:com.navercorp.pinpoint.common.trace.TraceMetadataProvider

  • 文件内容:com.navercorp.pinpoint.plugin.bizlog.BizlogMetadataProvider

  • 文件功能:指定元数据类

开发插件功能类

  • 在 src\main\java\com\navercorp\pinpoint\plugin\bizlog 目录下创建功能类 BizPlugin.java:


package com.navercorp.pinpoint.plugin.bizlog;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentClass;import com.navercorp.pinpoint.bootstrap.instrument.InstrumentException;import com.navercorp.pinpoint.bootstrap.instrument.InstrumentMethod;import com.navercorp.pinpoint.bootstrap.instrument.Instrumentor;import com.navercorp.pinpoint.bootstrap.instrument.MethodFilters;import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformCallback;import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplate;import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplateAware;import com.navercorp.pinpoint.bootstrap.logging.PLogger;import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext;import com.navercorp.pinpoint.common.trace.AnnotationKey;import com.navercorp.pinpoint.common.trace.AnnotationKeyFactory;import com.navercorp.pinpoint.common.trace.ServiceType;import com.navercorp.pinpoint.common.trace.ServiceTypeFactory;
import java.security.ProtectionDomain;
/** * @author willzhao */public class BizlogPlugin implements ProfilerPlugin, TransformTemplateAware { //BIZLOG_SERVICE_TYPE是bizlog插件的身份定义,用了1998这个id public static final ServiceType BIZLOG_SERVICE_TYPE = ServiceTypeFactory.of(1998, "BIZLOG");
//BIZLOG_ANNOTATION_KEY_INFO是打算在pinpoint追踪信息中显示的属性的定义,用了9998这个id public static final AnnotationKey BIZLOG_ANNOTATION_KEY_INFO = AnnotationKeyFactory.of(9998, "bizlog.info", com.navercorp.pinpoint.common.trace.AnnotationKeyProperty.VIEW_IN_RECORD_SET);
private static final String BIZLOG_SCOPE = "BIZLOG_SCOPE";
private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
private TransformTemplate transformTemplate;
@Override public void setup(ProfilerPluginSetupContext context) { //Logger类被加载的时候,会注入这里new的TransformCallback,对这个类的实例在线程中的行为进行拦截 transformTemplate.transform("ch.qos.logback.classic.Logger", new TransformCallback() {
@Override public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException { InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer); //找到所有名为info的方法 for (InstrumentMethod m : target.getDeclaredMethods(MethodFilters.name("info"))) { ////注入Interceptor,在Logger类的实例执行info方法的时候会执行这个interceptor m.addScopedInterceptor("com.navercorp.pinpoint.plugin.bizlog.interceptor.BizlogInterceptor", BIZLOG_SCOPE); }
return target.toBytecode(); } }); }

@Override public void setTransformTemplate(TransformTemplate transformTemplate) { this.transformTemplate = transformTemplate; }}
复制代码


  • 以上方法对 ch.qos.logback.classic.Logger 类就行了注入,在 Logger 类的实例的 info 方法被调用时注入的 Interceptor 就会被执行;

开发元信息类

  • 在 src\main\java\com\navercorp\pinpoint\plugin\bizlog 目录下创建元信息类 BizlogMetadataProvider.java:


package com.navercorp.pinpoint.plugin.bizlog;
import com.navercorp.pinpoint.common.trace.TraceMetadataProvider;import com.navercorp.pinpoint.common.trace.TraceMetadataSetupContext;
/** * @author willzhao */public class BizlogMetadataProvider implements TraceMetadataProvider { /** * @see TraceMetadataProvider#setup(TraceMetadataSetupContext) */ @Override public void setup(TraceMetadataSetupContext context) { //设定当前插件的ServiceType,既插件的唯一身份 context.addServiceType(BizlogPlugin.BIZLOG_SERVICE_TYPE); //设定当前插件要展示的参数 context.addAnnotationKey(BizlogPlugin.BIZLOG_ANNOTATION_KEY_INFO); }}
复制代码


  • 以上方法会被 pinpoint 调用,这样 pinpoint 就知道了我们这次新增的这个插件了,以及我们要在 pinpoint 中显示的参数;

拦截器 BizlogInterceptor

  • 拦截器是 Logger 类被加载的时候被 pinpoint 注入的,被拦截的方法在执行前后所做的事情都在拦截器中定义,以下就是 BizlogInterceptor:


package com.navercorp.pinpoint.plugin.bizlog.interceptor;
import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor;import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;import com.navercorp.pinpoint.bootstrap.context.Trace;import com.navercorp.pinpoint.bootstrap.context.TraceContext;import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor;import com.navercorp.pinpoint.bootstrap.logging.PLogger;import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;import com.navercorp.pinpoint.plugin.bizlog.BizlogPlugin;
/** * logger info method interceptor * * @author willzhao */public class BizlogInterceptor implements AroundInterceptor { private final TraceContext traceContext; private final MethodDescriptor descriptor; private final PLogger logger = PLoggerFactory.getLogger(getClass());
public BizlogInterceptor(TraceContext traceContext, MethodDescriptor descriptor) { this.traceContext = traceContext; this.descriptor = descriptor; }
private static boolean shouldTrace(Object[] args){ return null!=args && args.length>0 && (args[0] instanceof String) && ((String)args[0]).indexOf("pinpoint_bizlog_name")>-1; }
@Override public void before(Object target, Object[] args) { if (logger.isDebugEnabled()) { logger.beforeInterceptor(target, args); }
final Trace trace = traceContext.currentTraceObject(); if (trace == null) { return; }
if(!shouldTrace(args)){ return; }
trace.traceBlockBegin();
}
@Override public void after(Object target, Object[] args, Object result, Throwable throwable) { if (logger.isDebugEnabled()) { logger.afterInterceptor(target, args); }
Trace trace = traceContext.currentTraceObject(); if (trace == null) { return; }
if(!shouldTrace(args)){ return; }
try { SpanEventRecorder recorder = trace.currentSpanEventRecorder(); recorder.recordServiceType(BizlogPlugin.BIZLOG_SERVICE_TYPE); recorder.recordApi(descriptor); recorder.recordException(throwable); recorder.recordAttribute(BizlogPlugin.BIZLOG_ANNOTATION_KEY_INFO, args[0]); } finally { trace.traceBlockEnd(); } }}
复制代码


  • 上述的代码中,before 和 after 方法分别代表 logger.info 方法执行前和执行后拦截器所做的事情,shouldTrace 方法检查入参中是否有"pinpoint_bizlog_name"前缀,如果没有就不执行拦截操作了,如果有,就执行 trace 操作,recorder.recordAttribute 会将入参记录并在 pinpoint 追踪信息中展示出来;

plugins 工程的配置

  • 由于我们新建的 bizlog 工程和其他插件工程一样是 plugins 的子工程,为了能构建和打包,要在 plugins 工程中配置,打开 plugins 文件夹下的 pom.xml 文件:

  • 首先,在 modeles 节点中增加以下内容:


<module>bizlog</module>
复制代码


然后,在 dependencies 节点增加以下内容:


<dependency>  <groupId>com.navercorp.pinpoint</groupId>  <artifactId>pinpoint-bizlog-plugin</artifactId>  <version>${project.version}</version></dependency>
复制代码

开发小结

  • 除了修改 plugins 目录下的 pom.xml 文件,本次开发的插件一共需要新增六个文件:


  1. 插件的 pom.xml;

  2. com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin:定义插件功能类

  3. com.navercorp.pinpoint.common.trace.TraceMetadataProvider:定义插件元信息类

  4. BizlogPlugin.java:插件功能类,确定对哪个类的哪些方法做拦截

  5. BizlogMetadataProvider.java:元信息类:确定插件 id 和插件参数 id

  6. BizlogInterceptor.java:拦截类:定义在拦截的时候做什么事情

bizlog 插件源码下载

  • bizlog 的源码可以在我的 git 下载,地址是:git@github.com:zq2599/pinpoint163-plugin-develop.git

  • 这里面包含了完整的 pinpoint1.6.X 分支的源码,bizlog 的在 plugins 目录下,如下图红框所示:


编译构建 bizlog 插件的环境

  • 开发已经完成,接下来就是编译构建 bizlog 插件了,推荐在 Docker 环境去构建,在 Docker 构建 pinpoint 插件的方法请参照《Docker下,极速体验编译pinpoint1.6.x分支》,和文中略有区别的是,为了方便复制文件我们用以下命令来启动容器(多了个-v 参数):


docker run --name=ppcompile001 -p 19003:22 -idt -v c:/share:/usr/Downloads bolingcavalry/jdk7-mvn339-pinpoint16x-compile:0.0.1
复制代码


  • 这样启动后,当前电脑的 c:/share 目录和容器的/usr/Downloads 目录实际上是同一个位置了;

开始编译构建

  • 编译 pinpoint 插件的 Docker 环境准备好后,我们把 bizlog 插件相关的内容都移植过来吧:


  1. 像上面那样修改 ppcompile001 容器中 plugins 目录下的 pom.xml 文件,给<modeles>节点和<dependency>节点增加内容;

  2. 将前面做好的 bizlog 目录整体复制到 ppcompile001 容器中的 plugins 目录下;

  3. 在 pinpoint 目录下执行编译命令:mvn install -Dmaven.test.skip=true -e

  4. 编译成功后,在 bizlog/target 目录下可以看到最新的插件,如下图红色字体所示:

准备 pinpoint 环境

  • 为了验证 bizlog 插件,我们要有包含以下功能的环境:


  1. 有 pinpoint server;

  2. 有 pinpoint agent;

  3. pinpoint agent 上部署了 web 应用,能被 pinpoint 追踪;


  • 如何快速准备好这样一套环境呢?请参照《Docker下,极速体验pinpoint1.6.3》一文,能够以最快速度将 pinpoint server 和 pinpoint agent 搭建好,然后把 web 应用部署到 pinpoint agent 上;

部署 bizlog 插件


  1. 进入 pinpoint server 容器,在 pinpoint-collector 和 pinpoint-web 两个 tomcat server 的**apache-tomcat-8.0.36/webapps/ROOT/WEB-INF/lib/**目录下,放置 bizlog 的 jar 包;

  2. 重启 collector 和 web 两个 tomcat;

  3. 进入 tomcat001 容器,在**pinpoint-agent-1.6.3/plugin/**目录下放置 bizlog 的 jar 包;

  4. 重启 tomcat001 容器;

验证 bizlog 插件

  • 部署在 tomcat001 上的 web 应用中,有下面这段代码:


public String tracegson(HttpServletRequest request, Model model) {        String name = get(request, "name");        String age = get(request, "age");
Student student = new Student(); student.setName(name); student.setAge(Integer.valueOf(age));
Gson gson = new Gson();
String parseStr = gson.toJson(student, Student.class);
logger.info("gson str [{}]", parseStr);
return String.format("gson str : %s [%s]", parseStr, tag()); }
复制代码


  • 为了验证 bizlog 插件,在方法 return 之前加了下面这两句,两次执行 logger.info 方法,第一次带上了 pinpoint_bizlog_name 前缀,第二次没有带:


logger.info("pinpoint_bizlog_name 1. [" + name + "], age [" + age + "]");
logger.info("2. [" + name + "], age [" + age + "]");
复制代码


  • 加上之后,将 web 应用部署到 tomcat001 容器上,访问地址:http://localhost:8081/pinpointtracedemo/tracegson?name=tom&age=11

  • 然后去 pinpoint 上看一下,如下图:


  • 可以看到我们的插件已经出现在红框位置,而且只将“pinpoint_bizlog_name”前缀的 log 信息打印出来,今后需要通过 pinpoint 追踪的信息,都可以通过 logger.info("pinpoint_bizlog_xxxxxx 的方式来实现;

  • 以上就是开发一个完整插件的过程,希望能对您有所帮助,也祝您顺利开发出自己需要的插件;

了解 pinpoint 编译环境的更多细节

了解 pinpoint server、pinpoint agent 部署的更多细节


欢迎关注 InfoQ:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
pinpoint插件开发之二:从零开始新建一个插件_Java 分布式_程序员欣宸_InfoQ写作社区