skywalking源码--探针插件工程结构

本源码来自于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,否则修改参数不会生效。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值