源码角度了解Skywalking之tomcat插件的实现

举报
周杰伦本人 发表于 2022/10/30 18:10:20 2022/10/30
【摘要】 源码角度了解Skywalking之tomcat插件的实现 ApplicationDispatcherInstrumentation ForwardInterceptor请求转发拦截器 TomcatInstrumentation TomcatInvokeInterceptor拦截器 TomcatExceptionInterceptor拦截器 总结 源码角度了解Skywalking之tomca...

源码角度了解Skywalking之tomcat插件的实现

通过前几篇的文章,我们都知道定义Skywalking的插件都会在resources文件夹下定义一个def文件,标注这个插件的特殊类,来区分插件的不同的版本

tomcat插件也不例外,它的skywalking-plugin.def文件的定义:

tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.TomcatInstrumentation
tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.ApplicationDispatcherInstrumentation

tomcat的插件主要分为两部分

第一部分,ClassInstanceMethodsEnhancePluginDefine的实现类

img

第二部分:InstanceMethodsAroundInterceptor的实现类

img

通过这个类的关系图,我们能看出来ClassInstanceMethodsEnhancePluginDefine的实现类有两个:ApplicationDispatcherInstrumentation和TomcatInstrumentation

通过之前的文章介绍,ClassEnhancePluginDefine在进行拦截构造方法、实例方法、静态方法进行增强的时候,利用模板方法模式获取这三个方法的切入点方法是抽象方法,具体由AbstractClassEnhancePluginDefine的

子类来实现,而ClassInstanceMethodsEnhancePluginDefine实现了获取静态方法的逻辑,直接返回null,继承ClassInstanceMethodsEnhancePluginDefine类的类就直接重写构造方法和实例方法就可以了。tomcat插件中的ApplicationDispatcherInstrumentation和TomcatInstrumentation分别是ClassInstanceMethodsEnhancePluginDefine的子类

ApplicationDispatcherInstrumentation

ApplicationDispatcherInstrumentation主要负责拦截tomcat的ApplicationDispatcher类的所有构造方法和forward()方法,对应的拦截器为ForwardInterceptor

ForwardInterceptor请求转发拦截器

ForwardInterceptor实现了InstanceMethodsAroundInterceptor接口,重写了beforeMethod()方法和afterMethod()方法

ForwardInterceptor的beforeMethod()方法:

public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
    MethodInterceptResult result) throws Throwable {
    if (ContextManager.isActive()) {
        AbstractSpan abstractTracingSpan = ContextManager.activeSpan();
        Map<String, String> eventMap = new HashMap<String, String>();
        eventMap.put("forward-url", objInst.getSkyWalkingDynamicField() == null ? "" : String.valueOf(objInst.getSkyWalkingDynamicField()));
        abstractTracingSpan.log(System.currentTimeMillis(), eventMap);
        ContextManager.getRuntimeContext().put(Constants.FORWARD_REQUEST_FLAG, true);
    }
}
  1. 通过log记录请求转发的url
  2. 添加请求转发的标记到RuntimeContext中

afterMethod()方法就是将转发标记从RuntimeContext中移除。

实现InstanceConstructorInterceptor接口,重写了onConstruct()方法

ForwardInterceptor的onConstruct()方法:

public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
    objInst.setSkyWalkingDynamicField(allArguments[1]);
}

将ApplicationDispatcher的第二个参数也就是请求转发的地址记录到增强字_$EnhancedClassField_ws中。

TomcatInstrumentation

TomcatInstrumentation拦截的是StandardHostValve的invoke()方法,拦截器为TomcatInvokeInterceptor,拦截throwable()方法,拦截器为TomcatExceptionInterceptor

TomcatInvokeInterceptor拦截器

TomcatInvokeInterceptor的beforeMethod()方法:

@Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
    Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
    HttpServletRequest request = (HttpServletRequest)allArguments[0];
    ContextCarrier contextCarrier = new ContextCarrier();

    CarrierItem next = contextCarrier.items();
    while (next.hasNext()) {
        next = next.next();
        next.setHeadValue(request.getHeader(next.getHeadKey()));
    }

    AbstractSpan span = ContextManager.createEntrySpan(request.getRequestURI(), contextCarrier);
    Tags.URL.set(span, request.getRequestURL().toString());
    Tags.HTTP.METHOD.set(span, request.getMethod());
    span.setComponent(ComponentsDefine.TOMCAT);
    SpanLayer.asHttp(span);

}
  1. 获得HttpServletRequest对象,invoke()方法的第一个参数是HttpServletRequest对象
  2. 创建ContextCarrier对象
  3. 从Http请求头中反序列化ContextCarrier
  4. 当tomcat服务与用户直接对接的时候,请求不与trace关联,这时候会创建TracingContext和EntrySpan对象,当tomcat在其他插件之后执行的时候,已经有相应的TracingContext和EntrySpan对象了,重新调用EntrySpan的start()方法,当tomcat作为下游服务跨服务调用tomcat的时候,根据ContextCarrier的请求头创建新的TracingContext和EntrySpan,调用EntrySpan的start()方法
  5. 为span添加Tags,记录请求路径和请求方法
  6. 设置layer

如果序列化上下文不为空,当前跟踪段的 {@link TraceSegmentrefs} 将引用上一级的跟踪段 id。

ContextManager的createEntrySpan()方法:

public static AbstractSpan createEntrySpan(String operationName, ContextCarrier carrier) {
        AbstractSpan span;
        AbstractTracerContext context;
        operationName = StringUtil.cut(operationName, OPERATION_NAME_THRESHOLD);
        if (carrier != null && carrier.isValid()) {
            SamplingService samplingService = ServiceManager.INSTANCE.findService(SamplingService.class);
            samplingService.forceSampled();
            context = getOrCreate(operationName, true);
            span = context.createEntrySpan(operationName);
            context.extract(carrier);
        } else {
            context = getOrCreate(operationName, false);
            span = context.createEntrySpan(operationName);
        }
        return span;
    }

ContextManager的afterMethod()方法

@Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
    Class<?>[] argumentsTypes, Object ret) throws Throwable {
    HttpServletResponse response = (HttpServletResponse)allArguments[1];

    AbstractSpan span = ContextManager.activeSpan();
    if (IS_SERVLET_GET_STATUS_METHOD_EXIST && response.getStatus() >= 400) {
        span.errorOccurred();
        Tags.STATUS_CODE.set(span, Integer.toString(response.getStatus()));
    }
    ContextManager.stopSpan();
    ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG);
    return ret;
}
  1. 获取HttpServletResponse对象
  2. 获取当前span
  3. 如果响应结果异常,标记span的errorOccurred字段为true,添加状态码tag
  4. 关闭span
  5. 从RuntimeContext中移除请求转发标记

TomcatExceptionInterceptor拦截器

TomcatExceptionInterceptor这个类逻辑就比较简单了,beforeMethod()方法中标记errorOccurred字段,添加错误log

总结

这篇文章我们介绍了tomcat插件的实现,先是定义了def文件,然后是创建ClassInstanceMethodsEnhancePluginDefine的实现类进行切入点的实现,一种是应用的请求转发的介入点,一种是对StandardHostValve的invoke()方法拦截,也就是在进行相应的url请求的时候进行拦截增强,开启相应的TracingContext和EntrySpan对象了进行记录开始时间

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。