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

听闻少年二字,当与平庸相斥。这篇文章主要讲述pinpoint插件开发之二:从零开始新建一个插件相关的知识,希望能为你提供帮助。
欢迎访问我的GitHub
本篇概览

  • 在上一章《pinpoint插件开发之一:牛刀小试,调整gson插件》我们初步体验了pinpoint插件的构建和使用,本章我们从零开始创建一个全新插件,体验完整的插件开发过程;
开发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方法入参的字符串,如果以pinpointbizlog为前缀,那么我们的插件才做拦截,将日志的信息打印出来,其他的保持原样;
  • 这样只要业务执行诸如logger.info(" pinpointbizlog" + " userid :" + userid);这样的代码,我们的插件就会将userid : xxx这样的字符串在pinpoint的追踪信息中显示出来;
开始吧
  • 功能已经设计好了,那我们就开始吧,给插件起个名字:bizlog
下载pinpoint源码
  • 目前pinpoint的稳定版本是1.6.x,可以用git clone命令,也可以在git网站上将其整体打包下载下来,地址是:https://github.com/naver/pinpoint/tree/1.6.x,下载方法如下图:
    pinpoint插件开发之二(从零开始新建一个插件)

    文章图片
导入到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目录,如下图:
pinpoint插件开发之二(从零开始新建一个插件)

文章图片

  • 文件名: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:```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目录下,如下图红框所示:
    pinpoint插件开发之二(从零开始新建一个插件)

    文章图片
编译构建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插件开发之二(从零开始新建一个插件)

      文章图片
准备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插件
  • 部署验证bizlog插件的方法和上一章《pinpoint插件开发之一:牛刀小试,调整gson插件》一样,准备好pinpoint的将.jar文件复制到pinpoint环境中的server和agent上:
    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 + "]");

【pinpoint插件开发之二(从零开始新建一个插件)】logger.info(" 2. [" + name + " ], age [" + age + " ]" );
- 加上之后,将web应用部署到tomcat001容器上,访问地址:http://localhost:8081/pinpointtracedemo/tracegson?name=tom& age=11- 然后去pinpoint上看一下,如下图: ![这里写图片描述](https://s4.51cto.com/images/blog/202204/29095719_626b45ff2167d97255.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)- 可以看到我们的插件已经出现在红框位置,而且只将“pinpoint_bizlog_name”前缀的log信息打印出来,今后需要通过pinpoint追踪的信息,都可以通过logger.info("pinpoint_bizlog_xxxxxx的方式来实现;- 以上就是开发一个完整插件的过程,希望能对您有所帮助,也祝您顺利开发出自己需要的插件;### 了解pinpoint编译环境的更多细节 - 如果您对pinpoint编译环境的打造有兴趣,例如在pinpoint发布新版本的时候亲自打造一套编译构建环境,可以看一下文章[《把pinpoint编译环境做成Docker镜像文件》](https://blog.51cto.com/u_13674465/5256651);### 了解pinpoint server、pinpoint agent部署的更多细节 - 如果您想亲自打造一套pinpoint server,pinpoint agent,并且在agent上部署应用,您可以看这篇文章[《Docker下,pinpoint环境搭建》](https://blog.51cto.com/u_13674465/5258713);### 欢迎关注51CTO博客:程序员欣宸 > [学习路上,你不孤单,欣宸原创一路相伴...](https://blog.51cto.com/u_13674465)


    推荐阅读