天天看点

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

源码角度了解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的实现类

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

第二部分:InstanceMethodsAroundInterceptor的实现类

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

通过这个类的关系图,我们能看出来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拦截器

总结

❤️ 感谢大家

  1. 欢迎关注我❤️,点赞👍🏻,评论🤤,转发🙏
  2. 关注

    盼盼小课堂

    ,定期为你推送好文,还有群聊不定期抽奖活动,可以畅所欲言,与大神们一起交流,一起学习。
  3. 有不当之处欢迎批评指正。