From d87f66f48705fd4d2f43c60bb247fe5256f71ba6 Mon Sep 17 00:00:00 2001 From: yuqianwei Date: Tue, 10 Dec 2024 21:47:06 +0800 Subject: [PATCH 1/3] [Feature] Support Tracing for GlobalFilter and GatewayFilter in spring-cloud-gateway-plugin #12839 --- .../v20x/GatewayFilterInterceptor.java | 93 +++++ .../cloud/gateway/v20x/define/Constants.java | 4 + .../define/GatewayFilterInstrumentation.java | 64 ++++ .../src/main/resources/skywalking-plugin.def | 3 +- .../v20x/GatewayFilterInterceptorTest.java | 333 ++++++++++++++++++ .../v21x/GatewayFilterInterceptor.java | 93 +++++ .../cloud/gateway/v21x/define/Constants.java | 3 + .../define/GatewayFilterInstrumentation.java | 64 ++++ .../src/main/resources/skywalking-plugin.def | 1 + .../v21x/GatewayFilterInterceptorTest.java | 332 +++++++++++++++++ .../gateway/v3x/GatewayFilterInterceptor.java | 93 +++++ .../define/GatewayFilterInstrumentation.java | 67 ++++ .../src/main/resources/skywalking-plugin.def | 1 + .../v3x/GatewayFilterInterceptorTest.java | 332 +++++++++++++++++ .../gateway/v4x/GatewayFilterInterceptor.java | 93 +++++ .../define/GatewayFilterInstrumentation.java | 67 ++++ .../src/main/resources/skywalking-plugin.def | 1 + .../v4x/GatewayFilterInterceptorTest.java | 332 +++++++++++++++++ 18 files changed, 1975 insertions(+), 1 deletion(-) create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/GatewayFilterInstrumentation.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptorTest.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/GatewayFilterInstrumentation.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptorTest.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/GatewayFilterInstrumentation.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptorTest.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/GatewayFilterInstrumentation.java create mode 100644 apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptorTest.java diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..f7cbe2dd58 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + STACK_DEEP.get().getAndIncrement(); + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + STACK_DEEP.get().getAndDecrement(); + if (ContextManager.isActive()) { + if (isExit()) { + STACK_DEEP.remove(); + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().intValue() == 1; + } + + private boolean isExit() { + return STACK_DEEP.get().intValue() <= 0; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java index 450699c959..f20a1c22a6 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/Constants.java @@ -29,4 +29,8 @@ public interface Constants { // HttpClientRequest String INTERCEPT_CLASS_HTTP_CLIENT_REQUEST = "reactor.ipc.netty.http.client.HttpClientRequest"; String HTTPCLIENT_REQUEST_HEADERS_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.HttpclientRequestHeadersInterceptor"; + + // GatewayFilter + String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.GatewayFilterInterceptor"; } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..3e303d8df3 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGateway200EnhancePluginDefine { + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(Constants.INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return Constants.GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def index 4bd0c3f419..0e4bf641bd 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/resources/skywalking-plugin.def @@ -18,4 +18,5 @@ spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.HttpClientInstrumentation spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.HttpClientRequestInstrumentation spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.ServerWebExchangeInstrumentation -spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.DispatcherHandlerInstrumentation \ No newline at end of file +spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.GatewayFilterInstrumentation \ No newline at end of file diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..3cd7bc8705 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptorTest.java @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; + +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..e7b043ab59 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + STACK_DEEP.get().getAndIncrement(); + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + STACK_DEEP.get().getAndDecrement(); + if (ContextManager.isActive()) { + if (isExit()) { + STACK_DEEP.remove(); + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().intValue() == 1; + } + + private boolean isExit() { + return STACK_DEEP.get().intValue() <= 0; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java index 1dbb09ff16..5c88626779 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/Constants.java @@ -33,4 +33,7 @@ public interface Constants { String INTERCEPT_CLASS_TCP_CLIENT = "reactor.netty.tcp.TcpClient"; String TCP_CLIENT_CONSTRUCTOR_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.TcpClientConstructorInterceptor"; + // GatewayFilter + String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.GatewayFilterInterceptor"; } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..62d562c0b0 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGateway210EnhancePluginDefine { + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(Constants.INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return Constants.GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def index 9929db9240..8ce727bb5d 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/resources/skywalking-plugin.def @@ -19,3 +19,4 @@ spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.TcpClientInstrumentation spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.ServerWebExchangeInstrumentation spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.GatewayFilterInstrumentation diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..c2250e3beb --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptorTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..b8d0b0b06b --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + STACK_DEEP.get().getAndIncrement(); + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + STACK_DEEP.get().getAndDecrement(); + if (ContextManager.isActive()) { + if (isExit()) { + STACK_DEEP.remove(); + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().intValue() == 1; + } + + private boolean isExit() { + return STACK_DEEP.get().intValue() <= 0; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..644d844f35 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGatewayV3EnhancePluginDefine { + + private static final String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + private static final String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.GatewayFilterInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def index ba4b480501..10525a931a 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/resources/skywalking-plugin.def @@ -18,3 +18,4 @@ spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.NettyRoutingFilterInstrumentation spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.ServerWebExchangeInstrumentation spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.GatewayFilterInstrumentation diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..db0e0e3174 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptorTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java new file mode 100644 index 0000000000..853ae8c146 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.adapter.DefaultServerWebExchange; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY; + +public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor { + + private static final ThreadLocal STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0)); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + STACK_DEEP.get().getAndIncrement(); + if (isEntry()) { + ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; + + EnhancedInstance enhancedInstance = getInstance(exchange); + + AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter"); + if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) { + ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField()); + } + span.setComponent(SPRING_CLOUD_GATEWAY); + } + } + + public static EnhancedInstance getInstance(Object o) { + EnhancedInstance instance = null; + if (o instanceof DefaultServerWebExchange) { + instance = (EnhancedInstance) o; + } else if (o instanceof ServerWebExchangeDecorator) { + ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate(); + return getInstance(delegate); + } + return instance; + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + STACK_DEEP.get().getAndDecrement(); + if (ContextManager.isActive()) { + if (isExit()) { + STACK_DEEP.remove(); + ContextManager.stopSpan(); + } + } + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private boolean isEntry() { + return STACK_DEEP.get().intValue() == 1; + } + + private boolean isExit() { + return STACK_DEEP.get().intValue() <= 0; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/GatewayFilterInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/GatewayFilterInstrumentation.java new file mode 100644 index 0000000000..5a61b89ac1 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/define/GatewayFilterInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType; + +public class GatewayFilterInstrumentation extends AbstractGatewayV4EnhancePluginDefine { + + private static final String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter"; + private static final String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.GatewayFilterInterceptor"; + + @Override + protected ClassMatch enhanceClass() { + return HierarchyMatch.byHierarchyMatch(INTERCEPT_CLASS_GATEWAY_FILTER); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("filter").and( + takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange")); + } + + @Override + public String getMethodsInterceptor() { + return GATEWAY_FILTER_INTERCEPTOR; + } + + @Override + public boolean isOverrideArgs() { + return true; + } + } + }; + } +} diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def index 5cbcbfcdd8..a335a6b336 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/resources/skywalking-plugin.def @@ -18,3 +18,4 @@ spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.NettyRoutingFilterInstrumentation spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.ServerWebExchangeInstrumentation spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.DispatcherHandlerInstrumentation +spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.GatewayFilterInstrumentation diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptorTest.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptorTest.java new file mode 100644 index 0000000000..d59c63cd17 --- /dev/null +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptorTest.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.ContextSnapshot; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import reactor.core.publisher.Mono; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@RunWith(TracingSegmentRunner.class) +public class GatewayFilterInterceptorTest { + + private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter"; + + private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance { + private ContextSnapshot snapshot; + Map attributes = new HashMap<>(); + + @Override + public Object getSkyWalkingDynamicField() { + return snapshot; + } + + @Override + public void setSkyWalkingDynamicField(Object value) { + this.snapshot = (ContextSnapshot) value; + } + + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + } + + private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance(); + private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor(); + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Before + public void setUp() throws Exception { + } + + private final ServerWebExchange exchange = new ServerWebExchange() { + Map attributes = new HashMap<>(); + @Override + public ServerHttpRequest getRequest() { + return null; + } + + @Override + public ServerHttpResponse getResponse() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Mono getSession() { + return null; + } + + @Override + public Mono getPrincipal() { + return null; + } + + @Override + public Mono> getFormData() { + return null; + } + + @Override + public Mono> getMultipartData() { + return null; + } + + @Override + public LocaleContext getLocaleContext() { + return null; + } + + @Override + public ApplicationContext getApplicationContext() { + return null; + } + + @Override + public boolean isNotModified() { + return false; + } + + @Override + public boolean checkNotModified(Instant instant) { + return false; + } + + @Override + public boolean checkNotModified(String s) { + return false; + } + + @Override + public boolean checkNotModified(String s, Instant instant) { + return false; + } + + @Override + public String transformUrl(String s) { + return null; + } + + @Override + public void addUrlTransformer(Function function) { + + } + + @Override + public String getLogPrefix() { + return null; + } + }; + + @Test + public void testInterceptOnlyOnce() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null); + Assert.assertTrue(ContextManager.isActive()); + AbstractSpan activeSpan = ContextManager.activeSpan(); + Assert.assertTrue(activeSpan instanceof AbstractTracingSpan); + Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + + } + + @Test + public void testNestedInterception() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertTrue(ContextManager.isActive()); + + interceptor.afterMethod(null, null, null, null, null); + Assert.assertFalse(ContextManager.isActive()); + + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithNullDynamicField() throws Throwable { + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 1); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + } + + @Test + public void testWithContextSnapshot() throws Throwable { + final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null); + SpanLayer.asHttp(entrySpan); + entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX); + enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture()); + interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null); + interceptor.afterMethod(null, null, null, null, null); + // no more need this, span was stopped at interceptor#afterMethod + // ContextManager.stopSpan(); + ContextManager.stopSpan(entrySpan); + final List traceSegments = segmentStorage.getTraceSegments(); + Assert.assertEquals(traceSegments.size(), 1); + final List spans = SegmentHelper.getSpans(traceSegments.get(0)); + Assert.assertNotNull(spans); + Assert.assertEquals(spans.size(), 2); + SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY); + SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX); + SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP); + } + +} From e6cfe7fd34e9af3ca93ed203a4ac89b40b625cc2 Mon Sep 17 00:00:00 2001 From: yuqianwei Date: Sat, 14 Dec 2024 08:23:52 +0800 Subject: [PATCH 2/3] [Feature] Support Tracing for GlobalFilter and GatewayFilter in Gateway #736 --- CHANGES.md | 1 + .../config/expectedData.yaml | 26 ++++++++--- .../config/expectedData.yaml | 46 +++++++++++++++---- .../config/expectedData.yaml | 4 +- .../config/expectedData.yaml | 46 +++++++++++++++---- .../config/expectedData.yaml | 46 +++++++++++++++---- 6 files changed, 131 insertions(+), 38 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 56c5131421..b7e95d6617 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Release Notes. * Add empty judgment for constructorInterceptPoint * Bump up gRPC to 1.68.1 * Bump up netty to 4.1.115.Final +* Support Tracing for GlobalFilter and GatewayFilter in Spring Gateway All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/222?closed=1) diff --git a/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml index 8373df690b..d20a2486bc 100644 --- a/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-2.0.x-scenario/config/expectedData.yaml @@ -34,8 +34,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -61,8 +61,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -75,8 +75,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -88,3 +88,17 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' diff --git a/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml index c312b744ab..7eecbf2365 100644 --- a/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-2.1.x-scenario/config/expectedData.yaml @@ -54,8 +54,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -76,8 +76,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -95,8 +95,8 @@ segmentItems: - { key: message, value: not null } - { key: stack, value: not null } - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -106,11 +106,23 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null } skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + spanType: Local + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + skipAnalysis: 'false' - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -123,8 +135,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -136,6 +148,20 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' - segmentId: not null spans: - operationName: /provider/b/testcase diff --git a/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml index 61852cba38..6a052d28fd 100644 --- a/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-3.x-filter-context-scenario/config/expectedData.yaml @@ -43,8 +43,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario diff --git a/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml index 70df54e93f..7a883f6261 100644 --- a/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-3.x-scenario/config/expectedData.yaml @@ -54,8 +54,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -76,8 +76,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -95,8 +95,8 @@ segmentItems: - { key: message, value: not null } - { key: stack, value: not null} - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -106,11 +106,23 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null } skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + spanType: Local + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + skipAnalysis: 'false' - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -123,8 +135,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -136,6 +148,20 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' - segmentId: not null spans: - operationName: /provider/b/testcase diff --git a/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml index 70df54e93f..7a883f6261 100644 --- a/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/gateway-4.x-scenario/config/expectedData.yaml @@ -54,8 +54,8 @@ segmentItems: - {key: http.method, value: GET} - {key: http.status_code, value: '200'} refs: - - {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070', - refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' - serviceName: gateway-projectA-scenario @@ -76,8 +76,8 @@ segmentItems: - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -95,8 +95,8 @@ segmentItems: - { key: message, value: not null } - { key: stack, value: not null} - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -106,11 +106,23 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null } skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + spanType: Local + refs: + - { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null } + skipAnalysis: 'false' - segmentId: not null spans: - operationName: SpringCloudGateway/sendRequest - parentSpanId: 0 - spanId: 1 + parentSpanId: 1 + spanId: 2 spanLayer: Http startTime: nq 0 endTime: nq 0 @@ -123,8 +135,8 @@ segmentItems: - {key: http.status_code, value: '200'} skipAnalysis: 'false' - operationName: SpringCloudGateway/RoutingFilter - parentSpanId: -1 - spanId: 0 + parentSpanId: 0 + spanId: 1 startTime: nq 0 endTime: nq 0 componentId: 61 @@ -136,6 +148,20 @@ segmentItems: parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} skipAnalysis: 'false' + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + spanType: Local + peer: '' + refs: + - {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: not null, traceId: not null} + skipAnalysis: 'false' - segmentId: not null spans: - operationName: /provider/b/testcase From 776d206bc0c0ce64c1a116172ae335fc67d7ec3a Mon Sep 17 00:00:00 2001 From: yuqianwei Date: Mon, 16 Dec 2024 12:40:40 +0800 Subject: [PATCH 3/3] [Feature] Support Tracing for GlobalFilter and GatewayFilter in Gateway #736 --- .../gateway/v20x/GatewayFilterInterceptor.java | 15 ++++++++------- .../gateway/v21x/GatewayFilterInterceptor.java | 15 ++++++++------- .../gateway/v3x/GatewayFilterInterceptor.java | 15 ++++++++------- .../gateway/v4x/GatewayFilterInterceptor.java | 15 ++++++++------- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java index f7cbe2dd58..b5174773c3 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.0.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v20x/GatewayFilterInterceptor.java @@ -39,7 +39,6 @@ public class GatewayFilterInterceptor implements InstanceMethodsAroundIntercepto @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { - STACK_DEEP.get().getAndIncrement(); if (isEntry()) { ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; @@ -67,10 +66,8 @@ public static EnhancedInstance getInstance(Object o) { @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { - STACK_DEEP.get().getAndDecrement(); - if (ContextManager.isActive()) { - if (isExit()) { - STACK_DEEP.remove(); + if (isExit()) { + if (ContextManager.isActive()) { ContextManager.stopSpan(); } } @@ -84,10 +81,14 @@ public void handleMethodException(EnhancedInstance objInst, Method method, Objec } private boolean isEntry() { - return STACK_DEEP.get().intValue() == 1; + return STACK_DEEP.get().getAndIncrement() == 0; } private boolean isExit() { - return STACK_DEEP.get().intValue() <= 0; + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java index e7b043ab59..18b93a08d1 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-2.1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v21x/GatewayFilterInterceptor.java @@ -39,7 +39,6 @@ public class GatewayFilterInterceptor implements InstanceMethodsAroundIntercepto @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { - STACK_DEEP.get().getAndIncrement(); if (isEntry()) { ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; @@ -67,10 +66,8 @@ public static EnhancedInstance getInstance(Object o) { @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { - STACK_DEEP.get().getAndDecrement(); - if (ContextManager.isActive()) { - if (isExit()) { - STACK_DEEP.remove(); + if (isExit()) { + if (ContextManager.isActive()) { ContextManager.stopSpan(); } } @@ -84,10 +81,14 @@ public void handleMethodException(EnhancedInstance objInst, Method method, Objec } private boolean isEntry() { - return STACK_DEEP.get().intValue() == 1; + return STACK_DEEP.get().getAndIncrement() == 0; } private boolean isExit() { - return STACK_DEEP.get().intValue() <= 0; + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java index b8d0b0b06b..133424539b 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v3x/GatewayFilterInterceptor.java @@ -39,7 +39,6 @@ public class GatewayFilterInterceptor implements InstanceMethodsAroundIntercepto @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { - STACK_DEEP.get().getAndIncrement(); if (isEntry()) { ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; @@ -67,10 +66,8 @@ public static EnhancedInstance getInstance(Object o) { @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { - STACK_DEEP.get().getAndDecrement(); - if (ContextManager.isActive()) { - if (isExit()) { - STACK_DEEP.remove(); + if (isExit()) { + if (ContextManager.isActive()) { ContextManager.stopSpan(); } } @@ -84,10 +81,14 @@ public void handleMethodException(EnhancedInstance objInst, Method method, Objec } private boolean isEntry() { - return STACK_DEEP.get().intValue() == 1; + return STACK_DEEP.get().getAndIncrement() == 0; } private boolean isExit() { - return STACK_DEEP.get().intValue() <= 0; + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java index 853ae8c146..233eb24ea7 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/optional-spring-cloud/gateway-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/cloud/gateway/v4x/GatewayFilterInterceptor.java @@ -39,7 +39,6 @@ public class GatewayFilterInterceptor implements InstanceMethodsAroundIntercepto @Override public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { - STACK_DEEP.get().getAndIncrement(); if (isEntry()) { ServerWebExchange exchange = (ServerWebExchange) allArguments[0]; @@ -67,10 +66,8 @@ public static EnhancedInstance getInstance(Object o) { @Override public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { - STACK_DEEP.get().getAndDecrement(); - if (ContextManager.isActive()) { - if (isExit()) { - STACK_DEEP.remove(); + if (isExit()) { + if (ContextManager.isActive()) { ContextManager.stopSpan(); } } @@ -84,10 +81,14 @@ public void handleMethodException(EnhancedInstance objInst, Method method, Objec } private boolean isEntry() { - return STACK_DEEP.get().intValue() == 1; + return STACK_DEEP.get().getAndIncrement() == 0; } private boolean isExit() { - return STACK_DEEP.get().intValue() <= 0; + boolean isExit = STACK_DEEP.get().decrementAndGet() == 0; + if (isExit) { + STACK_DEEP.remove(); + } + return isExit; } }