diff --git a/.gitignore b/.gitignore index 1a468655b..bdc57edb2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ target/ .DS_Store *~ packages/ +**/dependency-reduced-pom.xml diff --git a/README.md b/README.md index 15037a4f1..09eb5e25c 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDU - JVM Detail. - + - Services Dependency Tree. diff --git a/README_ZH.md b/README_ZH.md index 12edcffdb..f86fcd3cc 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -60,7 +60,7 @@ This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDU - JVM明细信息 - + - 服务依赖树. diff --git a/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java index b37078faa..c635c2d2e 100644 --- a/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java +++ b/apm-network/src/main/java/org/skywalking/apm/network/trace/component/ComponentsDefine.java @@ -43,8 +43,10 @@ public class ComponentsDefine { public static final OfficialComponent JETTY_CLIENT = new OfficialComponent(18, "JettyClient"); - public static final OfficialComponent JETTY_SERVER = new OfficialComponent(19, "JettyServer"); - + public static final OfficialComponent JETTY_SERVER = new OfficialComponent(19, "JettyServer"); + + public static final OfficialComponent MEMCACHE = new OfficialComponent(20, "Memcache"); + private static ComponentsDefine instance = new ComponentsDefine(); private String[] components; @@ -54,7 +56,7 @@ public class ComponentsDefine { } public ComponentsDefine() { - components = new String[20]; + components = new String[21]; addComponent(TOMCAT); addComponent(HTTPCLIENT); addComponent(DUBBO); @@ -74,6 +76,7 @@ public class ComponentsDefine { addComponent(NUTZ_HTTP); addComponent(JETTY_CLIENT); addComponent(JETTY_SERVER); + addComponent(MEMCACHE); } private void addComponent(OfficialComponent component) { @@ -87,4 +90,4 @@ public class ComponentsDefine { return components[componentId]; } } -} +} \ No newline at end of file diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java index 87bfae3ed..131ddfb86 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/AbstractClassEnhancePluginDefine.java @@ -26,7 +26,7 @@ public abstract class AbstractClassEnhancePluginDefine { * @throws PluginException, when set builder failure. */ public DynamicType.Builder define(String transformClassName, - DynamicType.Builder builder, ClassLoader classLoader) throws PluginException { + DynamicType.Builder builder, ClassLoader classLoader, EnhanceContext context) throws PluginException { String interceptorDefineClassName = this.getClass().getName(); if (StringUtil.isEmpty(transformClassName)) { @@ -53,15 +53,16 @@ public abstract class AbstractClassEnhancePluginDefine { /** * find origin class source code for interceptor */ - DynamicType.Builder newClassBuilder = this.enhance(transformClassName, builder, classLoader); + DynamicType.Builder newClassBuilder = this.enhance(transformClassName, builder, classLoader, context); + context.initializationStageCompleted(); logger.debug("enhance class {} by {} completely.", transformClassName, interceptorDefineClassName); return newClassBuilder; } protected abstract DynamicType.Builder enhance(String enhanceOriginClassName, - DynamicType.Builder newClassBuilder, ClassLoader classLoader) throws PluginException; + DynamicType.Builder newClassBuilder, ClassLoader classLoader, EnhanceContext context) throws PluginException; /** * Define the {@link ClassMatch} for filtering class. diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/EnhanceContext.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/EnhanceContext.java new file mode 100644 index 000000000..e3417c38d --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/EnhanceContext.java @@ -0,0 +1,34 @@ +package org.skywalking.apm.agent.core.plugin; + +/** + * The EnhanceContext represents the context or status for processing a class. + * + * Based on this context, the plugin core {@link org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine} + * knows how to process the specific steps for every particular plugin. + * + * @author wusheng + */ +public class EnhanceContext { + private boolean isEnhanced = false; + /** + * The object has already been enhanced or extended. + * e.g. added the new field, or implemented the new interface + */ + private boolean objectExtended = false; + + public boolean isEnhanced() { + return isEnhanced; + } + + public void initializationStageCompleted() { + isEnhanced = true; + } + + public boolean isObjectExtended() { + return objectExtended; + } + + public void extendObjectCompleted() { + objectExtended = true; + } +} diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java index 271b32cee..96e90fee6 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/PluginFinder.java @@ -22,7 +22,7 @@ import static net.bytebuddy.matcher.ElementMatchers.not; * @author wusheng */ public class PluginFinder { - private final Map nameMatchDefine = new HashMap(); + private final Map> nameMatchDefine = new HashMap>(); private final List signatureMatchDefine = new LinkedList(); public PluginFinder(List plugins) { @@ -35,28 +35,34 @@ public class PluginFinder { if (match instanceof NameMatch) { NameMatch nameMatch = (NameMatch)match; - nameMatchDefine.put(nameMatch.getClassName(), plugin); + LinkedList pluginDefines = nameMatchDefine.get(nameMatch.getClassName()); + if (pluginDefines == null) { + pluginDefines = new LinkedList(); + nameMatchDefine.put(nameMatch.getClassName(), pluginDefines); + } + pluginDefines.add(plugin); } else { signatureMatchDefine.add(plugin); } } } - public AbstractClassEnhancePluginDefine find(TypeDescription typeDescription, + public List find(TypeDescription typeDescription, ClassLoader classLoader) { + List matchedPlugins = new LinkedList(); String typeName = typeDescription.getTypeName(); if (nameMatchDefine.containsKey(typeName)) { - return nameMatchDefine.get(typeName); + matchedPlugins.addAll(nameMatchDefine.get(typeName)); } for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) { IndirectMatch match = (IndirectMatch)pluginDefine.enhanceClass(); if (match.isMatch(typeDescription)) { - return pluginDefine; + matchedPlugins.add(pluginDefine); } } - return null; + return matchedPlugins; } public ElementMatcher buildMatch() { diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java index 3dc7075cb..b31e2c3b2 100644 --- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/ClassEnhancePluginDefine.java @@ -6,6 +6,7 @@ import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.SuperMethodCall; import net.bytebuddy.implementation.bind.annotation.Morph; import org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine; +import org.skywalking.apm.agent.core.plugin.EnhanceContext; import org.skywalking.apm.agent.core.plugin.PluginException; import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; import org.skywalking.apm.agent.core.plugin.interceptor.EnhanceException; @@ -46,10 +47,11 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi */ @Override protected DynamicType.Builder enhance(String enhanceOriginClassName, - DynamicType.Builder newClassBuilder, ClassLoader classLoader) throws PluginException { + DynamicType.Builder newClassBuilder, ClassLoader classLoader, + EnhanceContext context) throws PluginException { newClassBuilder = this.enhanceClass(enhanceOriginClassName, newClassBuilder, classLoader); - newClassBuilder = this.enhanceInstance(enhanceOriginClassName, newClassBuilder, classLoader); + newClassBuilder = this.enhanceInstance(enhanceOriginClassName, newClassBuilder, classLoader, context); return newClassBuilder; } @@ -62,7 +64,8 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi * @return new byte-buddy's builder for further manipulation. */ private DynamicType.Builder enhanceInstance(String enhanceOriginClassName, - DynamicType.Builder newClassBuilder, ClassLoader classLoader) throws PluginException { + DynamicType.Builder newClassBuilder, ClassLoader classLoader, + EnhanceContext context) throws PluginException { ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints(); InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints(); @@ -83,16 +86,21 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi } /** - * alter class source code.
+ * Manipulate class source code.
* * new class need:
* 1.Add field, name {@link #CONTEXT_ATTR_NAME}. * 2.Add a field accessor for this field. * + * And make sure the source codes manipulation only occurs once. + * */ - newClassBuilder = newClassBuilder.defineField(CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE) - .implement(EnhancedInstance.class) - .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); + if (!context.isObjectExtended()) { + newClassBuilder = newClassBuilder.defineField(CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE) + .implement(EnhancedInstance.class) + .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); + context.extendObjectCompleted(); + } /** * 2. enhance constructors diff --git a/apm-sniffer/apm-agent/pom.xml b/apm-sniffer/apm-agent/pom.xml index de97031ce..064e0917c 100644 --- a/apm-sniffer/apm-agent/pom.xml +++ b/apm-sniffer/apm-agent/pom.xml @@ -125,6 +125,11 @@ apm-jetty-server-9.x-plugin ${project.version} + + org.skywalking + apm-spymemcached-2.x-plugin + ${project.version} + diff --git a/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java b/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java index 1edf9f841..0d8a51d62 100644 --- a/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java +++ b/apm-sniffer/apm-agent/src/main/java/org/skywalking/apm/agent/SkyWalkingAgent.java @@ -1,6 +1,7 @@ package org.skywalking.apm.agent; import java.lang.instrument.Instrumentation; +import java.util.List; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; @@ -9,6 +10,7 @@ import org.skywalking.apm.agent.core.boot.ServiceManager; import org.skywalking.apm.agent.core.conf.SnifferConfigInitializer; import org.skywalking.apm.agent.core.logging.EasyLogResolver; import org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine; +import org.skywalking.apm.agent.core.plugin.EnhanceContext; import org.skywalking.apm.agent.core.plugin.PluginBootstrap; import org.skywalking.apm.agent.core.plugin.PluginException; import org.skywalking.apm.agent.core.plugin.PluginFinder; @@ -54,13 +56,21 @@ public class SkyWalkingAgent { @Override public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { - AbstractClassEnhancePluginDefine pluginDefine = pluginFinder.find(typeDescription, classLoader); - if (pluginDefine != null) { - DynamicType.Builder newBuilder = pluginDefine.define(typeDescription.getTypeName(), builder, classLoader); - if (newBuilder != null) { - logger.debug("Finish the prepare stage for {}.", typeDescription.getName()); - return newBuilder; + List pluginDefines = pluginFinder.find(typeDescription, classLoader); + if (pluginDefines.size() > 0) { + DynamicType.Builder newBuilder = builder; + EnhanceContext context = new EnhanceContext(); + for (AbstractClassEnhancePluginDefine define : pluginDefines) { + DynamicType.Builder possibleNewBuilder = define.define(typeDescription.getTypeName(), newBuilder, classLoader, context); + if (possibleNewBuilder != null) { + newBuilder = possibleNewBuilder; + } } + if (context.isEnhanced()) { + logger.debug("Finish the prepare stage for {}.", typeDescription.getName()); + } + + return newBuilder; } logger.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName()); diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 976ca663c..145822656 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -26,6 +26,7 @@ struts2-2.x-plugin nutz-plugins jetty-plugin + spymemcached-2.x-plugin pom diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/ControllerServiceMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/AbstractMethodInteceptor.java similarity index 73% rename from apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/ControllerServiceMethodInterceptor.java rename to apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/AbstractMethodInteceptor.java index 08967f935..a96f94a78 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/ControllerServiceMethodInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/AbstractMethodInteceptor.java @@ -1,8 +1,5 @@ package org.skywalking.apm.plugin.spring.mvc; -import java.lang.reflect.Method; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.skywalking.apm.agent.core.context.CarrierItem; import org.skywalking.apm.agent.core.context.ContextCarrier; import org.skywalking.apm.agent.core.context.ContextManager; @@ -13,33 +10,30 @@ import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; import org.skywalking.apm.network.trace.component.ComponentsDefine; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; + /** - * The ControllerServiceMethodInterceptor only use the first mapping value. + * the abstract method inteceptor */ -public class ControllerServiceMethodInterceptor implements InstanceMethodsAroundInterceptor { +public abstract class AbstractMethodInteceptor implements InstanceMethodsAroundInterceptor { + public abstract String getRequestURL(Method method); @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - MethodInterceptResult result) throws Throwable { + MethodInterceptResult result) throws Throwable { PathMappingCache pathMappingCache = (PathMappingCache)objInst.getSkyWalkingDynamicField(); String requestURL = pathMappingCache.findPathMapping(method); if (requestURL == null) { - RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class); - if (methodRequestMapping.value().length > 0) { - requestURL = methodRequestMapping.value()[0]; - } else if (methodRequestMapping.path().length > 0) { - requestURL = methodRequestMapping.path()[0]; - } else { - requestURL = ""; - } + requestURL = getRequestURL(method); pathMappingCache.addPathMapping(method, requestURL); requestURL = pathMappingCache.findPathMapping(method); } - HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); ContextCarrier contextCarrier = new ContextCarrier(); CarrierItem next = contextCarrier.items(); @@ -57,7 +51,7 @@ public class ControllerServiceMethodInterceptor implements InstanceMethodsAround @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, - Object ret) throws Throwable { + Object ret) throws Throwable { HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse(); AbstractSpan span = ContextManager.activeSpan(); @@ -69,8 +63,9 @@ public class ControllerServiceMethodInterceptor implements InstanceMethodsAround return ret; } - @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, - Class[] argumentsTypes, Throwable t) { + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { ContextManager.activeSpan().errorOccurred().log(t); } } diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptor.java new file mode 100644 index 000000000..b04942cb1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptor.java @@ -0,0 +1,24 @@ +package org.skywalking.apm.plugin.spring.mvc; + +import java.lang.reflect.Method; + +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * The RequestMappingMethodInterceptor only use the first mapping value. + * it will inteceptor with @RequestMapping + * @author clevertension + */ +public class RequestMappingMethodInterceptor extends AbstractMethodInteceptor { + @Override + public String getRequestURL(Method method) { + String requestURL = ""; + RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class); + if (methodRequestMapping.value().length > 0) { + requestURL = methodRequestMapping.value()[0]; + } else if (methodRequestMapping.path().length > 0) { + requestURL = methodRequestMapping.path()[0]; + } + return requestURL; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptor.java new file mode 100644 index 000000000..ee69bde38 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptor.java @@ -0,0 +1,56 @@ +package org.skywalking.apm.plugin.spring.mvc; + +import java.lang.reflect.Method; + +import org.springframework.web.bind.annotation.*; + +/** + * The RestMappingMethodInterceptor only use the first mapping value. + * it will inteceptor with + * @GetMapping, @PostMapping, @PutMapping + * @DeleteMapping, @PatchMapping + * @author clevertension + */ +public class RestMappingMethodInterceptor extends AbstractMethodInteceptor { + @Override + public String getRequestURL(Method method) { + String requestURL = ""; + GetMapping getMapping = method.getAnnotation(GetMapping.class); + PostMapping postMapping = method.getAnnotation(PostMapping.class); + PutMapping putMapping = method.getAnnotation(PutMapping.class); + DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class); + PatchMapping patchMapping = method.getAnnotation(PatchMapping.class); + if (getMapping != null) { + if (getMapping.value().length > 0) { + requestURL = getMapping.value()[0]; + } else if (getMapping.path().length > 0) { + requestURL = getMapping.path()[0]; + } + } else if (postMapping != null) { + if (postMapping.value().length > 0) { + requestURL = postMapping.value()[0]; + } else if (postMapping.path().length > 0) { + requestURL = postMapping.path()[0]; + } + } else if (putMapping != null) { + if (putMapping.value().length > 0) { + requestURL = putMapping.value()[0]; + } else if (putMapping.path().length > 0) { + requestURL = putMapping.path()[0]; + } + } else if (deleteMapping != null) { + if (deleteMapping.value().length > 0) { + requestURL = deleteMapping.value()[0]; + } else if (deleteMapping.path().length > 0) { + requestURL = deleteMapping.path()[0]; + } + } else if (patchMapping != null) { + if (patchMapping.value().length > 0) { + requestURL = patchMapping.value()[0]; + } else if (patchMapping.path().length > 0) { + requestURL = patchMapping.path()[0]; + } + } + return requestURL; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java index 96f7715d7..feb6323a2 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/main/java/org/skywalking/apm/plugin/spring/mvc/define/AbstractControllerInstrumentation.java @@ -20,8 +20,8 @@ import static org.skywalking.apm.agent.core.plugin.match.ClassAnnotationMatch.by * org.skywalking.apm.plugin.spring.mvc.ControllerConstructorInterceptor set the controller base path to * dynamic field before execute constructor. * - * org.skywalking.apm.plugin.spring.mvc.ControllerServiceMethodInterceptor get the request path from - * dynamic field first, if not found, ControllerServiceMethodInterceptor generate request path that + * org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor get the request path from + * dynamic field first, if not found, RequestMappingMethodInterceptor generate request path that * combine the path value of current annotation on current method and the base path and set the new path to the dynamic * filed * @@ -56,7 +56,26 @@ public abstract class AbstractControllerInstrumentation extends ClassInstanceMet @Override public String getMethodsInterceptor() { - return "org.skywalking.apm.plugin.spring.mvc.ControllerServiceMethodInterceptor"; + return "org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor"; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return isAnnotatedWith(named("org.springframework.web.bind.annotation.GetMapping")) + .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PostMapping"))) + .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PutMapping"))) + .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.DeleteMapping"))) + .or(isAnnotatedWith(named("org.springframework.web.bind.annotation.PatchMapping"))); + } + @Override + public String getMethodsInterceptor() { + return "org.skywalking.apm.plugin.spring.mvc.RestMappingMethodInterceptor"; } @Override diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/ControllerConstructorInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/ControllerConstructorInterceptorTest.java new file mode 100644 index 000000000..365e252b1 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/ControllerConstructorInterceptorTest.java @@ -0,0 +1,107 @@ +package org.skywalking.apm.plugin.spring.mvc; + + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.lang.reflect.Method; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class ControllerConstructorInterceptorTest { + private ControllerConstructorInterceptor controllerConstructorInterceptor; + private final MockEnhancedInstance1 inst1 = new MockEnhancedInstance1(); + private final MockEnhancedInstance2 inst2 = new MockEnhancedInstance2(); + private final MockEnhancedInstance3 inst3 = new MockEnhancedInstance3(); + @Before + public void setUp() throws Exception { + controllerConstructorInterceptor = new ControllerConstructorInterceptor(); + } + + @Test + public void testOnConstruct_Accuracy1() throws Throwable { + controllerConstructorInterceptor.onConstruct(inst1, null); + PathMappingCache cache = (PathMappingCache)inst1.getSkyWalkingDynamicField(); + Assert.assertNotNull(cache); + + Object obj = new Object(); + Method m = obj.getClass().getMethods()[0]; + cache.addPathMapping(m, "#toString"); + + Assert.assertEquals("the two value should be equal", cache.findPathMapping(m), "/test1#toString"); + } + + @Test + public void testOnConstruct_Accuracy2() throws Throwable { + controllerConstructorInterceptor.onConstruct(inst2, null); + PathMappingCache cache = (PathMappingCache)inst2.getSkyWalkingDynamicField(); + Assert.assertNotNull(cache); + + Object obj = new Object(); + Method m = obj.getClass().getMethods()[0]; + cache.addPathMapping(m, "#toString"); + + Assert.assertEquals("the two value should be equal", cache.findPathMapping(m), "#toString"); + } + + @Test + public void testOnConstruct_Accuracy3() throws Throwable { + controllerConstructorInterceptor.onConstruct(inst3, null); + PathMappingCache cache = (PathMappingCache)inst3.getSkyWalkingDynamicField(); + Assert.assertNotNull(cache); + + Object obj = new Object(); + Method m = obj.getClass().getMethods()[0]; + cache.addPathMapping(m, "#toString"); + + Assert.assertEquals("the two value should be equal", cache.findPathMapping(m), "/test3#toString"); + } + + @RequestMapping(value="/test1") + private class MockEnhancedInstance1 implements EnhancedInstance { + private Object value; + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + } + + private class MockEnhancedInstance2 implements EnhancedInstance { + private Object value; + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + } + + @RequestMapping(path="/test3") + private class MockEnhancedInstance3 implements EnhancedInstance { + private Object value; + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/PathMappingCacheTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/PathMappingCacheTest.java new file mode 100644 index 000000000..e7dd987a5 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/PathMappingCacheTest.java @@ -0,0 +1,34 @@ +package org.skywalking.apm.plugin.spring.mvc; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.test.tools.TracingSegmentRunner; + +import java.lang.reflect.Method; + +import static org.powermock.api.mockito.PowerMockito.whenNew; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class PathMappingCacheTest { + private PathMappingCache pathMappingCache; + + @Before + public void setUp() throws Exception { + pathMappingCache = new PathMappingCache("org.skywalking.apm.plugin.spring.mvc"); + } + + @Test + public void testAddPathMapping1() throws Throwable { + Object obj = new Object(); + Method m = obj.getClass().getMethods()[0]; + pathMappingCache.addPathMapping(m, "#toString"); + + Assert.assertEquals("the two value should be equal", pathMappingCache.findPathMapping(m), "org.skywalking.apm.plugin.spring.mvc#toString"); + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptorTest.java new file mode 100644 index 000000000..7fa10b10c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RequestMappingMethodInterceptorTest.java @@ -0,0 +1,157 @@ +package org.skywalking.apm.plugin.spring.mvc; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.core.context.trace.*; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.skywalking.apm.agent.test.helper.SegmentHelper; +import org.skywalking.apm.agent.test.helper.SegmentRefHelper; +import org.skywalking.apm.agent.test.helper.SpanHelper; +import org.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.skywalking.apm.agent.test.tools.SegmentStorage; +import org.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.skywalking.apm.network.trace.component.ComponentsDefine; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.lang.reflect.Method; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.when; +import static org.skywalking.apm.agent.test.tools.SpanAssert.*; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class RequestMappingMethodInterceptorTest { + private RequestMappingMethodInterceptor interceptor; + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + private ServletRequestAttributes servletRequestAttributes; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + @Mock + private MethodInterceptResult methodInterceptResult; + + private Object[] arguments; + private Class[] argumentType; + + private EnhancedInstance enhancedInstance; + + private ControllerConstructorInterceptor controllerConstructorInterceptor; + + @Before + public void setUp() throws Exception { + interceptor = new RequestMappingMethodInterceptor(); + enhancedInstance = new MockEnhancedInstance1(); + controllerConstructorInterceptor = new ControllerConstructorInterceptor(); + servletRequestAttributes = new ServletRequestAttributes(request, response); + + when(request.getScheme()).thenReturn("http"); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(8080); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/testRequestURL")); + when(response.getStatus()).thenReturn(200); + + arguments = new Object[] {request, response}; + argumentType = new Class[] {request.getClass(), response.getClass()}; + + + } + + @Test + public void testWithoutSerializedContextData() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RequestMappingClass1 mappingClass1 = new RequestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("testRequestURL"); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0)); + } + + @Test + public void testWithOccurException() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RequestMappingClass1 mappingClass1 = new RequestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("testRequestURL"); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.handleMethodException(enhancedInstance, m, arguments, argumentType, new RuntimeException()); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0)); + List logDataEntities = SpanHelper.getLogs(spans.get(0)); + assertThat(logDataEntities.size(), is(1)); + assertException(logDataEntities.get(0), RuntimeException.class); + } + + private void assertTraceSegmentRef(TraceSegmentRef ref) { + assertThat(SegmentRefHelper.getEntryApplicationInstanceId(ref), is(1)); + assertThat(SegmentRefHelper.getSpanId(ref), is(3)); + assertThat(SegmentRefHelper.getTraceSegmentId(ref).toString(), is("1.444.555")); + } + + private void assertHttpSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), is("/test/testRequestURL")); + assertComponent(span, ComponentsDefine.SPRING_MVC_ANNOTATION); + assertTag(span, 0, "http://localhost:8080/test/testRequestURL"); + assertThat(span.isEntry(), is(true)); + assertLayer(span, SpanLayer.HTTP); + } + + @RequestMapping(value = "/test") + private class MockEnhancedInstance1 implements EnhancedInstance { + private Object value; + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + } + + private class RequestMappingClass1 { + @RequestMapping("/testRequestURL") + public void testRequestURL() { + + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptorTest.java new file mode 100644 index 000000000..21ded57d7 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/RestMappingMethodInterceptorTest.java @@ -0,0 +1,277 @@ +package org.skywalking.apm.plugin.spring.mvc; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.core.context.trace.*; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.skywalking.apm.agent.test.helper.SegmentHelper; +import org.skywalking.apm.agent.test.helper.SegmentRefHelper; +import org.skywalking.apm.agent.test.helper.SpanHelper; +import org.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.skywalking.apm.agent.test.tools.SegmentStorage; +import org.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.skywalking.apm.network.trace.component.ComponentsDefine; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.when; +import static org.skywalking.apm.agent.test.tools.SpanAssert.*; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class RestMappingMethodInterceptorTest { + private RestMappingMethodInterceptor interceptor; + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + @Mock + private MethodInterceptResult methodInterceptResult; + + private Object[] arguments; + private Class[] argumentType; + + private EnhancedInstance enhancedInstance; + + private ControllerConstructorInterceptor controllerConstructorInterceptor; + + @Before + public void setUp() throws Exception { + interceptor = new RestMappingMethodInterceptor(); + enhancedInstance = new RestMappingMethodInterceptorTest.MockEnhancedInstance1(); + controllerConstructorInterceptor = new ControllerConstructorInterceptor(); + + when(request.getScheme()).thenReturn("http"); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(8080); + when(response.getStatus()).thenReturn(200); + + arguments = new Object[] {request, response}; + argumentType = new Class[] {request.getClass(), response.getClass()}; + + + } + + @Test + public void testGetMapping() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RestMappingClass1 mappingClass1 = new RestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("getRequestURL"); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/getRequestURL")); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0), "/getRequestURL"); + } + + @Test + public void testPostMapping() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RestMappingClass1 mappingClass1 = new RestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("postRequestURL"); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/postRequestURL")); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0), "/postRequestURL"); + } + + + @Test + public void testPutMapping() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RestMappingClass1 mappingClass1 = new RestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("putRequestURL"); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/putRequestURL")); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0), "/putRequestURL"); + } + + @Test + public void testDeleteMapping() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RestMappingClass1 mappingClass1 = new RestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("deleteRequestURL"); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/deleteRequestURL")); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0), "/deleteRequestURL"); + } + + @Test + public void testPatchMapping() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RestMappingClass1 mappingClass1 = new RestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("patchRequestURL"); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/patchRequestURL")); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0), "/patchRequestURL"); + } + + @Test + public void testDummy() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RestMappingClass1 mappingClass1 = new RestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("dummy"); + when(request.getRequestURI()).thenReturn("/test"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test")); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0), ""); + } + + @Test + public void testWithOccurException() throws Throwable { + controllerConstructorInterceptor.onConstruct(enhancedInstance, null); + RestMappingClass1 mappingClass1 = new RestMappingClass1(); + Method m = mappingClass1.getClass().getMethod("getRequestURL"); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/getRequestURL")); + ServletRequestAttributes servletRequestAttributes = new ServletRequestAttributes(request, response); + RequestContextHolder.setRequestAttributes(servletRequestAttributes); + + interceptor.beforeMethod(enhancedInstance, m, arguments, argumentType, methodInterceptResult); + interceptor.handleMethodException(enhancedInstance, m, arguments, argumentType, new RuntimeException()); + interceptor.afterMethod(enhancedInstance, m, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0), "/getRequestURL"); + List logDataEntities = SpanHelper.getLogs(spans.get(0)); + assertThat(logDataEntities.size(), is(1)); + assertException(logDataEntities.get(0), RuntimeException.class); + } + + private void assertTraceSegmentRef(TraceSegmentRef ref) { + assertThat(SegmentRefHelper.getEntryApplicationInstanceId(ref), is(1)); + assertThat(SegmentRefHelper.getSpanId(ref), is(3)); + assertThat(SegmentRefHelper.getTraceSegmentId(ref).toString(), is("1.444.555")); + } + + private void assertHttpSpan(AbstractTracingSpan span, String suffix) { + assertThat(span.getOperationName(), is("/test" + suffix)); + assertComponent(span, ComponentsDefine.SPRING_MVC_ANNOTATION); + assertTag(span, 0, "http://localhost:8080/test" + suffix); + assertThat(span.isEntry(), is(true)); + assertLayer(span, SpanLayer.HTTP); + } + + @RequestMapping(value = "/test") + private class MockEnhancedInstance1 implements EnhancedInstance { + private Object value; + + @Override + public Object getSkyWalkingDynamicField() { + return value; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.value = value; + } + } + + private class RestMappingClass1 { + @GetMapping("/getRequestURL") + public void getRequestURL() { + + } + @PostMapping("/postRequestURL") + public void postRequestURL() { + + } + @PutMapping("/putRequestURL") + public void putRequestURL() { + + } + @DeleteMapping("/deleteRequestURL") + public void deleteRequestURL() { + + } + @PatchMapping("/patchRequestURL") + public void patchRequestURL() { + + } + public void dummy() { + + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/ControllerInstrumentationTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/ControllerInstrumentationTest.java new file mode 100644 index 000000000..198d359c2 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/ControllerInstrumentationTest.java @@ -0,0 +1,62 @@ +package org.skywalking.apm.plugin.spring.mvc.define; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.skywalking.apm.agent.test.tools.TracingSegmentRunner; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatchers; + +import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class ControllerInstrumentationTest { + private ControllerInstrumentation controllerInstrumentation; + + @Before + public void setUp() throws Exception { + controllerInstrumentation = new ControllerInstrumentation(); + } + + @Test + public void testGetEnhanceAnnotations() throws Throwable { + Assert.assertArrayEquals(new String[] {ControllerInstrumentation.ENHANCE_ANNOTATION}, + controllerInstrumentation.getEnhanceAnnotations()); + } + + @Test + public void testGetInstanceMethodsInterceptPoints() throws Throwable { + InstanceMethodsInterceptPoint[] methodPoints = controllerInstrumentation.getInstanceMethodsInterceptPoints(); + assertThat(methodPoints.length, is(2)); + assertThat(methodPoints[0].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor")); + assertThat(methodPoints[1].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RestMappingMethodInterceptor")); + + Assert.assertFalse(methodPoints[0].isOverrideArgs()); + Assert.assertFalse(methodPoints[1].isOverrideArgs()); + + Assert.assertNotNull(methodPoints[0].getMethodsMatcher()); + Assert.assertNotNull(methodPoints[1].getMethodsMatcher()); + + } + + @Test + public void testGetConstructorsInterceptPoints() throws Throwable { + ConstructorInterceptPoint[] cips = controllerInstrumentation.getConstructorsInterceptPoints(); + Assert.assertEquals(cips.length, 1); + ConstructorInterceptPoint cip = cips[0]; + Assert.assertNotNull(cip); + + Assert.assertEquals(cip.getConstructorInterceptor(), "org.skywalking.apm.plugin.spring.mvc.ControllerConstructorInterceptor"); + Assert.assertTrue(cip.getConstructorMatcher().equals(ElementMatchers.any())); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/RestControllerInstrumentationTest.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/RestControllerInstrumentationTest.java new file mode 100644 index 000000000..f2b8d16df --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-4.x-plugin/src/test/java/org/skywalking/apm/plugin/spring/mvc/define/RestControllerInstrumentationTest.java @@ -0,0 +1,45 @@ +package org.skywalking.apm.plugin.spring.mvc.define; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.skywalking.apm.agent.test.tools.TracingSegmentRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class RestControllerInstrumentationTest { + private RestControllerInstrumentation restControllerInstrumentation; + + @Before + public void setUp() throws Exception { + restControllerInstrumentation = new RestControllerInstrumentation(); + } + + @Test + public void testGetEnhanceAnnotations() throws Throwable { + Assert.assertArrayEquals(new String[] {restControllerInstrumentation.ENHANCE_ANNOTATION}, + restControllerInstrumentation.getEnhanceAnnotations()); + } + + @Test + public void testGetInstanceMethodsInterceptPoints() throws Throwable { + InstanceMethodsInterceptPoint[] methodPoints = restControllerInstrumentation.getInstanceMethodsInterceptPoints(); + assertThat(methodPoints.length, is(2)); + assertThat(methodPoints[0].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RequestMappingMethodInterceptor")); + assertThat(methodPoints[1].getMethodsInterceptor(), is("org.skywalking.apm.plugin.spring.mvc.RestMappingMethodInterceptor")); + + Assert.assertFalse(methodPoints[0].isOverrideArgs()); + Assert.assertFalse(methodPoints[1].isOverrideArgs()); + + Assert.assertNotNull(methodPoints[0].getMethodsMatcher()); + Assert.assertNotNull(methodPoints[1].getMethodsMatcher()); + + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml new file mode 100644 index 000000000..784547130 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.skywalking + apm-sdk-plugin + 3.2.3-2017 + + + apm-spymemcached-2.x-plugin + spymemcached-2.x-plugin + http://maven.apache.org + + + UTF-8 + 2.11.1 + + + + + net.spy + spymemcached + ${spymemcached.version} + provided + + + org.apache.logging.log4j + log4j-core + 2.4.1 + test + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptor.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptor.java new file mode 100644 index 000000000..f7c9d1716 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptor.java @@ -0,0 +1,26 @@ +package org.skywalking.apm.plugin.spymemcached.v2; + +import java.net.InetSocketAddress; +import java.util.List; + +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor; + +public class MemcachedConstructorWithInetSocketAddressListArgInterceptor implements InstanceConstructorInterceptor { + + @Override + public void onConstruct(EnhancedInstance objInst, Object[] allArguments) { + StringBuilder memcachConnInfo = new StringBuilder(); + @SuppressWarnings("unchecked") + List inetSocketAddressList = (List)allArguments[1]; + for (InetSocketAddress inetSocketAddress : inetSocketAddressList) { + String host = inetSocketAddress.getAddress().getHostAddress(); + int port = inetSocketAddress.getPort(); + memcachConnInfo.append(host).append(":").append(port).append(";"); + } + if (memcachConnInfo.length() > 1) { + memcachConnInfo = new StringBuilder(memcachConnInfo.substring(0, memcachConnInfo.length() - 1)); + } + objInst.setSkyWalkingDynamicField(memcachConnInfo.toString()); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptor.java new file mode 100644 index 000000000..89a864dab --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptor.java @@ -0,0 +1,42 @@ +package org.skywalking.apm.plugin.spymemcached.v2; + +import java.lang.reflect.Method; + +import org.skywalking.apm.agent.core.context.ContextManager; +import org.skywalking.apm.agent.core.context.tag.Tags; +import org.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.skywalking.apm.network.trace.component.ComponentsDefine; + +public class MemcachedMethodInterceptor implements InstanceMethodsAroundInterceptor { + + private static final String SPY_MEMCACHE = "SpyMemcached/"; + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + String peer = String.valueOf(objInst.getSkyWalkingDynamicField()); + AbstractSpan span = ContextManager.createExitSpan(SPY_MEMCACHE + method.getName(), peer); + span.setComponent(ComponentsDefine.MEMCACHE); + Tags.DB_TYPE.set(span, ComponentsDefine.MEMCACHE.getName()); + SpanLayer.asDB(span); + Tags.DB_STATEMENT.set(span, method.getName() + " " + allArguments[0]); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Object ret) throws Throwable { + ContextManager.stopSpan(); + return ret; + } + + @Override public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + AbstractSpan span = ContextManager.activeSpan(); + span.errorOccurred(); + span.log(t); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/define/MemcachedInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/define/MemcachedInstrumentation.java new file mode 100644 index 000000000..bbb18baab --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/java/org/skywalking/apm/plugin/spymemcached/v2/define/MemcachedInstrumentation.java @@ -0,0 +1,82 @@ +package org.skywalking.apm.plugin.spymemcached.v2.define; + +import static org.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +import java.util.List; + +import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +/** + * + * {@link MemcachedInstrumentation} presents that skywalking intercept all constructors and methods of + * {@link net.spy.memcached.MemcachedClient}. + * {@link XMemcachedConstructorWithInetSocketAddressListArgInterceptor} intercepts the constructor with + * argument {@link java.net.InetSocketAddress}. + * + * @author IluckySi + * + */ +public class MemcachedInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "net.spy.memcached.MemcachedClient"; + private static final String CONSTRUCTOR_WITH_INETSOCKETADDRESS_LIST_ARG_INTERCEPT_CLASS = "org.skywalking.apm.plugin.spymemcached.v2.MemcachedConstructorWithInetSocketAddressListArgInterceptor"; + private static final String METHOD_INTERCEPT_CLASS = "org.skywalking.apm.plugin.spymemcached.v2.MemcachedMethodInterceptor"; + + @Override + public ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[] { + new ConstructorInterceptPoint() { + @Override + public ElementMatcher getConstructorMatcher() { + return takesArgument(1, List.class); + } + + @Override + public String getConstructorInterceptor() { + return CONSTRUCTOR_WITH_INETSOCKETADDRESS_LIST_ARG_INTERCEPT_CLASS; + } + } + }; + } + + @Override + protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("touch").or(named("append")) .or(named("prepend")).or(named("asyncCAS")) + .or(named("cas")) .or(named("add")).or(named("set")).or(named("replace")) + .or(named("asyncGet")).or(named("asyncGets")).or(named("gets")).or(named("getAndTouch")) + .or(named("get")).or(named("asyncGetBulk")) .or(named("asyncGetAndTouch")) + .or(named("getBulk")).or(named("getStats")) .or(named("incr")) + .or(named("decr")).or(named("asyncIncr")) .or(named("asyncDecr")) + .or(named("delete")); + } + + @Override + public String getMethodsInterceptor() { + return METHOD_INTERCEPT_CLASS; + } + + @Override public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..f552f1602 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1 @@ +spymemcached-2.x=org.skywalking.apm.plugin.spymemcached.v2.define.MemcachedInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptorTest.java new file mode 100644 index 000000000..d4238a200 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedConstructorWithInetSocketAddressListArgInterceptorTest.java @@ -0,0 +1,39 @@ +package org.skywalking.apm.plugin.spymemcached.v2; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; + +@RunWith(MockitoJUnitRunner.class) +public class MemcachedConstructorWithInetSocketAddressListArgInterceptorTest { + + private MemcachedConstructorWithInetSocketAddressListArgInterceptor interceptor; + + @Mock + private EnhancedInstance enhancedInstance; + + @Before + public void setUp() throws Exception { + interceptor = new MemcachedConstructorWithInetSocketAddressListArgInterceptor(); + } + + @Test + public void onConstructWithInetSocketAddressList() { + List inetSocketAddressList = new ArrayList(); + inetSocketAddressList.add(new InetSocketAddress("127.0.0.1", 11211)); + inetSocketAddressList.add(new InetSocketAddress("127.0.0.2", 11211)); + interceptor.onConstruct(enhancedInstance, new Object[]{null, inetSocketAddressList}); + + verify(enhancedInstance, times(1)).setSkyWalkingDynamicField("127.0.0.1:11211;127.0.0.2:11211"); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptorTest.java new file mode 100644 index 000000000..3ddc3be8c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/spymemcached-2.x-plugin/src/test/java/org/skywalking/apm/plugin/spymemcached/v2/MemcachedMethodInterceptorTest.java @@ -0,0 +1,121 @@ +package org.skywalking.apm.plugin.spymemcached.v2; + +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; +import java.util.List; + +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.skywalking.apm.agent.core.context.trace.LogDataEntity; +import org.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.skywalking.apm.agent.core.context.util.KeyValuePair; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.skywalking.apm.agent.test.helper.SegmentHelper; +import org.skywalking.apm.agent.test.helper.SpanHelper; +import org.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.skywalking.apm.agent.test.tools.SegmentStorage; +import org.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.skywalking.apm.agent.test.tools.TracingSegmentRunner; + +import net.spy.memcached.MemcachedClient; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class MemcachedMethodInterceptorTest { + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Mock + private EnhancedInstance enhancedInstance; + private MemcachedMethodInterceptor interceptor; + + private Object[] allArgument; + private Class[] argumentType; + + @Before + public void setUp() throws Exception { + allArgument = new Object[] {"OperationKey", "OperationValue"}; + argumentType = new Class[] {String.class, String.class}; + + interceptor = new MemcachedMethodInterceptor(); + when(enhancedInstance.getSkyWalkingDynamicField()).thenReturn("127.0.0.1:11211"); + } + + @Test + public void testIntercept() throws Throwable { + interceptor.beforeMethod(enhancedInstance, getMockSetMethod(), allArgument, argumentType, null); + interceptor.afterMethod(enhancedInstance, getMockGetMethod(), allArgument, argumentType, null); + + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertThat(spans.size(), is(1)); + assertMemcacheSpan(spans.get(0)); + } + + @Test + public void testInterceptWithException() throws Throwable { + interceptor.beforeMethod(enhancedInstance, getMockSetMethod(), allArgument, argumentType, null); + interceptor.handleMethodException(enhancedInstance, getMockSetMethod(), allArgument, argumentType, new RuntimeException()); + interceptor.afterMethod(enhancedInstance, getMockSetMethod(), allArgument, argumentType, null); + + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + assertThat(spans.size(), is(1)); + assertMemcacheSpan(spans.get(0)); + + assertLogData(SpanHelper.getLogs(spans.get(0))); + } + + private void assertLogData(List logDataEntities) { + assertThat(logDataEntities.size(), is(1)); + LogDataEntity logData = logDataEntities.get(0); + Assert.assertThat(logData.getLogs().size(), is(4)); + Assert.assertThat(logData.getLogs().get(0).getValue(), CoreMatchers.is("error")); + Assert.assertThat(logData.getLogs().get(1).getValue(), CoreMatchers.is(RuntimeException.class.getName())); + Assert.assertNull(logData.getLogs().get(2).getValue()); + assertNotNull(logData.getLogs().get(3).getValue()); + } + + private void assertMemcacheSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), is("SpyMemcached/set")); + assertThat(span.isExit(), is(true)); + assertThat(SpanHelper.getComponentId(span), is(20)); + List tags = SpanHelper.getTags(span); + assertThat(tags.get(0).getValue(), is("Memcache")); + assertThat(tags.get(1).getValue(), is("set OperationKey")); + assertThat(SpanHelper.getLayer(span), is(SpanLayer.DB)); + } + + private Method getMockSetMethod() { + try { + return MemcachedClient.class.getMethod("set", String.class, int.class, Object.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } + + private Method getMockGetMethod() { + try { + return MemcachedClient.class.getMethod("get", String.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } +}