PDF书籍《手写调用链监控APM系统-Java版》第8章 插件与链路的结合:Gson插件实现

本人阅读了 Skywalking 的大部分核心代码,也了解了相关的文献,对此深有感悟,特此借助巨人的思想自己手动用JAVA语言实现了一个 "调用链监控APM" 系统。本书采用边讲解实现原理边编写代码的方式,看本书时一定要跟着敲代码。

作者已经将过程写成一部书籍,奈何没有钱发表,如果您知道渠道可以联系本人。一定重谢。

本书涉及到的核心技术与思想

JavaAgent , ByteBuddy,SPI服务,类加载器的命名空间,增强JDK类,kafka,插件思想,切面,链路栈等等。实际上远不止这么多,差不多贯通了整个java体系。

适用人群

自己公司要实现自己的调用链的;写架构的;深入java编程的;阅读Skywalking源码的;

版权

本书是作者呕心沥血亲自编写的代码,不经同意切勿拿出去商用,否则会追究其责任。

原版PDF+源码请见:

本章涉及到的工具类也在这里面:

PDF书籍《手写调用链监控APM系统-Java版》第1章 开篇介绍-CSDN博客

第8章 插件与链路的结合:Gson插件实现

Gson是一个json解析工具,也没跨线程或进程调用,非常适合LocalSpan案例。

Gson在将json字符串反序列化成对象时,执行的类和方法信息如下:

类名:com.google.gson.Gson

方法:fromJson(第一个参数为JsonReader.class)

非JDK类库

Gson在将对象序列化成json字符串时,执行的类和方法信息如下:

类名:com.google.gson.Gson

方法:toJson (第二个参数为JsonWriter.class)

非JDK类库

在插件apm-agent-plugins 聚合模块下新建一个gson-plugin插件项目。POM文件添加gosn依赖,内容如下:

复制代码
<dependencies>
    <dependency>
        <groupId>com.hadluo.apm</groupId>
        <artifactId>apm-commons</artifactId>
        <version>1.0</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

新增插件定义类GsonFromInstrumentation:

复制代码
public class GsonFromInstrumentation extends AbstractClassEnhancePluginDefine {
    @Override
    public String enhanceClass() {
        // 拦截的类
        return "com.google.gson.Gson";
    }
    @Override
    public MethodsInterceptPoint[] configMethodsInterceptPoint() {
        return new MethodsInterceptPoint[]{
                new MethodsInterceptPoint() {
                    @Override
                    public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        // 拦截fromJson方法,且第一个参数为JsonReader类型
                        return ElementMatchers.named("fromJson").and(ElementMatchers.takesArgument(0, JsonReader.class));
                    }
                    @Override
                    public String getMethodsInterceptor() {
                        // 拦截逻辑执行的拦截器
                        return "com.hadluo.apm.plugin.gson.GsonFromInterceptor";
                    }
                    @Override
                    public boolean isOverrideArgs() {
                        return false;
                    }
                }
        };
    }
}

同理新增GsonToInstrumentation。

新增插件定义配置文件hadluo-apm-plugin.def,内容:

复制代码
gson-from=com.hadluo.apm.plugin.gson.GsonFromInstrumentation
gson-to=com.hadluo.apm.plugin.gson.GsonToInstrumentation

新增拦截逻辑执行的拦截器GsonFromInterceptor :

复制代码
public class GsonFromInterceptor implements InstanceMethodsAroundInterceptor {
    @Override
    public void beforeMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes) throws Throwable {
        // 创建 local span
        TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);
        AbstractSpan localSpan = manager.createLocalSpan("Gson/FromJson");
        localSpan.setComponent("GSON");
        int length = allArguments[0].toString().length();
        localSpan.setTag("length" , Integer.toString(length)) ;
    }
    @Override
    public Object afterMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
        TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);
        manager.stopSpan();
        return ret;
    }
    @Override
    public void handleMethodException(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
        TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);
        manager.activeSpan().log(t) ;
    }
}

GsonFromInterceptor 在beforeMethod中创建了LocalSpan,tag信息你可以自己定义,我这里就随便取了长度。在异常时采集log到span里面,在afterMethod中stopSpan。

同理我们可以编写出GsonToInterceptor,代码如下:

复制代码
public class GsonToInterceptor implements InstanceMethodsAroundInterceptor {
    @Override
    public void beforeMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes) throws Throwable {
        // 创建 local span
        TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);
        AbstractSpan localSpan = manager.createLocalSpan("Gson/ToJson");
        localSpan.setComponent("GSON");
        int length = allArguments[0].toString().length();
        localSpan.setTag("length" , Integer.toString(length)) ;
    }
    @Override
    public Object afterMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {
        TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);
        manager.stopSpan();
        return ret;
    }
    @Override
    public void handleMethodException(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {
        TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);
        manager.activeSpan().log(t) ;
    }
}

到此gson插件代码编写完成,根据四部曲完善其它配置,我就不讲解了。

修改测试controller代码,在接口中进行json操作:

复制代码
@GetMapping("/order")
public String order(@RequestParam("shopId")String shopId) throws ClassNotFoundException {
    System.out.println("下单请求 商品ID:" + shopId);
    Gson gson = new Gson();
    String json = gson.toJson(shopId);
    gson.fromJson(json, String.class);
    return UUID.randomUUID().toString();
}

打包测试,结果

复制代码
{
    "msgTypeClass": "com.hadluo.apm.commons.kafka.Segment",
    "sampleTime": 1733369232204,
    "serviceName": null,
    "serviceInstance": "bc640bee591447c2869b1c66ef2907be@192.168.2.233",
    "traceId": "495c2b8bbf49424baa796b43726d256a.44.17333692321780001",
    "traceSegmentId": "495c2b8bbf49424baa796b43726d256a.44.17333692321780000",
    "spans": [
        {
            "spanId": 1,
            "parentSpanId": 0,
            "startTime": 0,
            "endTime": 1733369232195,
            "refs": [

            ],
            "operationName": "Gson/ToJson",
            "peer": null,
            "spanType": "Local",
            "spanLayer": null,
            "component": "GSON",
            "tags": {
                "length": "2"
            },
            "logs": {

            }
        },
        {
            "spanId": 2,
            "parentSpanId": 0,
            "startTime": 0,
            "endTime": 1733369232195,
            "refs": [

            ],
            "operationName": "Gson/FromJson",
            "peer": null,
            "spanType": "Local",
            "spanLayer": null,
            "component": "GSON",
            "tags": {

            },
            "logs": {

            }
        },
        {
            "spanId": 0,
            "parentSpanId": -1,
            "startTime": 1733369232179,
            "endTime": 1733369232204,
            "refs": [

            ],
            "operationName": "/order",
            "peer": null,
            "spanType": "Entry",
            "spanLayer": "HTTP",
            "component": "Tomcat",
            "tags": {
                "http.method": "GET",
                "url": "/order"
            },
            "logs": {

            }
        }
    ]
}

结果分析:

由于tomcat插件还存在,所以第一个span为EntrySpan,然后是toJson的LocalSpan,最后是fromJson的LocalSpan 。 分析上述json数据也是如此。

相关推荐
考虑考虑3 分钟前
Jpa使用union all
java·spring boot·后端
用户37215742613525 分钟前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊1 小时前
Java学习第22天 - 云原生与容器化
java
渣哥3 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧4 小时前
Spring Secutiy基本原理及工作流程
java
Java水解5 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆7 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学7 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole7 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端