本源码来自于skywalking-agent 8.9.0版本
本节主要讲解skywalking-agent的轻量级队列内核,该实现在datacarrier模块主要用于进行数据发送OAP服务端的实现,主要采用缓存批量异步发送的方式进行发送。
注:本篇文章主要是作为自己看书后的总结,内容有可能会存在一些个人理解上的偏差,如果有网友找出问题欢迎提出,感谢!!!如果我理解上的错误误导了您,在此表示抱歉!!!
本篇主要描述skywalking探针插件的工程结构,包括每个包在探针中的作用,以及包下每个类是如何设计的。
工程结构
探针插件结构主要包括两部分:定义拦截形式、实现拦截形式的拦截器。例如tomcat插件结构如下:
其中:TomcatInstrumentation、ApplicationDispatcherInstrumentation定义了拦截形式,TomcatExceptionInterceptor、TomcatInvokeInterceptor、ForwardInterceptor 为实现拦截形式的拦截器。
探针插件拦截方法的结构主要由两部分组成:匹配器定义、拦截器实现。当插件拦截的方法被匹配器匹配成功时就会执行探针插件的拦截器。
这里对于拦截形式类的写法官方建议为:*Instrumentation,对于拦截器官方建议为:*Interceptor。
定义拦截形式
对于使用字节码工具实现的 Skywalking Agent 来说,可以实现任意的拦截形式。这里主要介绍类增强的拦截定义和两种最常用的方法拦截形式定义。
类增强的拦截形式
定义类增强的拦截需要继承 AbstractClassEnhancePluginDefine,并重写 protected abstract ClassMatch enhanceClass();返回对象为ClassMatch,代表拦截类的匹配方式。这里常见的匹配方式如下:
#byName:通过类路径+类名进行匹配,不要使用.class.getName来获取类路径+类名,因为这样可能存在ClassLoader问题*。
#byClassAnnotationMatch:通过类注解,实现拦截类的匹配,注意这里不支持从父类继承的注解。
#byHierarchyMatch:通过父类或者接口实现拦截类的匹配。这里如果存在多实现可能存在多次拦截,所以不太建议使用这种方式,容易存在隐藏式BUG。
demo:
public abstract class AbstractControllerInstrumentation extends AbstractSpring4Instrumentation {
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[] {
new ConstructorInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getConstructorMatcher() {
return any();
}
@Override
public String getConstructorInterceptor() {
return "org.apache.skywalking.apm.plugin.spring.mvc.v4.ControllerConstructorInterceptor";
}
}
};
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new DeclaredInstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.RequestMapping"));
}
@Override
public String getMethodsInterceptor() {
return Constants.REQUEST_MAPPING_METHOD_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
},
new DeclaredInstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.GetMapping"))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PostMapping")))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PutMapping")))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.DeleteMapping")))
.or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PatchMapping")));
}
@Override
public String getMethodsInterceptor() {
return Constants.REST_MAPPING_METHOD_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override
protected ClassMatch enhanceClass() {
return ClassAnnotationMatch.byClassAnnotationMatch(getEnhanceAnnotations());
}
protected abstract String[] getEnhanceAnnotations();
}
构造器方法的拦截形式
定义构造器方法的拦截需要继承基类 AbstractClassEnhancePluginDefine ,并重写 public abstract ConstructorInterceptPoint[] getConstructorsInterceptPoints();
其中返回值 ConstructorInterceptPoint[] 的每个元素 ConstructorInterceptPoint 需要定义两个方法:getMethodsMatcher即为构造器方法的匹配方式;getMethodsInterceptor 即为针对该拦截的构造器需要执行的拦截器。
demo
public abstract class AbstractConnectionInstrumentation extends AbstractMysqlInstrumentation {
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[0];
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(Constants.PREPARE_STATEMENT_METHOD_NAME);
}
@Override
public String getMethodsInterceptor() {
return org.apache.skywalking.apm.plugin.jdbc.mysql.Constants.CREATE_PREPARED_STATEMENT_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override
protected abstract ClassMatch enhanceClass();
}
实例方法的拦截形式
定义实例方法的拦截,需要继承 ClassInstanceMethodsEnhancePluginDefine 类,并重写 public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() {
return null;
}
该方法需要定义3个方法
getMethodsMatcher:方法匹配的方式;
getMethodsInterceptor:对匹配的方法实现的增强类;
isOverrideArgs:对匹配的方法的入参是否支持修改。
demo
public abstract class AbstractConnectionInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return new ConstructorInterceptPoint[0];
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(Constants.PREPARE_STATEMENT_METHOD_NAME);
}
@Override
public String getMethodsInterceptor() {
return org.apache.skywalking.apm.plugin.mssql.commons.Constants.CREATE_PREPARED_STATEMENT_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
},
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(Constants.PREPARE_CALL_METHOD_NAME);
}
@Override
public String getMethodsInterceptor() {
return org.apache.skywalking.apm.plugin.mssql.commons.Constants.CREATE_CALLABLE_STATEMENT_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
}
实现拦截形式的拦截器
针对插件的拦截器类,给予开发者对所拦截的方法,在执行前、执行后、执行异常时,进行无侵入的拦截,通过调用skywalking agent相关核心开发库进行数据传递。
demo
public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor {
private static boolean IS_SERVLET_GET_STATUS_METHOD_EXIST;
private static final String SERVLET_RESPONSE_CLASS = "javax.servlet.http.HttpServletResponse";
private static final String GET_STATUS_METHOD = "getStatus";
static {
IS_SERVLET_GET_STATUS_METHOD_EXIST = MethodUtil.isMethodExist(
TomcatInvokeInterceptor.class.getClassLoader(), SERVLET_RESPONSE_CLASS, GET_STATUS_METHOD);
}
/**
* * The {@link TraceSegment#ref} of current trace segment will reference to the trace segment id of the previous
* level if the serialized context is not null.
*
* @param result change this result, if you want to truncate the method.
*/
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
Request request = (Request) allArguments[0];
ContextCarrier contextCarrier = new ContextCarrier();
CarrierItem next = contextCarrier.items();
while (next.hasNext()) {
next = next.next();
next.setHeadValue(request.getHeader(next.getHeadKey()));
}
String operationName = String.join(":", request.getMethod(), request.getRequestURI());
AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier);
Tags.URL.set(span, request.getRequestURL().toString());
Tags.HTTP.METHOD.set(span, request.getMethod());
span.setComponent(ComponentsDefine.TOMCAT);
SpanLayer.asHttp(span);
if (TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS) {
collectHttpParam(request, span);
}
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
Request request = (Request) allArguments[0];
HttpServletResponse response = (HttpServletResponse) allArguments[1];
AbstractSpan span = ContextManager.activeSpan();
if (IS_SERVLET_GET_STATUS_METHOD_EXIST) {
Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.getStatus());
if (response.getStatus() >= 400) {
span.errorOccurred();
}
}
// Active HTTP parameter collection automatically in the profiling context.
if (!TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS && span.isProfiling()) {
collectHttpParam(request, span);
}
ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG);
ContextManager.stopSpan();
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t) {
AbstractSpan span = ContextManager.activeSpan();
span.log(t);
}
private void collectHttpParam(Request request, AbstractSpan span) {
final Map<String, String[]> parameterMap = new HashMap<>();
final org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest();
final Parameters parameters = coyoteRequest.getParameters();
for (final Enumeration<String> names = parameters.getParameterNames(); names.hasMoreElements(); ) {
final String name = names.nextElement();
parameterMap.put(name, parameters.getParameterValues(name));
}
if (!parameterMap.isEmpty()) {
String tagValue = CollectionUtil.toString(parameterMap);
tagValue = TomcatPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD > 0 ?
StringUtil.cut(tagValue, TomcatPluginConfig.Plugin.Http.HTTP_PARAMS_LENGTH_THRESHOLD) :
tagValue;
Tags.HTTP.PARAMS.set(span, tagValue);
}
}
}
注意对于 beforeMethod 方法,如果想要修改入参,要在定义拦截器时将 isOverrideArgs 方法的返回值设置为 true,否则修改参数不会生效。