天天看點

源碼角度了解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. 有不當之處歡迎批評指正。