skywalking自定义插件开发

skywalking是使用字节码操作技术和AOP概念拦截Java类方法的方式来追踪链路的,由于skywalking已经打包了字节码操作技术和链路追踪的上下文传播,因此只需定义拦截点即可。

这里以skywalking-8.7.0版本为例。
关于插件拦截的原理,可以看我的另一篇文章:skywalking插件工作原理剖析

1. 创建插件模块

apm-sniffer/apm-sdk-plugin 目录下创建一个插件maven子模块。

2. 插件开发

(1)思路

  1. 定义拦截点,通常是类的方法
  2. 定义拦截器,支持在拦截方法执行前后进行日志采集
  3. 定义配置文件,启用拦截点
  4. 编译打包,将生成的jar放到探针的plugins目录下

(2)定义拦截点

① 官方提供的拦截点扩展入口

skywalking提供了2种供扩展的拦截点:

  • ClassInstanceMethodsEnhancePluginDefine:支持定义构造方法和实例方法的拦截点。
  • ClassStaticMethodsEnhancePluginDefine:支持定义静态方法的拦截点。

当然还可以直接扩展 ClassEnhancePluginDefine,这个类是上面两个类的父类。这种方式较为麻烦,一般不推荐使用。

这里以拦截实例方法为例,继承 ClassInstanceMethodsEnhancePluginDefine 类。

② 类拦截规则

skywalking提供了4种类拦截的规则:

  • byName:类名匹配(包名+类名)
  • byClassAnnotationMatch:类注解匹配
  • byMethodAnnotationMatch:方法注解匹配
  • byHierarchyMatch:父类或接口匹配

注意:

  1. 这里的匹配规则要用字符串,不要用类引用的方式(byName(ThirdPartyClass.class.getName())),否则可能会导致探针异常。
  2. 注解匹配的方式,不支持继承的注解
  3. 父类或接口匹配的方法,尽量避免使用,否则可能会出现一些难以预料的问题

③ 设置要拦截的类名

实现 enhanceClass() 方法,定义要拦截的类名,必须是全路径的名称,即包名+类名。

④ 设置拦截的实例方法和拦截器的类名

实现 getInstanceMethodsInterceptPoints() 方法,定义要拦截的实例方法,以及对应拦截器的类名。拦截器类名也是包名+类名。

这里支持定义多个实例方法,每个实例方法可以使用不同的拦截器。还支持拦截私有方法(private)。

⑤ 代码示例

下面的代码实现的功能是:使用拦截器 MingBaoServiceInterceptor 拦截 MingBaoService 类的 service 方法。

public class MingBaoServiceInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
    // 要拦截的类
    private static final String ENHANCE_CLASS = "com.mingbao.service.MingBaoService";
    // 拦截器的类名
    private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.mingbao.service.MingBaoServiceInterceptor";

    /**
     * 定义要拦截的类名
     */
    @Override
    protected ClassMatch enhanceClass() {
        return NameMatch.byName(ENHANCE_CLASS);
    }
    /**
     * 定义要拦截类的方法,以及对应的拦截器
     */
    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
                new InstanceMethodsInterceptPoint() {
                    @Override
                    public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        // 这里是要拦截的方法
                        return named("service");
                    }
                    @Override
                    public String getMethodsInterceptor() {
                        // 定义拦截器的类名
                        return INTERCEPT_CLASS;
                    }
                    @Override
                    public boolean isOverrideArgs() {
                        // 如果有要改方法参数的需求,这里可以设置成true
                        return false;
                    }
                }
        };
    }
    /**
     * 这里是拦截构造方法,忽略
     */
    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { return null; }
}

(3)定义拦截器

① 3种常见的拦截器接口

  • InstanceMethodsAroundInterceptor:实例方法拦截器
  • StaticMethodsAroundInterceptor:静态方法拦截器
  • InstanceConstructorInterceptor:构造方法拦截器

要拦截对应的方法,必须要实现对应的接口。这里以实现 InstanceMethodsAroundInterceptor 接口,拦截实例方法为例。

② 在方法执行前拦截

实现 beforeMethod(EnhancedInstance, Method, Object[], Class<?>[], MethodInterceptResult) 方法,此方法会在被拦截的方法执行前执行。此方法中一般是定义日志链路节点span对象,一个span对象对应着日志链路中的一个节点。

  • 拦截方法参数说明:
1.EnhancedInstance objInst:被增强的实例,一般用不上
2.Method method:被拦截的方法
3.Object[] allArguments:被拦截方法的入参
4.Class<?>[] argumentsTypes:被拦截方法的入参的类型
5.MethodInterceptResult result:此参数可以作为被拦截方法的返回参数,如果给此参数赋值了,会阻断被拦截方法的执行,直接返回此参数。
	可以通过defineReturnValue()方法来定义要返回的数据。
  • 链路节点对象span的类型:

节点对象都实现了 AbstractSpan 接口,可以借助ContextManager类来创建和获取节点对象。

创建一个span对象后,就会生成一个链路日志的节点。

1.EntrySpan:入口层span,它会作为一条链路的起点。如接收Http请求的接口层、Dubbo服务的提供方以及MQ消费者。
2.LocalSpan:中间层span,它会出现在链路的中间节点上。如一个业务方法被调用。
3.ExitSpan:出口层span,它会作为一条链路的终点。如发送Http请求的工具、Dubbo服务的调用方以及MQ生产者。
  • 链路节点对象span常用的设置项
1.component:组件类型,比如说Tomcat、Dubbo、SpringMVC...可以从ComponentsDefine类中定义好的一些官方组件类型中选,自定义的组件类型是无法在UI中显示出来的。也可以不设置值,默认会显示Unknown。(可选的类型就那么多,一般自定义时根本找不到合适的)。
2.layer:日志层级,可以从SpanLayer类中选择,一共就5个:DB、RPC_FRAMEWORK、HTTP、MQ和CACHE。可选的也不多,不合适可以不设置,默认会显示Unknown。
3.tag:日志标签,支持自定义日志字段,可以通过 span.tag(new StringTag("msg"), msg) 的方式来设置。结合后端配置项 core.default.searchableTracesTags可以达到自定义字段搜索的目的。

③ 在方法执行后拦截

实现 afterMethod(EnhancedInstance, Method, Object[], Class<?>[], Object) 方法,此方法会在被拦截的方法执行前执行。此方法中一般是将方法的返回数据记录到链路节点对象中。

  • 拦截方法参数说明:
前4个参数和beforeMethod()方法中一样,介绍下最后那个参数
Object ret:方法的返回数据

④ 代码示例

public class MingBaoServiceInterceptor implements InstanceMethodsAroundInterceptor {
    private static final Gson GSON = new Gson();
    /**
     * 在拦截方法前执行
     */
    @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
        // 解析方法入参
        String message = (String) allArguments[0];

        // 初始化span,这里创建了一个EntrySpan
        ContextCarrier contextCarrier = new ContextCarrier();
        AbstractSpan span = ContextManager.createEntrySpan(MethodUtil.generateOperationName(method), contextCarrier);
        // 自定义标签,记录方法入参
        span.tag(new StringTag("req"), req);

        // 下面的参数如果不合适可以不设置
        span.setLayer(SpanLayer.MQ);
        span.setComponent(new OfficialComponent(999, "mbService"));
    }
    /**
     * 在拦截方法后执行
     */
    @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
        // 获取上下文中的span对象
        AbstractSpan span = ContextManager.activeSpan();
        // 自定义标签,记录方法出参
        span.tag(new StringTag("resp"), GSON.toJson(ret));

        // 停止日志记录,移除上下文
        ContextManager.stopSpan();
        // 返回方法出参
        return ret;
    }
    /**
     * 记录方法异常
     */
    @Override
    public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
        ContextManager.activeSpan().log(t);
    }
}

(4)定义配置文件

在自定义插件模块的resources目录下定义 skywalking-plugin.def 配置文件,该文件用于帮助探针启动时加载插件时,寻找插件拦截点。

注意:自定义插件时,不管是类还是配置文件,都要把apache的许可证注释带上,可以参考其他插件类文件中最上面被注释的那一段。

mingbao-service=org.apache.skywalking.apm.plugin.mingbao.service.MingBaoServiceInstrumentation

3. 使用插件

(1)插件打包

对自定义的插件子模块执行mvn package操作,构建完成后会生成一个名称类似 mingbao-service-plugin-8.7.0.jar 的jar包,将jar包拷贝到 skywalking-agent/plugins 目录下。

(2)使用自定义插件

自定义插件的使用和自带插件使用方式相同,将 skywalking-agent 打包到项目镜像中,使用javaagent探针启动即可。

这里有个建议:如果使用docker来部署项目,可以将 skywalking-agent 目录放到项目同级目录下,并在项目同级目录下构建docker镜像。因为docker build命令无法操作命令执行目录的父级目录所包含的其他文件。因此要保证 skywalking-agent 要在执行docker build命令的目录下。

4. 检查插件生效

启动项目后,调用被拦截的类方法,然后看UI上是否生成了对应的日志。一般情况UI上会延迟几秒钟才会生成日志。

5. 可能会遇到的问题

(1)插件不生效

在探针的logs目录下(docker镜像部署的项目要先进入镜像才能看到),会生成 skywalking-api.log 目录,日志默认级别为 INFO。插件不生效时,一般情况下,日志文件中一定有错误日志。

  • SecurityException

如果遇到报错:java.lang.SecurityException: Invalid signature file digest for Manifest main attributes,那么一般是因为自定义插件中依赖了第三方依赖包,在打包时生成了 *.SF*.RSA 文件,把上述文件删掉即可。可以使用下面的命令:

zip -d mingbao-service-plugin-8.7.0.jar 'META-INF/*SF' 'META-INF/*RSA'

原文链接:https://www.cnblogs.com/wind-wound/p/17349722.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:skywalking自定义插件开发 - Python技术站

(0)
上一篇 2023年4月25日
下一篇 2023年4月25日

相关文章

  • JDBCTM 指南:入门3 – DriverManager

    下面是详细讲解“JDBCTM 指南:入门3 – DriverManager”的完整攻略。 JDBCTM 指南:入门3 – DriverManager 在本文中,我们将介绍JDBC中的DriverManager类,它是Java SQL API的一个基本组件,用于管理数据库驱动程序。 什么是 DriverManager DriverManager是Java提供的…

    Java 2023年6月16日
    00
  • SpringBoot后端接收数组对象的实现

    下面就是”SpringBoot后端接收数组对象的实现”的完整攻略: 1. 创建后端接口接收数组对象 在SpringBoot中创建后端接收数组对象的接口时,可以使用@RequestParam注解将前端传过来的数组转化为Java中的List对象,示例如下: @PostMapping("/api/saveData") public void s…

    Java 2023年5月20日
    00
  • 浅谈JS如何写出漂亮的条件表达式

    下面是详细讲解“浅谈JS如何写出漂亮的条件表达式”的完整攻略: 1. 使用三元运算符 三元运算符是一种简洁的条件表达式语法,可以用来简化if-else语句的编码。三元运算符包含一个条件判断语句和两个表达式,形式如下: condition ? expression1 : expression2 其中,condition是一个布尔表达式,如果计算结果为true,…

    Java 2023年6月15日
    00
  • 面试题快慢链表和快慢指针

    快慢链表和快慢指针是算法中常见的一种技巧。它们在链表中查找中间节点、判断链表是否有环等情况下十分实用。下面就对快慢链表和快慢指针的使用进行详细讲解。 快慢指针 快慢指针的基本思想是将两个指针指向链表的头节点,快指针每次走两步,慢指针每次走一步,当快指针走到链表的末尾时,慢指针指向的就是链表的中间节点。 示例 1: 找到链表的中间节点 我们有一个链表,包含以下…

    Java 2023年5月19日
    00
  • Java操作redis设置第二天凌晨过期的解决方案

    下面就是Java操作redis设置第二天凌晨过期的解决方案的完整攻略。 准备工作 首先需要引入redis的Java客户端库,如Jedis,Lettuce等,具体可参考官方文档进行引入。 方案一:设置过期时间为当天凌晨 我们可以通过计算当前时间距离当天凌晨的秒数,将该秒数加上一天86400秒作为过期时间,在Redis中进行设置。 示例代码如下: // Jedi…

    Java 2023年5月20日
    00
  • 新浪开源轻量级分布式RPC框架motan简单示例解析

    新浪开源轻量级分布式RPC框架motan简单示例解析 简介 Motan是新浪微博公司开发的一个轻量级分布式RPC框架,主要用于各种服务之间的调用。其定位是一个高性能、易扩展、易用的分布式RPC框架。 安装配置 1. 下载motan 在项目的GitHub页面中,找到 Download 按钮,下载最新版的 motan-x.x.x-release.zip。 2. …

    Java 2023年5月19日
    00
  • java eclipse 中文件的上传和下载示例解析

    Java Eclipse 文件上传和下载说明文档 介绍 在Java程序中,文件的上传和下载是一项重要的功能。Eclipse提供了简单而强大的方式来实现这两个功能。本文将介绍Eclipse中如何通过Java编写代码来实现文件上传和下载,并提供两个示例来帮助您更好地理解这些功能。 文件上传 在Eclipse中,文件上传可以使用Apache Commons Fil…

    Java 2023年6月15日
    00
  • Java异常类型介绍及处理方法

    Java异常类型介绍及处理方法 什么是Java异常 Java异常是程序中出现问题的信号,可以用来指示程序中的错误。它们在程序中自动抛出,也可以使用 throw 关键字手动抛出。在程序中处理异常时,可以使用 try-catch 块来处理异常并且避免程序崩溃。Java中的异常分为两种类型:受检异常和非受检异常。 受检异常 受检异常(Checked Excepti…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部