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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 从零开始新建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源码

这里写图片描述

导入到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文件,给节点和节点增加内容;
  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部署的更多细节

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
负载均衡 数据可视化 NoSQL
强烈推荐,好用的时序图开源插件PlantUML!
PlantUML这个开源时序图插件,它通过简单的语法和自动化的图形线条关联解决了传统画图软件中对齐困难、逻辑判断不易表示等问题,并提供了美观的图形和易于修改的特点,特别适合新入职场的开发者快速上手绘制高质量的时序图。
强烈推荐,好用的时序图开源插件PlantUML!
|
3月前
|
jenkins 持续交付
jenkins学习笔记之六:共享库方式集成构建工具
jenkins学习笔记之六:共享库方式集成构建工具
|
5月前
|
NoSQL Java Redis
若依如何使用(基本环境的配置)
若依如何使用(基本环境的配置)
|
消息中间件 自然语言处理 Java
ElasticSearch 学习笔记(四)-----ES在SpringBoot中的集成以及项目应用开发指南
接上一篇ElasticSearch 学习笔记(三)-----ES的设计原理以及分词器说明。今天我们主要介绍ES 与SpringBoot 的集成以及项目应用开发指南。
895 0
ElasticSearch 学习笔记(四)-----ES在SpringBoot中的集成以及项目应用开发指南
|
Kubernetes API 数据安全/隐私保护
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
你有没有想过, 如果要在 kubernetes 集群中 **发布** 一个最基本的 **无状态服务**, 并 **提供** 给用户访问, 最少需要配置几个 `K8S Config API` ? 自己写一个, 提升自己。
234 0
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
|
存储 移动开发 小程序
uniapp环境搭建以及基础配置详解
安装编辑器 HbuilderX(HbuilderX 是通用的前端开发工具,但为 uni-app 做了特别强化)。 下载 APP 开发板,可开箱即用。 安装微信开发者工具。
788 0
uniapp环境搭建以及基础配置详解
|
资源调度
插件开发步骤
插件开发步骤
138 0
|
监控 JavaScript Dubbo
pinpoint插件开发实践
Pinpoint是一款全链路分析工具,提供了无侵入式的调用链监控、方法执行详情查看、应用状态信息监控等功能。基于Google Dapper论文进行的实现。
5782 0
|
机器学习/深度学习 NoSQL Java
《IntelliJ IDEA 插件开发》第四节:扩展创建工程向导步骤,开发DDD脚手架
一、前言 二、需求目的 三、案例开发 1. 工程结构 2. UI 工程配置窗体 3. 配置工程步骤创建 4. 开发脚手架服务 5. 调用脚手架服务 6. 配置模板工程 四、测试验证 五、总结 六、系列推荐
880 0
《IntelliJ IDEA 插件开发》第四节:扩展创建工程向导步骤,开发DDD脚手架
|
Java 测试技术 API
《IntelliJ IDEA 插件开发》第一节:两种方式创建插件工程
一、前言 二、需求目的 三、环境说明 四、模板方式创建 1. 创建引导 2. 工程结构 3. plugin.xml 配置 4. MyAction 事件入口 5. 运行测试 五、Gradle 方式创建 1. 创建引导 2. 工程结构 3. build.gradle 配置 4. MyAction 事件入口 5. 运行测试 六、总结
1352 0
《IntelliJ IDEA 插件开发》第一节:两种方式创建插件工程