11 KiB
插件开发指南
本文档描述了如何理解,开发和贡献插件。
概念
Span
Span是分布式跟踪系统中一个重要且通用的概念。 可从Google Dapper Paper 和 OpenTracing学习Span相关知识
SkyWalking从2017年开始支持OpenTracing和OpenTracing-Java API,我们的Span概念与论文和OpenTracing类似。我们也扩展了Span。
Span有三种类型
1.1 EntrySpan EntrySpan代表服务提供者,也是服务器端的端点。 作为一个APM系统,我们的目标是 应用服务器。 所以几乎所有的服务和MQ-comsumer都是EntrySpan。
1.2 LocalSpan LocalSpan表示普通的Java方法,它与远程服务无关,也不是MQ生产者/消费者 也不是服务(例如HTTP服务)提供者/消费者。
1.3 ExitSpan ExitSpan代表一个服务客户端或MQ的生产者,在SkyWalking的早期命名为“LeafSpan”。 例如 通过JDBC访问DB,读取Redis / Memcached被编目为ExitSpan。
ContextCarrier
为了实现分布式跟踪,需要绑定跨进程的跟踪,并且应该传播上下文 整个过程。 这就是ContextCarrier的职责。
以下是有关如何在A -> B分布式调用中使用ContextCarrier的步骤。
- 在客户端,创建一个新的空的
ContextCarrier。 - 通过
ContextManager#createExitSpan创建一个 ExitSpan 或者 使用ContextManager#inject来初始化ContextCarrier. - 将
ContextCarrier所有信息放到heads(例如HTTP HEAD)、attachments(例如Dubbo RPC framework) 或者messages(例如Kafka) - 通过服务调用,将
ContextCarrier传递到服务端。 - 在服务端,在对应组件的heads、attachments或messages获取
ContextCarrier所有消息。 ContestManager#createEntrySpan创建EntrySpan 或者使用ContextManager#extract将服务端和客户端的绑定。
让我们通过Apache HTTPComponent client插件 和Tomcat 7服务器插件演示,步骤如下:
- 客户端Apache HTTPComponent client插件
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());
}
- 服务端Tomcat 7服务器插件
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);
ContextSnapshot
除了跨进程,跨线程也是需要支持的,例如异步线程(内存中的消息队列)和批处理在java中很常见,跨进程和跨线程十分相似,因为都是需要传播 上下文。 唯一的区别是,不需要跨线程序列化。
以下是有关跨线程传播的三个步骤:
- 使用
ContextManager#capture获取ContextSnapshot对象。 - 让子线程以任何方式,通过方法参数或由现有参数携带来访问ContextSnapshot
- 在子线程中使用
ContextManager#continued。
核心 API
ContextManager
ContextManager提供所有主要API。
- Create EntrySpan
public static AbstractSpan createEntrySpan(String endpointName, ContextCarrier carrier)
按操作名称创建 EntrySpan (例如服务名称, uri) 和 ContextCarrier.
- Create LocalSpan
public static AbstractSpan createLocalSpan(String endpointName)
按操作名称创建 LocalSpan (例如完整的方法结构)
- Create ExitSpan
public static AbstractSpan createExitSpan(String endpointName, ContextCarrier carrier, String remotePeer)
按操作名称创建 ExitSpan (例如服务名称, uri) 和 ContextCarrier 和 对端地址 (例如ip+port或hostname+port)
AbstractSpan
/**
* 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 endpointName);
除了设置操作名称,标签信息和日志外,还要设置两个属性,即component(组件)和layer(层),特别是对于EntrySpan和ExitSpan。
SpanLayer 是span的编目. 有五个值:
- UNKNOWN (默认)
- DB
- RPC_FRAMEWORK, (为了RPC框架,非普通的HTTP调用)for a RPC framework, not an ordinary HTTP
- HTTP
- MQ
组件ID由SkyWalking项目定义和保留,对于组件的名称或ID的扩展,请遵循组件库的定义与扩展
开发插件
Abstract(抽象)
跟踪的基本方法是拦截Java方法,使用字节码操作技术和AOP概念。 SkyWalking包装了字节码操作技术并跟踪上下文的传播, 所以你只需要定义拦截点(换句话说就是Spring的切面)
Intercept(拦截)
SkyWalking提供两类通用的定义去拦截构造器,实例方法和类方法。
- Extend
ClassInstanceMethodsEnhancePluginDefinedefinesContructorintercept points andinstance methodintercept points. - 继承
ClassInstanceMethodsEnhancePluginDefine定义Contructor(构造器)拦截点和instance method(实例化方法)拦截点. - 继承
ClassStaticMethodsEnhancePluginDefine定义class method(类方法)拦截点.
当然,您也可以集成ClassEnhancePluginDefine去设置所有的拦截点,担着不常用。
Implement plugin(实现插件)
下文,我将通过扩展ClassInstanceMethodsEnhancePluginDefine来演示如何实现一个插件
- 定义目标类的名称
protected abstract ClassMatch enhanceClass();
ClassMatch 以下有四种方法表示如何去匹配目标类:
- byName, 通过完整的类名(package name +
.+ class name)(包名+类名)。 - byClassAnnotationMatch, 通过目标类存在某些注释。
- byMethodAnnotationMatch, 通过目标类的方法存在某些注释.
- byHierarchyMatch, 通过目标类的父类或接口
注意事项:
- 禁止使用
*.class.getName()去获取类名, 建议你使用文字字符串,这是为了 避免ClassLoader问题。 by*AnnotationMatch不支持继承的注释.- 非必要的话,不推荐使用
byHierarchyMatch, 因为使用它可能会触发拦截 许多未被发现的方法,会导致性能问题和不稳定。
实例:
@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();
}
也可以使用Matcher来设置目标方法。 如果要更改参数,请在isOverrideArgs中返回** true ** 参考拦截器。
以下部分将告诉您如何实现拦截器。
- Add plugin define into skywalking-plugin.def file
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);
}
在before,after和exception处理阶段使用核心API。
将插件贡献到Apache SkyWalking 仓库中
我们欢迎大家贡献插件。
请按照以下步骤操作:
- 提交有关您要贡献哪些插件的问题,包括支持的版本。
- 在
apm-sniffer / apm-sdk-plugin或apm-sniffer / optional-plugins下创建子模块,名称应包含支持的库名和版本 - Create sub modules under
apm-sniffer/apm-sdk-pluginorapm-sniffer/optional-plugins, and the name should include supported library name and versions - 按照本指南进行开发。 确保提供评论和测试用例。
- 开发并测试。
- 发送拉取请求并要求审核。
- 提供自动测试用例。 所有测试用例都托管在SkyAPMTest/agent-auto-integration-testcases repository. 关于如何编写测试用例,请按照如何编写 文档来实现.
- 在提供自动测试用例并在CI中递交测试后,插件提交者会批准您的插件。
- SkyWalking接受的插件。