源码角度了解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拦截器
TomcatExceptionInterceptor这个类逻辑就比较简单了,beforeMethod()方法中标记errorOccurred字段,添加错误log
总结
这篇文章我们介绍了tomcat插件的实现,先是定义了def文件,然后是创建ClassInstanceMethodsEnhancePluginDefine的实现类进行切入点的实现,一种是应用的请求转发的介入点,一种是对StandardHostValve的invoke()方法拦截,也就是在进行相应的url请求的时候进行拦截增强,开启相应的TracingContext和EntrySpan对象了进行记录开始时间
❤️ 感谢大家
如果你觉得这篇内容对你挺有有帮助的话: