diff --git a/apm-collector/apm-collector-cluster/src/main/java/org/skywalking/apm/collector/cluster/zookeeper/ClusterZKDataMonitor.java b/apm-collector/apm-collector-cluster/src/main/java/org/skywalking/apm/collector/cluster/zookeeper/ClusterZKDataMonitor.java index 7162adf97..b59482325 100644 --- a/apm-collector/apm-collector-cluster/src/main/java/org/skywalking/apm/collector/cluster/zookeeper/ClusterZKDataMonitor.java +++ b/apm-collector/apm-collector-cluster/src/main/java/org/skywalking/apm/collector/cluster/zookeeper/ClusterZKDataMonitor.java @@ -4,6 +4,8 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.HashSet; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; @@ -46,22 +48,30 @@ public class ClusterZKDataMonitor implements DataMonitor, Watcher { List paths; try { paths = client.getChildren(event.getPath(), true); + ClusterDataListener listener = listeners.get(event.getPath()); + Set remoteNodes = new HashSet(); + Set notifiedNodes = listener.getAddresses(); if (CollectionUtils.isNotEmpty(paths)) { for (String serverPath : paths) { Stat stat = new Stat(); byte[] data = client.getData(event.getPath() + "/" + serverPath, true, stat); String dataStr = new String(data); - if (stat.getCzxid() == stat.getMzxid()) { + String addressValue = serverPath + dataStr; + remoteNodes.add(addressValue); + if (!notifiedNodes.contains(addressValue)) { logger.info("path children has been created, path: {}, data: {}", event.getPath() + "/" + serverPath, dataStr); - listeners.get(event.getPath()).addAddress(serverPath + dataStr); - listeners.get(event.getPath()).serverJoinNotify(serverPath + dataStr); - } else { - logger.info("path children has been changed, path: {}, data: {}", event.getPath() + "/" + serverPath, dataStr); - listeners.get(event.getPath()).removeAddress(serverPath + dataStr); - listeners.get(event.getPath()).serverQuitNotify(serverPath + dataStr); + listener.addAddress(addressValue); + listener.serverJoinNotify(addressValue); } } } + for (String address : notifiedNodes) { + if (remoteNodes.isEmpty() || !remoteNodes.contains(address)) { + logger.info("path children has been changed, path and data: {}", event.getPath() + "/" + address); + listener.removeAddress(address); + listener.serverQuitNotify(address); + } + } } catch (ZookeeperClientException e) { logger.error(e.getMessage(), e); } 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 3689660ef..c679af69d 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 @@ -33,11 +33,13 @@ public class ComponentsDefine { public static final OfficialComponent SPRING_REST_TEMPLATE = new OfficialComponent(13, "SpringRestTemplate"); - public static final OfficialComponent SPRING_MVC_ANNOTATION = new OfficialComponent(14, "SpringMVCAnnotation"); + public static final OfficialComponent SPRING_MVC_ANNOTATION = new OfficialComponent(14, "SpringMVC"); - public static final OfficialComponent NUTZ_MVC_ANNOTATION = new OfficialComponent(15, "NutzMVCAnnotation"); + public static final OfficialComponent STRUTS2 = new OfficialComponent(15, "Struts2"); - public static final OfficialComponent NUTZ_HTTP = new OfficialComponent(16, "NutzHttp"); + public static final OfficialComponent NUTZ_MVC_ANNOTATION = new OfficialComponent(16, "NutzMVCAnnotation"); + + public static final OfficialComponent NUTZ_HTTP = new OfficialComponent(17, "NutzHttp"); private static ComponentsDefine instance = new ComponentsDefine(); @@ -48,7 +50,7 @@ public class ComponentsDefine { } public ComponentsDefine() { - components = new String[17]; + components = new String[18]; addComponent(TOMCAT); addComponent(HTTPCLIENT); addComponent(DUBBO); @@ -63,6 +65,7 @@ public class ComponentsDefine { addComponent(OKHTTP); addComponent(SPRING_REST_TEMPLATE); addComponent(SPRING_MVC_ANNOTATION); + addComponent(STRUTS2); addComponent(NUTZ_MVC_ANNOTATION); addComponent(NUTZ_HTTP); } diff --git a/apm-sniffer/apm-agent/pom.xml b/apm-sniffer/apm-agent/pom.xml index 9d33ce7ec..0aa99ea71 100644 --- a/apm-sniffer/apm-agent/pom.xml +++ b/apm-sniffer/apm-agent/pom.xml @@ -100,6 +100,11 @@ apm-spring-cloud-feign-1.x-plugin ${project.version} + + org.skywalking + apm-struts2-2.x-plugin + ${project.version} + org.skywalking apm-nutz-mvc-annotation-1.x-plugin diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 5f426d7e6..33ce19e05 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -23,6 +23,7 @@ resin-3.x-plugin resin-4.x-plugin spring-plugins + struts2-2.x-plugin nutz-plugins pom diff --git a/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/pom.xml new file mode 100644 index 000000000..8ddd96cc6 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/pom.xml @@ -0,0 +1,58 @@ + + + apm-sdk-plugin + org.skywalking + 3.2.1-2017 + + 4.0.0 + + apm-struts2-2.x-plugin + jar + + struts2-2.x-plugin + http://maven.apache.org + + + UTF-8 + + + + + org.apache.struts + struts2-core + 2.3.1 + provided + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + javax.servlet + jsp-api + 2.0 + test + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + attach-sources + + jar + + + + + + + diff --git a/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/java/org/skywalking/apm/plugin/struts2/Struts2Interceptor.java b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/java/org/skywalking/apm/plugin/struts2/Struts2Interceptor.java new file mode 100644 index 000000000..ffa257f18 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/java/org/skywalking/apm/plugin/struts2/Struts2Interceptor.java @@ -0,0 +1,56 @@ +package org.skywalking.apm.plugin.struts2; + +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.struts2.ServletActionContext; +import org.skywalking.apm.agent.core.context.CarrierItem; +import org.skywalking.apm.agent.core.context.ContextCarrier; +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 Struts2Interceptor implements InstanceMethodsAroundInterceptor { + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + HttpServletRequest request = ServletActionContext.getRequest(); + ContextCarrier contextCarrier = new ContextCarrier(); + + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + next.setHeadValue(request.getHeader(next.getHeadKey())); + } + + AbstractSpan span = ContextManager.createEntrySpan(request.getRequestURI(), contextCarrier); + Tags.URL.set(span, request.getRequestURL().toString()); + Tags.HTTP.METHOD.set(span, request.getMethod()); + span.setComponent(ComponentsDefine.STRUTS2); + SpanLayer.asHttp(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + HttpServletResponse response = ServletActionContext.getResponse(); + + AbstractSpan span = ContextManager.activeSpan(); + if (response.getStatus() >= 400) { + span.errorOccurred(); + Tags.STATUS_CODE.set(span, Integer.toString(response.getStatus())); + } + ContextManager.stopSpan(); + return ret; + } + + @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/struts2-2.x-plugin/src/main/java/org/skywalking/apm/plugin/struts2/define/Struts2Instrumentation.java b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/java/org/skywalking/apm/plugin/struts2/define/Struts2Instrumentation.java new file mode 100644 index 000000000..5c39a2d3c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/java/org/skywalking/apm/plugin/struts2/define/Struts2Instrumentation.java @@ -0,0 +1,51 @@ +package org.skywalking.apm.plugin.struts2.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +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 static net.bytebuddy.matcher.ElementMatchers.named; +import static org.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +/** + * {@link Struts2Instrumentation} enhance the invokeAction method + * in com.opensymphony.xwork2.DefaultActionInvocation class by + * org.skywalking.apm.plugin.struts2.Struts2Interceptor class + * + * @author zhangxin + */ +public class Struts2Instrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "com.opensymphony.xwork2.DefaultActionInvocation"; + private static final String ENHANCE_METHOD = "invokeAction"; + private static final String INTERCEPT_CLASS = "org.skywalking.apm.plugin.struts2.Struts2Interceptor"; + + @Override protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[]{ + new InstanceMethodsInterceptPoint() { + @Override public ElementMatcher getMethodsMatcher() { + return named(ENHANCE_METHOD); + } + + @Override public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + + @Override public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override protected ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 000000000..775e3a65b --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1 @@ +struts2-2.x=org.skywalking.apm.plugin.struts2.define.Struts2Instrumentation diff --git a/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/test/java/org/skywalking/apm/plugin/struts2/Struts2InterceptorTest.java b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/test/java/org/skywalking/apm/plugin/struts2/Struts2InterceptorTest.java new file mode 100644 index 000000000..5d018534f --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/struts2-2.x-plugin/src/test/java/org/skywalking/apm/plugin/struts2/Struts2InterceptorTest.java @@ -0,0 +1,152 @@ +package org.skywalking.apm.plugin.struts2; + +import com.opensymphony.xwork2.ActionContext; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.struts2.ServletActionContext; +import org.apache.struts2.StrutsStatics; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; +import org.skywalking.apm.agent.core.conf.Config; +import org.skywalking.apm.agent.core.context.SW3CarrierItem; +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.trace.TraceSegmentRef; +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 static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.skywalking.apm.agent.test.tools.SpanAssert.assertComponent; +import static org.skywalking.apm.agent.test.tools.SpanAssert.assertException; +import static org.skywalking.apm.agent.test.tools.SpanAssert.assertLayer; +import static org.skywalking.apm.agent.test.tools.SpanAssert.assertTag; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +@PrepareForTest({ActionContext.class}) +public class Struts2InterceptorTest { + + private Struts2Interceptor struts2Interceptor; + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + private ActionContext actionContext; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Mock + private HttpServletRequest request; + @Mock + private HttpServletResponse response; + @Mock + private MethodInterceptResult methodInterceptResult; + + @Mock + private EnhancedInstance enhancedInstance; + + private Object[] arguments; + private Class[] argumentType; + + private Object[] exceptionArguments; + private Class[] exceptionArgumentType; + + @Before + public void setUp() throws Exception { + struts2Interceptor = new Struts2Interceptor(); + when(request.getRequestURI()).thenReturn("/test/testRequestURL"); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/test/testRequestURL")); + when(response.getStatus()).thenReturn(200); + + mockStatic(ActionContext.class); + actionContext = mock(ActionContext.class); + when(actionContext.get(org.apache.struts2.StrutsStatics.HTTP_RESPONSE)).thenReturn(response); + when(actionContext.get(StrutsStatics.HTTP_REQUEST)).thenReturn(request); + when(ActionContext.getContext()).thenReturn(actionContext); + + arguments = new Object[] {request, response}; + argumentType = new Class[] {request.getClass(), response.getClass()}; + + exceptionArguments = new Object[] {request, response, new RuntimeException()}; + exceptionArgumentType = new Class[] {request.getClass(), response.getClass(), new RuntimeException().getClass()}; + } + + @Test + public void testWithoutSerializedContextData() throws Throwable { + struts2Interceptor.beforeMethod(enhancedInstance, null, arguments, argumentType, methodInterceptResult); + struts2Interceptor.afterMethod(enhancedInstance, null, 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 testWithSerializedContextData() throws Throwable { + when(request.getHeader(SW3CarrierItem.HEADER_NAME)).thenReturn("1.234.111|3|1|1|#192.168.1.8:18002|#/portal/|#/testEntrySpan|#AQA*#AQA*Et0We0tQNQA*"); + + struts2Interceptor.beforeMethod(enhancedInstance, null, arguments, argumentType, methodInterceptResult); + struts2Interceptor.afterMethod(enhancedInstance, null, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + assertHttpSpan(spans.get(0)); + assertTraceSegmentRef(traceSegment.getRefs().get(0)); + } + + @Test + public void testWithOccurException() throws Throwable { + struts2Interceptor.beforeMethod(enhancedInstance, null, arguments, argumentType, methodInterceptResult); + struts2Interceptor.handleMethodException(enhancedInstance, null, arguments, argumentType, new RuntimeException()); + struts2Interceptor.afterMethod(enhancedInstance, null, 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.234.111")); + } + + private void assertHttpSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), is("/test/testRequestURL")); + assertComponent(span, ComponentsDefine.STRUTS2); + assertTag(span, 0, "http://localhost:8080/test/testRequestURL"); + assertThat(span.isEntry(), is(true)); + assertLayer(span, SpanLayer.HTTP); + } +}