这边文档描述插件的开发和贡献方法
Span是追踪系统中的通用概念(有时候被翻译成埋点),关于Span的定义,请参考OpenTracing 中文版。
SkyWalking作为OpenTracing的支持者,在核心实现中,与标准有较高的相似度。当然,作为实际产品的需要,我们一会扩展相关概念。
我们将span分为三类:
1.1 EntrySpan EntrySpan代表一个服务的提供方,即,服务端的入口点。它是每个Java对外服务的入口点。如:Web服务入口就是一个EntrySpan。
1.2 LocalSpan LocalSpan代表一个普通的Span,代表任意一个本地逻辑块(或方法)
1.3 ExitSpan ExitSpan也可以称为LeafSpan(SkyWalking的早期版本中的称呼),代表了一个远程服务的客户端调用。如:一次JDBC调用。
分布式追踪要解决的一个重要问题,就是跨进程调用链连接的问题,ContextCarrier的概念就是为了解决这种场景。
当发生一次A->B的网络调用时:
ContextManager#createExitSpan
方法创建一个ExitSpan,或者使用ContextManager#inject
,在过程中传入并初始化ContextCarrier
ContextCarrier
中所有元素放入请求头(如:HTTP头)或消息正文(如 Kafka)ContextCarrier
随请求传输到服务端ContestManager#createEntrySpan
方法创建EntrySpan,或者使用ContextManager#extract
,建立分布式调用关联以HTTPComponent调用Tomcat为例:
span = ContextManager.createExitSpan("/span/operation/name", contextCarrier, "ip:port"); CarrierItem next = contextCarrier.items(); while (next.hasNext()) { next = next.next(); httpRequest.setHeader(next.getHeadKey(), next.getHeadValue()); }
ContextCarrier contextCarrier = new ContextCarrier(); CarrierItem next = contextCarrier.items(); while (next.hasNext()) { next = next.next(); next.setHeadValue(request.getHeader(next.getHeadKey())); } span = ContextManager.createEntrySpan(“/span/operation/name”, contextCarrier);
除了跨进程的RPC调用,另外一种追踪的常见场景是跨线程保持链路连接。跨线程和跨进程有很高的相似度,都是需要完成上下文的传递工作。 所以ContextSnapshot具有和ContextCarrier十分类似的API风格。
当发生一次A->B的跨线程调用时:
ContextManager提供了追踪相关操作的主入口
public static AbstractSpan createEntrySpan(String operationName, ContextCarrier carrier)
通过服务名、跨进程传递的ContextCarrier,创建EntrySpan。
public static AbstractSpan createLocalSpan(String operationName)
根据服务名(或方法名),创建LocalSpan
public static AbstractSpan createExitSpan(String operationName, ContextCarrier carrier, String remotePeer)
根据服务名,跨进程传递的ContextCarrier(空容器)和远端服务地址(IP、主机名、域名 + 端口),创建ExitSpan
AbstractSpan提供了Span内部,进行操作的各项API
/** * Set the component id, which defines in {@link ComponentsDefine} * * @param component * @return the span for chaining. */ AbstractSpan setComponent(Component component); /** * Only use this method in explicit instrumentation, like opentracing-skywalking-bridge. * It it higher recommend don't use this for performance consideration. * * @param componentName * @return the span for chaining. */ AbstractSpan setComponent(String componentName); AbstractSpan setLayer(SpanLayer layer); /** * Set a key:value tag on the Span. * * @return this Span instance, for chaining */ AbstractSpan tag(String key, String value); /** * Record an exception event of the current walltime timestamp. * * @param t any subclass of {@link Throwable}, which occurs in this span. * @return the Span, for chaining */ AbstractSpan log(Throwable t); AbstractSpan errorOccurred(); /** * Record an event at a specific timestamp. * * @param timestamp The explicit timestamp for the log record. * @param event the events * @return the Span, for chaining */ AbstractSpan log(long timestamp, Map<String, ?> event); /** * Sets the string name for the logical operation this span represents. * * @return this Span instance, for chaining */ AbstractSpan setOperationName(String operationName);
Span的操作语义和OpenTracing类似。
SpanLayer为我们的特有概念,如果是远程调用类的服务,请设置此属性,包括5个属性值
Component ID被SkyWalking项目组定义和保护。0到10000为保留值,如果你希望贡献新插件,可以在插件pull request通过,并提交的自动化 测试用户被接收后,申请自己的组件ID。私有插件,请使用10000以上的ID,避免重复。
因为所有的程序调用都是基于方法的,所以插件实际上就是基于方法的拦截,类似面向切面编程的AOP技术。SkyWalking底层已经完成相关的技术封装,所以插件开发者只需要定位需要拦截的类、方法,然后结合上文中的追踪API,即可完成插件的开发。
根据Java方法,共有三种拦截类型
我们将这三类拦截,分为两类,即:
当然,也可以同时支持实例和静态方法,直接继承ClassEnhancePluginDefine。但是,这种情况很少。
我们以继承ClassInstanceMethodsEnhancePluginDefine为例(ClassStaticMethodsEnhancePluginDefine十分类似,不再重复描述),描述定义插件的全过程
protected abstract ClassMatch enhanceClass();
ClassMatch反应类的匹配方式,目前提供四种:
注意实现:
*.class.getName()
(用户环境可能会引起ClassLoader问题)。如:
@Override protected ClassMatch enhanceClassName() { return byName("org.apache.catalina.core.StandardEngineValve"); }
protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints(); public interface InstanceMethodsInterceptPoint { /** * class instance methods matcher. * * @return methods matcher */ ElementMatcher<MethodDescription> getMethodsMatcher(); /** * @return represents a class name, the class instance must instanceof InstanceMethodsAroundInterceptor. */ String getMethodsInterceptor(); boolean isOverrideArgs(); }
返回拦截方法的匹配器,以及对应的拦截类,同样由于潜在的ClassLoader问题,不要使用*.class.getName()
。如何构建拦截器,请章节“四. 实现拦截器逻辑”。
tomcat-7.x/8.x=TomcatInstrumentation
我们继续以实现实例方法拦截为例,拦截器需要实现org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor。
/** * A interceptor, which intercept method's invocation. The target methods will be defined in {@link * ClassEnhancePluginDefine}'s subclass, most likely in {@link ClassInstanceMethodsEnhancePluginDefine} * * @author wusheng */ public interface InstanceMethodsAroundInterceptor { /** * called before target method invocation. * * @param result change this result, if you want to truncate the method. * @throws Throwable */ void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable; /** * called after target method invocation. Even method's invocation triggers an exception. * * @param ret the method's original return value. * @return the method's actual return value. * @throws Throwable */ Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable; /** * called when occur exception. * * @param t the exception occur. */ void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t); }
可以在方法执行前、执行后、执行异常三个点,进行拦截,设置修改方法参数(执行前),并调用核心API,设置追踪逻辑。
我们鼓励大家共同贡献支持各个类库的插件。
大家需支持以下步骤执行: