源碼角度了解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的實作類
第二部分:InstanceMethodsAroundInterceptor的實作類
通過這個類的關系圖,我們能看出來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);
}
}
- 通過log記錄請求轉發的url
- 添加請求轉發的标記到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);
}
- 獲得HttpServletRequest對象,invoke()方法的第一個參數是HttpServletRequest對象
- 建立ContextCarrier對象
- 從Http請求頭中反序列化ContextCarrier
- 當tomcat服務與使用者直接對接的時候,請求不與trace關聯,這時候會建立TracingContext和EntrySpan對象,當tomcat在其他插件之後執行的時候,已經有相應的TracingContext和EntrySpan對象了,重新調用EntrySpan的start()方法,當tomcat作為下遊服務跨服務調用tomcat的時候,根據ContextCarrier的請求頭建立新的TracingContext和EntrySpan,調用EntrySpan的start()方法
- 為span添加Tags,記錄請求路徑和請求方法
- 設定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;
}
- 擷取HttpServletResponse對象
- 擷取目前span
- 如果響應結果異常,标記span的errorOccurred字段為true,添加狀态碼tag
- 關閉span
- 從RuntimeContext中移除請求轉發标記
TomcatExceptionInterceptor攔截器
總結
❤️ 感謝大家
- 歡迎關注我❤️,點贊👍🏻,評論🤤,轉發🙏
- 關注
,定期為你推送好文,還有群聊不定期抽獎活動,可以暢所欲言,與大神們一起交流,一起學習。盼盼小課堂
- 有不當之處歡迎批評指正。