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'

skywalking自定义插件开发的更多相关文章

  1. Cordova自定义插件开发

    Cordova自定义插件开发 一.创建Cordova项目 在创建项目前请确保安装Cordova Cordova环境配置:https://www.w3cschool.cn/cordova/cordova ...

  2. ionic2踩坑之自定义插件开发及调用

    关于ionic2自定义插件开发的文章,插件怎么调用的文章,好像网上都有,不过作为一个新手来说,从插件的开发到某个页面怎么调用,没有一个完整的过程的话,两篇没有关联的文章也容易看的迷糊.这里放到一起来方 ...

  3. cordova自定义插件开发流程

    cordova自定义插件开发:1.cordova安装:npm install -g cordova2.plugman安装:npm install -g plugman3.cordova创建工程:cor ...

  4. JQUERY 插件开发——MENU(导航菜单)

    JQUERY 插件开发——MENU(导航菜单) 故事背景:由于最近太忙了,已经很久没有写jquery插件开发系列了.但是凭着自己对这方面的爱好,我还是抽了一些时间来过一下插件瘾的.今天的主题是导航菜单 ...

  5. Sonar 平台搭建及 Sonar 自定义规则打包部署篇

    引言 基于阿里开发手册的sonar自定义插件工程 开源地址: https://github.com/tigerge000/sonar-java-custom-rules.git由于最近来问童鞋,就算写 ...

  6. ElasticSearch 5.4 自定义插件

    ElasticSearch 做为数据仓库处理速度确实很强,但是很多和业务相关的函数ElasticSearch怎么支持的,通过查询发现,ElasticSearch支持自定义插件(相当于自定义函数),通过 ...

  7. Qt自定义插件编程小结

    qt自定义组件开发步骤演示.以下所有步骤的前提是自己先编译Qtcreator源码,最好生成release版的QtCreator,否则自定义的插件嵌入QtCreator会失败!!!(这个网上教程很多) ...

  8. 【转】持续集成 Sonar 平台搭建及 Sonar 自定义规则打包部署篇

    引言 基于阿里开发手册的sonar自定义插件工程 开源地址: https://github.com/tigerge000/sonar-java-custom-rules.git由于最近来问童鞋,就算写 ...

  9. mybatis ResultHandler vs ResultSetHandler及自定义扩展

    ResultSetHandler是mybatis的关键类之一,用于对jdbc返回的ResultSet进行映射处理,其中包括列前缀处理,逻辑分页,鉴别器(Discriminator,基于值实现动态映射列 ...

  10. 一、VIP课程:互联网工程专题 04-Maven私服使用与插件开发

    第四课:Maven私服构建与插件开发.docx 一.maven 生命周期 知识点概要: 生命周期的概念与意义 maven 三大生命周期与其对应的phase(阶段) 生命周期与插件的关系 生命周期与默认 ...

随机推荐

  1. 文心一言 VS 讯飞星火 VS chatgpt (79)-- 算法导论7.4 4题

    四.请用go语言,证明:RANDOMIZED-QUICKSORT 期望运行时间是 Ω(nlgn). 文心一言: 首先,为了证明RANDOMIZED-QUICKSORT的期望运行时间是Ω(nlg n), ...

  2. 【io_uring】liburing 用户库源码分析

    文章目录 整体流程 `io_uring_queue_init` `io_uring_get_sqe` `io_uring_prep_#OP` `io_uring_sqe_set_data` `io_u ...

  3. Socket.io入门

    Socket.io入门 根据官方文档socket.io使用必须客户端根服务端一致,socket.io不兼容webSocket或者其他模块,因为socket.io在连接时做了自定义处理, 所以不同的长连 ...

  4. 【matplotlib基础】--图例

    Matplotlib 中的图例是帮助观察者理解图像数据的重要工具.图例通常包含在图像中,用于解释不同的颜色.形状.标签和其他元素. 1. 主要参数 当不设置图例的参数时,默认的图例是这样的. impo ...

  5. Record - Dec. 2st, 2020 - Exam. REC

    Prob. 1 Desc. & Link. 有一个基础想法,即一次操作三可以用一次操作一加上一次操作二来实现,然后他又没让我们最小化操作次数,所以我们令 \(M=\min\{A+R,M\}\) ...

  6. Solution -「九省联考 2018」IIIDX

    Description Link. 给出一个堆,然后让你填数进去,使得其满足小根堆的性质,并使编号靠前的点的数最大. Solution 考虑贪心,把原数列降序排序,然后因为这个东西是整除分块的形式,所 ...

  7. AcWing - 闫氏DP分析法

    核心思想:从集合角度来分析DP问题 在我们遇到的DP问题中,一般都是求在一个有限集内的最值,但是这些方案数量一般都是指数级别的,想要一个一个查找出来不太可能.所以DP方法是用来优化这种寻找最优方案的过 ...

  8. android图片缩放双击旋转效果

    需要jar源码的请留言吧. 部分源码    demo下载地址 package uk.co.senab.photoview.sample; import android.app.ListActivity ...

  9. The method dismissDialog(int) from the type Activity is deprecated

    The method showDialog(int) from the type Activity is deprecated in android?   up vote6down votefavor ...

  10. MongoDB 中的索引分析

    MongoDB 的索引 前言 MongoDB 使用 B 树还是 B+ 树索引 单键索引 创建单键索引 使用 expireAfterSeconds 创建 TTL 索引 复合索引 最左匹配原则 ESR 规 ...