diff --git a/.github/workflows/plugins-jdk17-test.1.yaml b/.github/workflows/plugins-jdk17-test.1.yaml index 7738b85f44..824239b5a3 100644 --- a/.github/workflows/plugins-jdk17-test.1.yaml +++ b/.github/workflows/plugins-jdk17-test.1.yaml @@ -85,6 +85,7 @@ jobs: - caffeine-3.x-scenario - lettuce-webflux-6x-scenario - elasticsearch-java-9.x-scenario + - webflux-6.x-7.x-scenario steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/plugins-jdk21-test.0.yaml b/.github/workflows/plugins-jdk21-test.0.yaml index 9e6805515f..51ed4fe7e9 100644 --- a/.github/workflows/plugins-jdk21-test.0.yaml +++ b/.github/workflows/plugins-jdk21-test.0.yaml @@ -72,6 +72,7 @@ jobs: case: - spring-6.x-scenario - jdk-virtual-thread-executor-scenario + - gateway-5.x-scenario steps: - uses: actions/checkout@v2 with: diff --git a/CHANGES.md b/CHANGES.md index 138d0ab5a2..88d88eeb8c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,22 @@ Release Notes. ------------------ * Add Spring LDAP 3.3.x-4.x plugin. +* Fix `NoSuchMethodError: org.apache.skywalking.apm.plugin.spring.webflux.v6.DispatcherHandlerHandleMethodInterceptor` + on Spring Framework 7 (Spring Boot 4). `HttpHeaders` no longer implements `MultiValueMap`, so + `List get(Object)` was removed; the entry span was never created and the service produced no + traces. Affects `spring-webflux-6.x` and, through its shaded copy, `spring-cloud-gateway-4.x` + (apache/skywalking#14047). +* Extend `spring-webflux-6.x`, `spring-webflux-6.x-webclient`, `springmvc-annotation-6.x`, + `spring-resttemplate-6.x` and `spring-cloud-gateway-4.x` plugins to support Spring Framework 7, + Spring Boot 4 and Spring Cloud Gateway 5.x. +* Add Spring version witnesses to the `spring-webflux-5.x` and `spring-webflux-6.x` plugins, so they + no longer both match `DispatcherHandler#handle` on Reactor 3.4 (Spring Boot 2.4-2.7). +* **Breaking:** rename `apm-toolkit-webflux` to `apm-toolkit-webflux-5.x` and move its package to + `org.apache.skywalking.apm.toolkit.webflux.v5`; add `apm-toolkit-webflux-6.x` + (`org.apache.skywalking.apm.toolkit.webflux.v6`) for Reactor 3.5+ / Spring Boot 3.x and 4.x, where + `WebFluxSkyWalkingOperators#continueTracing` previously threw `NoSuchMethodError` because Reactor + removed `Signal#getContext()` in 3.5.0. Existing `apm-toolkit-webflux` jars (9.7.0 and earlier) + remain instrumented by the agent, so upgrading the agent alone does not force a change. * Fix the Log4j2 plugin descriptor (`Log4j2Plugins.dat`) missing from the `apm-toolkit-log4j-2.x` jar since 9.5.0, which broke `%traceId` and `%sw_ctx` resolution in Log4j2 `PatternLayout` (apache/skywalking#14006). All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/263?closed=1) diff --git a/apm-application-toolkit/apm-toolkit-webflux/pom.xml b/apm-application-toolkit/apm-toolkit-webflux-5.x/pom.xml similarity index 96% rename from apm-application-toolkit/apm-toolkit-webflux/pom.xml rename to apm-application-toolkit/apm-toolkit-webflux-5.x/pom.xml index fb4778fe06..86a4609ac9 100644 --- a/apm-application-toolkit/apm-toolkit-webflux/pom.xml +++ b/apm-application-toolkit/apm-toolkit-webflux-5.x/pom.xml @@ -24,7 +24,7 @@ 4.0.0 - apm-toolkit-webflux + apm-toolkit-webflux-5.x 5.1.0.RELEASE diff --git a/apm-application-toolkit/apm-toolkit-webflux/src/main/java/org/apache/skywalking/apm/toolkit/webflux/WebFluxSkyWalkingOperators.java b/apm-application-toolkit/apm-toolkit-webflux-5.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v5/WebFluxSkyWalkingOperators.java similarity index 98% rename from apm-application-toolkit/apm-toolkit-webflux/src/main/java/org/apache/skywalking/apm/toolkit/webflux/WebFluxSkyWalkingOperators.java rename to apm-application-toolkit/apm-toolkit-webflux-5.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v5/WebFluxSkyWalkingOperators.java index 70ebacc108..bc165d53b1 100644 --- a/apm-application-toolkit/apm-toolkit-webflux/src/main/java/org/apache/skywalking/apm/toolkit/webflux/WebFluxSkyWalkingOperators.java +++ b/apm-application-toolkit/apm-toolkit-webflux-5.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v5/WebFluxSkyWalkingOperators.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.toolkit.webflux; +package org.apache.skywalking.apm.toolkit.webflux.v5; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Signal; diff --git a/apm-application-toolkit/apm-toolkit-webflux/src/main/java/org/apache/skywalking/apm/toolkit/webflux/WebFluxSkyWalkingTraceContext.java b/apm-application-toolkit/apm-toolkit-webflux-5.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v5/WebFluxSkyWalkingTraceContext.java similarity index 98% rename from apm-application-toolkit/apm-toolkit-webflux/src/main/java/org/apache/skywalking/apm/toolkit/webflux/WebFluxSkyWalkingTraceContext.java rename to apm-application-toolkit/apm-toolkit-webflux-5.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v5/WebFluxSkyWalkingTraceContext.java index 82198edb5c..7e4de8cc10 100644 --- a/apm-application-toolkit/apm-toolkit-webflux/src/main/java/org/apache/skywalking/apm/toolkit/webflux/WebFluxSkyWalkingTraceContext.java +++ b/apm-application-toolkit/apm-toolkit-webflux-5.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v5/WebFluxSkyWalkingTraceContext.java @@ -16,7 +16,7 @@ * */ -package org.apache.skywalking.apm.toolkit.webflux; +package org.apache.skywalking.apm.toolkit.webflux.v5; import org.springframework.web.server.ServerWebExchange; diff --git a/apm-application-toolkit/apm-toolkit-webflux-6.x/pom.xml b/apm-application-toolkit/apm-toolkit-webflux-6.x/pom.xml new file mode 100644 index 0000000000..bf1c90ed93 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-webflux-6.x/pom.xml @@ -0,0 +1,58 @@ + + + + + apm-application-toolkit + org.apache.skywalking + 9.8.0-SNAPSHOT + + 4.0.0 + + apm-toolkit-webflux-6.x + + + 6.0.0 + + 3.5.0 + + + + + io.projectreactor + reactor-core + ${reactor-core.version} + provided + + + org.springframework + spring-webflux + ${spring-webflux.version} + provided + + + + diff --git a/apm-application-toolkit/apm-toolkit-webflux-6.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingOperators.java b/apm-application-toolkit/apm-toolkit-webflux-6.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingOperators.java new file mode 100644 index 0000000000..93f85258fd --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-webflux-6.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingOperators.java @@ -0,0 +1,138 @@ +/* + * 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.toolkit.webflux.v6; + +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Signal; +import reactor.core.publisher.SignalType; +import reactor.util.context.Context; + +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +/** + * WebFlux operators that are capable to reuse tracing context from Reactor's Context. + */ +public final class WebFluxSkyWalkingOperators { + + private WebFluxSkyWalkingOperators() { + throw new IllegalStateException("You can't instantiate a utility class"); + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param signalType - Reactor's signal type + * @param runnable - lambda to execute within the tracing context + * @return consumer of a signal + */ + public static Consumer> continueTracing(SignalType signalType, Runnable runnable) { + return signal -> { + if (signalType != signal.getType()) { + return; + } + continueTracing(runnable).accept(signal); + }; + } + + /** + * Wraps a consumer with a local span and continue tracing context. + * + * @param signalType - Reactor's signal type + * @param consumer - lambda to execute within the tracing context + * @return consumer of a signal + */ + public static Consumer continueTracing(SignalType signalType, Consumer consumer) { + return signal -> { + if (signalType != signal.getType()) { + return; + } + continueTracing(Context.of(signal.getContextView()), () -> consumer.accept(signal)); + }; + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param runnable - lambda to execute within the tracing context + * @return consumer of a signal + */ + public static Consumer continueTracing(Runnable runnable) { + return signal -> { + Context context = Context.of(signal.getContextView()); + continueTracing(context, runnable); + }; + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param context - Reactor context that contains the tracing context + * @param runnable - lambda to execute within the tracing context + */ + public static void continueTracing(Context context, Runnable runnable) { + runnable.run(); + } + + /** + * Wraps a callable with a local span and continue tracing context. + * + * @param context - Reactor context that contains the tracing context + * @param callable - lambda to execute within the tracing context + * @param callable's return type + * @return value from the callable + */ + public static T continueTracing(Context context, Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + return sneakyThrow(e); + } + } + + /** + * Wraps a callable with a local span and continue tracing context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @param callable - lambda to execute within the tracing context + * @param callable's return type + * @return value from the callable + */ + public static T continueTracing(ServerWebExchange serverWebExchange, Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + return sneakyThrow(e); + } + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @param runnable - lambda to execute within the tracing context + */ + public static void continueTracing(ServerWebExchange serverWebExchange, Runnable runnable) { + runnable.run(); + } + + private static R sneakyThrow(Throwable t) throws T { + throw (T) t; + } +} diff --git a/apm-application-toolkit/apm-toolkit-webflux-6.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingTraceContext.java b/apm-application-toolkit/apm-toolkit-webflux-6.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingTraceContext.java new file mode 100644 index 0000000000..23b6597826 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-webflux-6.x/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingTraceContext.java @@ -0,0 +1,79 @@ +/* + * 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.toolkit.webflux.v6; + +import org.springframework.web.server.ServerWebExchange; + +import java.util.Optional; + +/** + * TraceContext for WebFlux. + */ +public class WebFluxSkyWalkingTraceContext { + /** + * Try to get the traceId of current trace context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @return traceId, if it exists, or empty {@link String}. + */ + public static String traceId(ServerWebExchange serverWebExchange) { + return ""; + } + + /** + * Try to get the segmentId of current trace context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @return segmentId, if it exists, or empty {@link String}. + */ + public static String segmentId(ServerWebExchange serverWebExchange) { + return ""; + } + + /** + * Try to get the spanId of current trace context. The spanId is a negative number when the trace context is + * missing. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @return spanId, if it exists, or empty {@link String}. + */ + public static int spanId(ServerWebExchange serverWebExchange) { + return -1; + } + + /** + * Try to get the custom value from trace context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @return custom data value. + */ + public static Optional getCorrelation(ServerWebExchange serverWebExchange, String key) { + return Optional.empty(); + } + + /** + * Put the custom key/value into trace context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @return previous value if it exists. + */ + public static Optional putCorrelation(ServerWebExchange serverWebExchange, String key, String value) { + return Optional.empty(); + } +} diff --git a/apm-application-toolkit/pom.xml b/apm-application-toolkit/pom.xml index 4f2c0e38f5..c7c2df33e0 100644 --- a/apm-application-toolkit/pom.xml +++ b/apm-application-toolkit/pom.xml @@ -41,6 +41,7 @@ apm-toolkit-micrometer-registry apm-toolkit-micrometer-1.10 apm-toolkit-kafka - apm-toolkit-webflux + apm-toolkit-webflux-5.x + apm-toolkit-webflux-6.x diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java index f6cc0168d8..2b7ea9c3da 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/RequestUtil.java @@ -101,7 +101,12 @@ private static void collectHttpHeaders(final List headersList, final Abs } public static Enumeration getHeaders(final ServerHttpRequest request, final String headerName) { - List values = request.getHeaders().get(headerName); + // Use getValuesAsList(String) rather than get(Object): Spring Framework 7 dropped the + // MultiValueMap contract from HttpHeaders, removing List get(Object). getValuesAsList(String) + // is declared on HttpHeaders itself in Spring 5, 6 and 7 with an identical descriptor. + // Note it additionally splits comma-delimited values, which is the more correct reading + // for header collection. + List values = request.getHeaders().getValuesAsList(headerName); if (values == null) { return Collections.enumeration(Collections.emptyList()); } diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java index 8f70a71b3c..94770300ab 100644 --- a/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java +++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/mvc-annotation-commons/src/main/java/org/apache/skywalking/apm/plugin/spring/mvc/commons/interceptor/AbstractMethodInterceptor.java @@ -188,9 +188,6 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA if (httpServletResponse != null) { statusCode = httpServletResponse.getStatus(); } else if (ServerHttpResponse.class.isAssignableFrom(response.getClass())) { - if (HttpResponseWrappers.servletStatusSupported()) { - statusCode = ((ServerHttpResponse) response).getRawStatusCode(); - } Object context = runtimeContext.get(REACTIVE_ASYNC_SPAN_IN_RUNTIME_CONTEXT); if (context != null) { ((AbstractSpan[]) context)[0] = span.prepareForAsync(); diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsActivation.java b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsActivation.java index 09d9b81b0a..4703686689 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsActivation.java +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsActivation.java @@ -21,28 +21,42 @@ 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.StaticMethodsInterceptPoint; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassStaticMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.ClassStaticMethodsEnhancePluginDefineV2; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; +import static org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch.byMultiClassMatch; /** */ -public class WebFluxSkyWalkingOperatorsActivation extends ClassStaticMethodsEnhancePluginDefine { +public class WebFluxSkyWalkingOperatorsActivation extends ClassStaticMethodsEnhancePluginDefineV2 { public static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.toolkit.activation.webflux.WebFluxSkyWalkingOperatorsInterceptor"; + /** + * The un-versioned name shipped by apm-toolkit-webflux 9.7.0 and earlier. Still matched so that + * applications which never upgrade their toolkit keep working against a newer agent. + */ public static final String ENHANCE_CLASS = "org.apache.skywalking.apm.toolkit.webflux.WebFluxSkyWalkingOperators"; + /** + * apm-toolkit-webflux-5.x, for Reactor 3.1-3.4 (Spring Boot 2.x). + */ + public static final String ENHANCE_CLASS_V5 = + "org.apache.skywalking.apm.toolkit.webflux.v5.WebFluxSkyWalkingOperators"; + /** + * apm-toolkit-webflux-6.x, for Reactor 3.5+ (Spring Boot 3.x and 4.x). + */ + public static final String ENHANCE_CLASS_V6 = + "org.apache.skywalking.apm.toolkit.webflux.v6.WebFluxSkyWalkingOperators"; public static final String ENHANCE_METHOD = "continueTracing"; @Override protected ClassMatch enhanceClass() { - return byName(ENHANCE_CLASS); + return byMultiClassMatch(ENHANCE_CLASS, ENHANCE_CLASS_V5, ENHANCE_CLASS_V6); } @Override @@ -51,9 +65,9 @@ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { } @Override - public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints() { - return new StaticMethodsInterceptPoint[] { - new StaticMethodsInterceptPoint() { + public StaticMethodsInterceptV2Point[] getStaticMethodsInterceptV2Points() { + return new StaticMethodsInterceptV2Point[] { + new StaticMethodsInterceptV2Point() { @Override public ElementMatcher getMethodsMatcher() { return named(ENHANCE_METHOD).and(takesArguments(2)) @@ -62,7 +76,7 @@ public ElementMatcher getMethodsMatcher() { } @Override - public String getMethodsInterceptor() { + public String getMethodsInterceptorV2() { return INTERCEPT_CLASS; } diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsInterceptor.java b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsInterceptor.java index 8ae39dfff8..21e2403c1a 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsInterceptor.java +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingOperatorsInterceptor.java @@ -21,7 +21,8 @@ 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.plugin.interceptor.enhance.EnhancedInstance; -import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.MethodInvocationContext; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.v2.StaticMethodsAroundInterceptorV2; import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; import org.springframework.web.server.ServerWebExchange; import reactor.util.context.Context; @@ -29,39 +30,57 @@ import java.lang.reflect.Method; /** + * Continues the trace context inside a Reactor operator. + *

+ * A span is only created when a context snapshot is actually available - an empty Reactor context, or a + * ServerWebExchange that was never enhanced because the optional WebFlux plugin is not installed, both + * legitimately yield nothing to continue. Because of that, this interceptor must remember whether it + * created a span for THIS invocation: {@link MethodInvocationContext} carries that decision from + * {@link #beforeMethod} to {@link #afterMethod}, so the interceptor never stops or logs against a span + * that belongs to somebody else, and nesting is handled correctly. */ -public class WebFluxSkyWalkingOperatorsInterceptor extends WebFluxSkyWalkingStaticMethodsAroundInterceptor { - +public class WebFluxSkyWalkingOperatorsInterceptor implements StaticMethodsAroundInterceptorV2 { + + private static final Object SPAN_CREATED = new Object(); + @Override public void beforeMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, - MethodInterceptResult result) { - // get ContextSnapshot from reactor context, the snapshot is set to reactor context by any other plugin + MethodInvocationContext context) { + // get ContextSnapshot from reactor context, the snapshot is set to reactor context by any other plugin // such as DispatcherHandlerHandleMethodInterceptor in spring-webflux-5.x-plugin + ContextSnapshot snapshot = null; if (parameterTypes[0] == Context.class) { - ((Context) allArguments[0]).getOrEmpty("SKYWALKING_CONTEXT_SNAPSHOT") - .ifPresent(ctx -> { - ContextManager.createLocalSpan("WebFluxOperators/onNext").setComponent(ComponentsDefine.SPRING_WEBFLUX); - ContextManager.continued((ContextSnapshot) ctx); - }); + snapshot = (ContextSnapshot) ((Context) allArguments[0]) + .getOrEmpty("SKYWALKING_CONTEXT_SNAPSHOT") + .orElse(null); } else if (parameterTypes[0] == ServerWebExchange.class) { - EnhancedInstance instance = getInstance(allArguments[0]); + EnhancedInstance instance = WebFluxSkyWalkingStaticMethodsAroundInterceptor.getInstance(allArguments[0]); if (instance != null && instance.getSkyWalkingDynamicField() != null) { - ContextManager.createLocalSpan("WebFluxOperators/onNext").setComponent(ComponentsDefine.SPRING_WEBFLUX); - ContextManager.continued((ContextSnapshot) instance.getSkyWalkingDynamicField()); + snapshot = (ContextSnapshot) instance.getSkyWalkingDynamicField(); } } + + if (snapshot != null) { + ContextManager.createLocalSpan("WebFluxOperators/onNext").setComponent(ComponentsDefine.SPRING_WEBFLUX); + ContextManager.continued(snapshot); + context.setContext(SPAN_CREATED); + } } @Override - public Object afterMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, Object ret) { - ContextManager.stopSpan(); + public Object afterMethod(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, Object ret, + MethodInvocationContext context) { + if (context.getContext() != null) { + ContextManager.stopSpan(); + } return ret; } @Override public void handleMethodException(Class clazz, Method method, Object[] allArguments, Class[] parameterTypes, - Throwable t) { - ContextManager.activeSpan().log(t); + Throwable t, MethodInvocationContext context) { + if (context.getContext() != null) { + ContextManager.activeSpan().log(t); + } } - } diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingStaticMethodsAroundInterceptor.java b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingStaticMethodsAroundInterceptor.java index 138d9440eb..f783a47002 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingStaticMethodsAroundInterceptor.java +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingStaticMethodsAroundInterceptor.java @@ -29,7 +29,7 @@ public abstract class WebFluxSkyWalkingStaticMethodsAroundInterceptor implements StaticMethodsAroundInterceptor { - protected EnhancedInstance getInstance(Object o) { + protected static EnhancedInstance getInstance(Object o) { EnhancedInstance instance = null; if (o instanceof DefaultServerWebExchange && o instanceof EnhancedInstance) { instance = (EnhancedInstance) o; @@ -40,7 +40,7 @@ protected EnhancedInstance getInstance(Object o) { return instance; } - protected ContextSnapshot getContextSnapshot(Object o) { + protected static ContextSnapshot getContextSnapshot(Object o) { return Optional.ofNullable(getInstance(o)) .map(EnhancedInstance::getSkyWalkingDynamicField) .filter(ContextSnapshot.class::isInstance) diff --git a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingTraceContextActivation.java b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingTraceContextActivation.java index 77d522f740..fe11b5329f 100644 --- a/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingTraceContextActivation.java +++ b/apm-sniffer/apm-toolkit-activation/apm-toolkit-webflux-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/webflux/WebFluxSkyWalkingTraceContextActivation.java @@ -23,7 +23,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint; import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassStaticMethodsEnhancePluginDefine; import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; -import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -32,7 +32,19 @@ public class WebFluxSkyWalkingTraceContextActivation extends ClassStaticMethodsE public static final String TRACE_ID_INTERCEPT_CLASS = "org.apache.skywalking.apm.toolkit.activation.webflux.WebFluxSkyWalkingTraceIDInterceptor"; public static final String SEGMENT_ID_INTERCEPT_CLASS = "org.apache.skywalking.apm.toolkit.activation.webflux.WebFluxSkyWalkingSegmentIDInterceptor"; public static final String SPAN_ID_INTERCEPT_CLASS = "org.apache.skywalking.apm.toolkit.activation.webflux.WebFluxSkyWalkingSpanIDInterceptor"; + /** + * The un-versioned name shipped by apm-toolkit-webflux 9.7.0 and earlier. Still matched so that + * applications which never upgrade their toolkit keep working against a newer agent. + */ public static final String ENHANCE_CLASS = "org.apache.skywalking.apm.toolkit.webflux.WebFluxSkyWalkingTraceContext"; + /** + * apm-toolkit-webflux-5.x, for Reactor 3.1-3.4 (Spring Boot 2.x). + */ + public static final String ENHANCE_CLASS_V5 = "org.apache.skywalking.apm.toolkit.webflux.v5.WebFluxSkyWalkingTraceContext"; + /** + * apm-toolkit-webflux-6.x, for Reactor 3.5+ (Spring Boot 3.x and 4.x). + */ + public static final String ENHANCE_CLASS_V6 = "org.apache.skywalking.apm.toolkit.webflux.v6.WebFluxSkyWalkingTraceContext"; public static final String ENHANCE_TRACE_ID_METHOD = "traceId"; public static final String ENHANCE_SEGMENT_ID_METHOD = "segmentId"; public static final String ENHANCE_SPAN_ID_METHOD = "spanId"; @@ -46,7 +58,7 @@ public class WebFluxSkyWalkingTraceContextActivation extends ClassStaticMethodsE */ @Override protected ClassMatch enhanceClass() { - return NameMatch.byName(ENHANCE_CLASS); + return MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS, ENHANCE_CLASS_V5, ENHANCE_CLASS_V6); } /** diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/DispatcherHandlerHandleMethodInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/DispatcherHandlerHandleMethodInterceptor.java index c86c6ea887..1bba71a7fc 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/DispatcherHandlerHandleMethodInterceptor.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/DispatcherHandlerHandleMethodInterceptor.java @@ -40,7 +40,6 @@ import reactor.core.publisher.Mono; import java.lang.reflect.Method; -import java.util.List; public class DispatcherHandlerHandleMethodInterceptor implements InstanceMethodsAroundInterceptor { @@ -56,9 +55,12 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr HttpHeaders headers = exchange.getRequest().getHeaders(); while (next.hasNext()) { next = next.next(); - List header = headers.get(next.getHeadKey()); - if (header != null && header.size() > 0) { - next.setHeadValue(header.get(0)); + // Use getFirst(String) rather than get(Object): Spring Framework 7 dropped the + // MultiValueMap contract from HttpHeaders, removing List get(Object). getFirst(String) + // is declared on HttpHeaders itself in Spring 5, 6 and 7 with an identical descriptor. + String headerValue = headers.getFirst(next.getHeadKey()); + if (headerValue != null) { + next.setHeadValue(headerValue); } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java index 1c3085c399..6b858eef62 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-5.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v5/define/DispatcherHandlerInstrumentation.java @@ -34,6 +34,12 @@ public class DispatcherHandlerInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { private static final String WEBFLUX_CONTEXT_WRITE_CLASS = "reactor.core.publisher.Mono"; private static final String WEBFLUX_CONTEXT_WRITE_METHOD = "subscriberContext"; + /** + * Removed in Spring Framework 6.0, so this pins the 5.x plugin to Spring 5. Mono#subscriberContext + * alone is not enough: on Reactor 3.4 both it and Mono#contextWrite exist, so without this the + * 5.x and 6.x plugins would both match DispatcherHandler#handle on Spring Boot 2.4-2.7. + */ + private static final String WEBFLUX_5_WITNESS_CLASS = "org.springframework.web.reactive.resource.GzipResourceResolver"; @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { @@ -72,4 +78,9 @@ protected List witnessMethods() { return Collections.singletonList( new WitnessMethod(WEBFLUX_CONTEXT_WRITE_CLASS, named(WEBFLUX_CONTEXT_WRITE_METHOD))); } + + @Override + protected String[] witnessClasses() { + return new String[] {WEBFLUX_5_WITNESS_CLASS}; + } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/DispatcherHandlerHandleMethodInterceptor.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/DispatcherHandlerHandleMethodInterceptor.java index 0f2e2f296d..31914ccdfe 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/DispatcherHandlerHandleMethodInterceptor.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/DispatcherHandlerHandleMethodInterceptor.java @@ -40,7 +40,6 @@ import reactor.core.publisher.Mono; import java.lang.reflect.Method; -import java.util.List; public class DispatcherHandlerHandleMethodInterceptor implements InstanceMethodsAroundInterceptor { @@ -56,9 +55,12 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr HttpHeaders headers = exchange.getRequest().getHeaders(); while (next.hasNext()) { next = next.next(); - List header = headers.get(next.getHeadKey()); - if (header != null && header.size() > 0) { - next.setHeadValue(header.get(0)); + // Use getFirst(String) rather than get(Object): Spring Framework 7 dropped the + // MultiValueMap contract from HttpHeaders, removing List get(Object). getFirst(String) + // is declared on HttpHeaders itself in Spring 5, 6 and 7 with an identical descriptor. + String headerValue = headers.getFirst(next.getHeadKey()); + if (headerValue != null) { + next.setHeadValue(headerValue); } } diff --git a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/DispatcherHandlerInstrumentation.java b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/DispatcherHandlerInstrumentation.java index 14600779ef..6d4991ded9 100644 --- a/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/DispatcherHandlerInstrumentation.java +++ b/apm-sniffer/optional-plugins/optional-spring-plugins/spring-webflux-6.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/webflux/v6/define/DispatcherHandlerInstrumentation.java @@ -37,6 +37,12 @@ public class DispatcherHandlerInstrumentation extends ClassInstanceMethodsEnhanc private static final String WEBFLUX_CONTEXT_WRITE_METHOD = "contextWrite"; private static final String WEBFLUX_METHOD_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.webflux.v6.DispatcherHandlerHandleMethodInterceptor"; private static final String WEBFLUX_ENHANCE_CLASS = "org.springframework.web.reactive.DispatcherHandler"; + /** + * Added in Spring Framework 6.0 and still present in 7.x, absent in 5.x. The Mono#contextWrite + * witness below only bounds Reactor, not Spring, so this is what keeps the 6.x plugin off a + * Spring 5 application when both webflux plugins are dropped into /plugins. + */ + private static final String WEBFLUX_6_WITNESS_CLASS = "org.springframework.web.reactive.DispatchExceptionHandler"; @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { @@ -78,4 +84,9 @@ protected List witnessMethods() { return Collections.singletonList( new WitnessMethod(WEBFLUX_CONTEXT_WRITE_CLASS, named(WEBFLUX_CONTEXT_WRITE_METHOD))); } + + @Override + protected String[] witnessClasses() { + return new String[] {WEBFLUX_6_WITNESS_CLASS}; + } } diff --git a/docs/en/setup/service-agent/java-agent/Application-toolkit-webflux.md b/docs/en/setup/service-agent/java-agent/Application-toolkit-webflux.md index 0fa738c54c..8f305ca927 100644 --- a/docs/en/setup/service-agent/java-agent/Application-toolkit-webflux.md +++ b/docs/en/setup/service-agent/java-agent/Application-toolkit-webflux.md @@ -2,20 +2,75 @@ These APIs provide advanced features to enhance interaction capabilities in Webflux cases. -Add the toolkit to your project dependency, through Maven or Gradle +## Choose the right toolkit + +The toolkit is split by Reactor generation, because `reactor.core.publisher.Signal#getContext()` was +removed in Reactor 3.5.0 and its replacement, `Signal#getContextView()`, does not exist before +Reactor 3.4.0. No single artifact can serve both. + +| Artifact | Java package | Reactor | Spring Boot | +|---|---|---|---| +| `apm-toolkit-webflux-5.x` | `org.apache.skywalking.apm.toolkit.webflux.v5` | 3.1.3 -> 3.4 | 2.x | +| `apm-toolkit-webflux-6.x` | `org.apache.skywalking.apm.toolkit.webflux.v6` | 3.5 -> 3.8 | 3.x and 4.x | + +The two artifacts expose exactly the same API, so only the dependency coordinate and the import +change. Add **one** of them — never both. + +For Spring Boot 2.x (Reactor 3.1.3 - 3.4): ```xml org.apache.skywalking - apm-toolkit-webflux + apm-toolkit-webflux-5.x ${skywalking.version} ``` +```java +import org.apache.skywalking.apm.toolkit.webflux.v5.WebFluxSkyWalkingOperators; +import org.apache.skywalking.apm.toolkit.webflux.v5.WebFluxSkyWalkingTraceContext; +``` + +For Spring Boot 3.x and 4.x (Reactor 3.5+): +```xml + + org.apache.skywalking + apm-toolkit-webflux-6.x + ${skywalking.version} + +``` +```java +import org.apache.skywalking.apm.toolkit.webflux.v6.WebFluxSkyWalkingOperators; +import org.apache.skywalking.apm.toolkit.webflux.v6.WebFluxSkyWalkingTraceContext; +``` + +### Migrating from `apm-toolkit-webflux` + +Before 9.8.0 there was a single un-versioned `apm-toolkit-webflux` artifact, in package +`org.apache.skywalking.apm.toolkit.webflux`. It is superseded by `apm-toolkit-webflux-5.x`, and its +`continueTracing` overloads that read the Reactor `Signal` context never worked on Reactor 3.5+ +(Spring Boot 3.0 and later). + +To migrate, change the artifactId and the import: pick `-5.x` to stay on Spring Boot 2.x, or `-6.x` +if you are on Spring Boot 3.x/4.x. Getting the migration itself wrong is caught at build time — a +stale coordinate fails to resolve, and a stale import fails to compile. + +**Upgrade the agent first, or together with the toolkit.** Compatibility is only backward, not +forward: + +| Toolkit | Agent | Result | +|---|---|---| +| old `apm-toolkit-webflux` (<= 9.7.0) | 9.8.0+ | works — the agent still matches the un-versioned classes | +| `-5.x` / `-6.x` (9.8.0+) | <= 9.7.0 | **silently untraced** — the older agent does not know the `.v5` / `.v6` classes, so it never instruments them | + +The second row compiles and runs perfectly normally; the only symptom is that the toolkit calls +stop producing spans. So do not roll out a 9.8.0+ toolkit against a 9.7.0 or older agent. The following scenarios are supported for tracing assistance. ### Continue Tracing from Client The `WebFluxSkyWalkingOperators#continueTracing` provides manual tracing continuous capabilities to adopt native Webflux APIs +With `apm-toolkit-webflux-5.x` (Reactor 3.1.3 - 3.4). `Mono#subscriberContext` was removed in +Reactor 3.5, so this form does not compile on Spring Boot 3.x/4.x: ```java @GetMapping("/testcase/annotation/mono/onnext") public Mono monoOnNext(@RequestBody(required = false) String body) { @@ -27,6 +82,7 @@ The `WebFluxSkyWalkingOperators#continueTracing` provides manual tracing continu } ``` +The `ServerWebExchange` overload takes no Reactor context, so it is identical on both generations: ```java @GetMapping("/login/userFunctions") public Mono> functionInfo(ServerWebExchange exchange, @RequestParam String userId) { @@ -41,6 +97,8 @@ The `WebFluxSkyWalkingOperators#continueTracing` provides manual tracing continu } ``` +With `apm-toolkit-webflux-6.x` (Reactor 3.5+). `Mono#deferContextual` and `Context#of(ContextView)` +were added in Reactor 3.4, so this form requires Reactor 3.4 or later: ```java Mono.just("key").subscribeOn(Schedulers.boundedElastic()) .doOnEach(WebFluxSkyWalkingOperators.continueTracing(SignalType.ON_NEXT, () -> log.info("test log with tid"))) diff --git a/docs/en/setup/service-agent/java-agent/Optional-plugins.md b/docs/en/setup/service-agent/java-agent/Optional-plugins.md index b8ab3524f9..528c1b2c08 100644 --- a/docs/en/setup/service-agent/java-agent/Optional-plugins.md +++ b/docs/en/setup/service-agent/java-agent/Optional-plugins.md @@ -13,11 +13,12 @@ So only released in `/optional-plugins` or `/bootstrap-plugins`, copy to `/plugi * Plugin of Gson serialization lib in optional plugin folder. * Plugin of Zookeeper 3.4.x in optional plugin folder. The reason of being optional plugin is, many business irrelevant traces are generated, which cause extra payload to agents and backends. At the same time, those traces may be just heartbeat(s). * [Customize enhance](Customize-enhance-trace.md) Trace methods based on description files, rather than write plugin or change source codes. -* Plugin of [Spring Cloud Gateway 2.x and 3.x and 4.x](agent-optional-plugins/spring-gateway.md) in optional plugin folder. Please only activate this plugin when you install agent in Spring Gateway. +* Plugin of [Spring Cloud Gateway 2.x, 3.x, 4.x and 5.x](agent-optional-plugins/spring-gateway.md) in optional plugin folder. Please only activate this plugin when you install agent in Spring Gateway. * Plugin of Spring Transaction in optional plugin folder. The reason of being optional plugin is, many local span are generated, which also spend more CPU, memory and network. * [Plugin of Kotlin coroutine](agent-optional-plugins/Kotlin-Coroutine-plugin.md) provides the tracing across coroutines automatically. As it will add local spans to all across routines scenarios, Please assess the performance impact. * Plugin of quartz-scheduler-2.x in the optional plugin folder. The reason for being an optional plugin is, many task scheduling systems are based on quartz-scheduler, this will cause duplicate tracing and link different sub-tasks as they share the same quartz level trigger, such as ElasticJob. -* Plugin of spring-webflux-5.x in the optional plugin folder. Please only activate this plugin when you use webflux alone as a web container. If you are using SpringMVC 5 or Spring Gateway, you don't need this plugin. +* Plugin of spring-webflux-5.x in the optional plugin folder, for Spring Framework 5.x. Please only activate this plugin when you use webflux alone as a web container. If you are using SpringMVC 5 or Spring Gateway, you do not need this plugin. +* Plugin of spring-webflux-6.x in the optional plugin folder, for Spring Framework 6.x and 7.x (Spring Boot 3.x and 4.x). Please only activate this plugin when you use webflux alone as a web container. If you are using SpringMVC 6 or Spring Gateway, you do not need this plugin. * Plugin of mybatis-3.x in optional plugin folder. The reason of being optional plugin is, many local span are generated, which also spend more CPU, memory and network. * Plugin of sentinel-1.x in the optional plugin folder. The reason for being an optional plugin is, the sentinel plugin generates a large number of local spans, which have a potential performance impact. * Plugin of ehcache-2.x in the optional plugin folder. The reason for being an optional plugin is, this plugin enhanced cache framework, generates large number of local spans, which have a potential performance impact. @@ -26,7 +27,7 @@ So only released in `/optional-plugins` or `/bootstrap-plugins`, copy to `/plugi * Plugin of jackson serialization lib in optional plugin folder. * Plugin of Apache ShenYu(incubating) Gateway 2.4.x in optional plugin folder. Please only activate this plugin when you install agent in Apache ShenYu Gateway. * Plugin of sampler plugin with CPU policy in the optional plugin folder. Please only activate this plugin when you need to disable trace collecting when the agent process CPU usage is too high(over threshold). -* Plugin for Spring 6.x and RestTemplate 6.x are in the optional plugin folder. Spring 6 requires Java 17 but SkyWalking is still compatible with Java 8. So, we put it in the optional plugin folder. +* Plugin for Spring 6.x/7.x and RestTemplate 6.x/7.x are in the optional plugin folder. Spring 6 and 7 require Java 17 but SkyWalking is still compatible with Java 8. So, we put them in the optional plugin folder. * Plugin of nacos-client 2.x lib in optional plugin folder. The reason is many business irrelevant traces are generated, which cause extra payload to agents and backends, also spend more CPU, memory and network. * Plugin of netty-http 4.1.x lib in optional plugin folder. The reason is some frameworks use Netty HTTP as kernel, which could double the unnecessary spans and create incorrect RPC relative metrics. diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md index d686c4f901..6e29b2fd24 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -7,15 +7,15 @@ metrics based on the tracing data. * [Tomcat](https://github.com/apache/tomcat) 8 * [Tomcat](https://github.com/apache/tomcat) 9 * [Tomcat](https://github.com/apache/tomcat) 10 - * [Spring Boot](https://github.com/spring-projects/spring-boot) Web 4.x + * [Spring Web](https://github.com/spring-projects/spring-framework) 4.x * Spring MVC 3.x, 4.x 5.x with servlet 3.x - * Spring MVC 6.x (Optional²) + * Spring MVC 6.x -> 7.x (Optional²) * [Nutz Web Framework](https://github.com/nutzam/nutz) 1.x * [Struts2 MVC](http://struts.apache.org/) 2.3.x -> 2.5.x, 7.x (Jakarta, JDK17) * Resin 3 (Optional¹), See [SkySPM Plugin Repository](https://github.com/SkyAPM/java-plugin-extensions) * Resin 4 (Optional¹), See [SkySPM Plugin Repository](https://github.com/SkyAPM/java-plugin-extensions) * [Jetty Server](http://www.eclipse.org/jetty/) 9.x -> 12.x - * [Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional²) -> 6.x (Optional²) + * [Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional²) -> 7.x (Optional²) * [Undertow](http://undertow.io/) 1.3.0.Final -> 2.3.18.Final * [RESTEasy](https://resteasy.dev/) 3.1.0.Final -> 6.2.4.Final * [Play Framework](https://www.playframework.com/) 2.6.x -> 2.8.x @@ -33,16 +33,16 @@ metrics based on the tracing data. * [Okhttp](https://github.com/square/okhttp) 2.x -> 3.x -> 4.x * [Apache httpcomponent HttpClient](http://hc.apache.org/) 2.0 -> 3.1, 4.2, 4.3, 5.0, 5.1 * [Spring RestTemplate](https://github.com/spring-projects/spring-framework) 4.x - * [Spring RestTemplate](https://github.com/spring-projects/spring-framework) 6.x (Optional²) + * [Spring RestTemplate](https://github.com/spring-projects/spring-framework) 6.x -> 7.x (Optional²) * [Jetty Client](http://www.eclipse.org/jetty/) 9.x -> 11.x * [Apache httpcomponent AsyncClient](https://hc.apache.org/httpcomponents-asyncclient-4.1.x/) 4.x * [AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client) 2.1+ - * [Spring Webflux WebClient](https://github.com/spring-projects/spring-framework/tree/main/spring-webflux) 5.x -> 6.x + * [Spring Webflux WebClient](https://github.com/spring-projects/spring-framework/tree/main/spring-webflux) 5.x -> 7.x * JRE HttpURLConnection (Optional²) * [Hutool-http](https://www.hutool.cn/) client 5.x * [Micronaut HTTP Client](https://github.com/micronaut-projects/micronaut-core) 3.2.x -> 3.6.x * HTTP Gateway - * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 2.0.2.RELEASE -> 4.3.x (Optional²) + * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) 2.0.2.RELEASE -> 5.0.x (Optional²) * [Apache ShenYu](https://shenyu.apache.org) (Rich protocol support: `HTTP`,`Spring Cloud`,`gRPC`,`Dubbo`,`SOFARPC`,`Motan`,`Tars`) 2.4.x (Optional²) * JDBC * Mysql Driver 5.x, 6.x, 8.x, 9.x diff --git a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway.md b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway.md index 6b84d91bec..2e4402b2ae 100644 --- a/docs/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway.md +++ b/docs/en/setup/service-agent/java-agent/agent-optional-plugins/spring-gateway.md @@ -1,6 +1,6 @@ # Spring Gateway Plugin -Spring Gateway Plugin only support Spring Gateway 2.x, 3.x and 4.x. It has capabilities to create entry spans for +Spring Gateway Plugin only support Spring Gateway 2.x, 3.x, 4.x and 5.x. It has capabilities to create entry spans for incoming calls, continue tracing context propagation in Spring Gateway and create exit spans for outgoing calls. About the filter extension of Gateway, it provides automatically support as much as possible, including GlobalFilter and GatewayFilter diff --git a/test/plugin/scenarios/gateway-5.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/gateway-5.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..f07157bc6a --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/config/expectedData.yaml @@ -0,0 +1,146 @@ +# +# 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. +# + +# The typical track this scenario asserts: +# +# curl -> gateway-projectA (Spring Cloud Gateway 5.x on Spring Boot 4) +# /provider/b/testcase Entry (spring-webflux, componentId 67) +# SpringCloudGateway/GatewayFilter Local (componentId 61) +# SpringCloudGateway/RoutingFilter Local (componentId 61) +# SpringCloudGateway/send Local (componentId 61) +# SpringCloudGateway/sendRequest Exit (componentId 61) +# -> gateway-projectB +# GET:/provider/b/testcase Entry (tomcat, componentId 1) + CrossProcess ref +# +# The componentId 67 entry span on projectA is produced by the webflux DispatcherHandler +# interceptor that gateway-4.x shades, which is what apache/skywalking#14047 broke on Spring +# Framework 7. The CrossProcess ref on projectB proves the gateway propagated context over the wire. +segmentItems: + - serviceName: gateway-projectB-scenario + segmentSize: ge 1 + segments: + - segmentId: not null + spans: + - operationName: GET:/provider/b/testcase + parentSpanId: -1 + spanId: 0 + spanLayer: Http + spanType: Entry + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:18070/provider/b/testcase'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + logs: [] + refs: + - {parentEndpoint: SpringCloudGateway/send, networkAddress: 'localhost:18070', + refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: not null, traceId: not null} + + - serviceName: gateway-projectA-scenario + segmentSize: ge 4 + segments: + - segmentId: not null + spans: + - operationName: /provider/b/testcase + parentSpanId: -1 + spanId: 0 + spanLayer: Http + spanType: Entry + startTime: nq 0 + endTime: nq 0 + componentId: 67 + isError: false + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/provider/b/testcase'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + logs: [] + - segmentId: not null + spans: + - operationName: SpringCloudGateway/sendRequest + parentSpanId: 0 + spanId: 1 + spanLayer: Http + spanType: Exit + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + peer: localhost:18070 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:18070/provider/b/testcase'} + - {key: http.status_code, value: '200'} + logs: [] + - operationName: SpringCloudGateway/send + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + spanType: Local + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + peer: '' + skipAnalysis: false + logs: [] + refs: + - {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: '', + refType: CrossThread, parentSpanId: 1, parentTraceSegmentId: not null, + parentServiceInstance: not null, parentService: not null, traceId: not null} + - segmentId: not null + spans: + - operationName: SpringCloudGateway/RoutingFilter + parentSpanId: 0 + spanId: 1 + spanLayer: Unknown + spanType: Local + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + peer: '' + skipAnalysis: false + logs: [] + refs: + - {parentEndpoint: /provider/b/testcase, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, + parentService: not null, traceId: not null} + - operationName: SpringCloudGateway/GatewayFilter + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + spanType: Local + startTime: nq 0 + endTime: nq 0 + componentId: 61 + isError: false + peer: '' + skipAnalysis: false + logs: [] + refs: + - {parentEndpoint: /provider/b/testcase, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, + parentService: not null, traceId: not null} diff --git a/test/plugin/scenarios/gateway-5.x-scenario/configuration.yml b/test/plugin/scenarios/gateway-5.x-scenario/configuration.yml new file mode 100644 index 0000000000..ff2a41c8dc --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/configuration.yml @@ -0,0 +1,23 @@ +# +# 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. +# + +type: jvm +entryService: http://localhost:8080/provider/b/testcase +healthCheck: http://localhost:8080/provider/b/healthCheck +startScript: ./bin/startup.sh +runningMode: with_optional +withPlugins: apm-spring-cloud-gateway-4.x-plugin-*.jar diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/bin/startup.sh b/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/bin/startup.sh new file mode 100755 index 0000000000..3697489bb0 --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/bin/startup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# 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. +# + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectA-scenario" ${home}/../libs/gateway-projectA-scenario.jar & +sleep 1 + +java -jar ${agent_opts} "-Dskywalking.agent.service_name=gateway-projectB-scenario" ${home}/../libs/gateway-projectB-scenario.jar & diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/pom.xml b/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/pom.xml new file mode 100644 index 0000000000..7cefa11eef --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/pom.xml @@ -0,0 +1,56 @@ + + + + + org.apache.skywalking + gateway-5.x-scenario + 5.0.0 + + 4.0.0 + + gateway-dist + + + gateway-5.x-scenario + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + gateway-5.x-scenario + false + + src/main/assembly/assembly.xml + + ../target/ + + + + + + + diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/src/main/assembly/assembly.xml b/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..4dc7aece18 --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-dist/src/main/assembly/assembly.xml @@ -0,0 +1,44 @@ + + + gateway-dist + + zip + + + + + ./bin + 0775 + + + + + + ../gateway-projectA-scenario/target/gateway-projectA-scenario.jar + ./libs + 0775 + + + ../gateway-projectB-scenario/target/gateway-projectB-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/pom.xml b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/pom.xml new file mode 100644 index 0000000000..1df72d0f8a --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/pom.xml @@ -0,0 +1,55 @@ + + + + + org.apache.skywalking + gateway-5.x-scenario + 5.0.0 + + 4.0.0 + + gateway-projectA-scenario + + + + org.springframework.cloud + spring-cloud-starter-gateway-server-webflux + ${test.framework.version} + + + + + gateway-projectA-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java new file mode 100644 index 0000000000..d4ba09e552 --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectA/Application.java @@ -0,0 +1,30 @@ +/* + * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectA; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml new file mode 100644 index 0000000000..bf0cb5c89a --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectA-scenario/src/main/resources/application.yml @@ -0,0 +1,31 @@ +# +# 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. +# + +server: + port: 8080 +spring: + cloud: + gateway: + server: + webflux: + httpclient: + connect-timeout: 2000 + routes: + - id: provider_route + uri: http://localhost:18070 + predicates: + - Path=/provider/b/* diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/pom.xml b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/pom.xml new file mode 100644 index 0000000000..63369424fd --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/pom.xml @@ -0,0 +1,54 @@ + + + + + org.apache.skywalking + gateway-5.x-scenario + 5.0.0 + + 4.0.0 + + gateway-projectB-scenario + + + + org.springframework.boot + spring-boot-starter-web + + + + + gateway-projectB-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java new file mode 100644 index 0000000000..23b6ba17a6 --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/Application.java @@ -0,0 +1,30 @@ +/* + * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java new file mode 100644 index 0000000000..4295e9c66c --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/java/test/apache/skywalking/apm/testcase/sc/gateway/projectB/controller/TestController.java @@ -0,0 +1,36 @@ +/* + * 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 test.apache.skywalking.apm.testcase.sc.gateway.projectB.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + @RequestMapping("/provider/b/testcase") + public String testcase() { + return "1"; + } + + @RequestMapping("/provider/b/healthCheck") + public String healthCheck() { + return "Success"; + } +} diff --git a/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..1a46d23370 --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/gateway-projectB-scenario/src/main/resources/application.properties @@ -0,0 +1,18 @@ +# +# 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. +# + +server.port=18070 diff --git a/test/plugin/scenarios/gateway-5.x-scenario/pom.xml b/test/plugin/scenarios/gateway-5.x-scenario/pom.xml new file mode 100644 index 0000000000..65f6b95ee9 --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/pom.xml @@ -0,0 +1,71 @@ + + + + 4.0.0 + + org.apache.skywalking + gateway-5.x-scenario + pom + 5.0.0 + + gateway-projectA-scenario + gateway-projectB-scenario + gateway-dist + + + skywalking-gateway-5.x-scenario + + + UTF-8 + 17 + 3.11.0 + + 5.0.3 + 4.0.8 + ${test.framework.version} + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + gateway-5.x-scenario + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + diff --git a/test/plugin/scenarios/gateway-5.x-scenario/support-version.list b/test/plugin/scenarios/gateway-5.x-scenario/support-version.list new file mode 100644 index 0000000000..e5229a26f1 --- /dev/null +++ b/test/plugin/scenarios/gateway-5.x-scenario/support-version.list @@ -0,0 +1,20 @@ +# +# 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. +# + +# Spring Cloud Gateway server (WebFlux flavour) versions. 5.0.x targets Spring Boot 4 / +# Spring Framework 7. One version per minor, latest patch. +5.0.3 diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/bin/startup.sh b/test/plugin/scenarios/webflux-6.x-7.x-scenario/bin/startup.sh new file mode 100755 index 0000000000..eaffb26abd --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# 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. +# + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/webflux-6.x-7.x-scenario.jar & diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/webflux-6.x-7.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..2b55dc006a --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/config/expectedData.yaml @@ -0,0 +1,111 @@ +# +# 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. +# + +# The typical track this scenario asserts: +# +# GET /case/webflux Entry (spring-webflux, componentId 67) +# -> WebClient GET /case/receive Exit (spring-webflux-webclient, componentId 99) +# -> GET /case/receive Entry (spring-webflux, componentId 67) + CrossProcess ref +# -> WebFluxOperators/onNext Local (webflux toolkit, componentId 67) + CrossThread ref +# +# The CrossProcess ref on the /case/receive entry span is the load-bearing assertion: it can only +# be produced when DispatcherHandler successfully reads the sw8 header off the inbound request, +# which is what apache/skywalking#14047 broke on Spring Framework 7. +segmentItems: + - serviceName: webflux-6.x-7.x-scenario + segmentSize: ge 4 + segments: + - segmentId: not null + spans: + - operationName: WebFluxOperators/onNext + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + spanType: Local + startTime: nq 0 + endTime: nq 0 + componentId: 67 + isError: false + peer: '' + skipAnalysis: false + logs: [] + refs: + - {parentEndpoint: /case/receive, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, + parentService: not null, traceId: not null} + - segmentId: not null + spans: + - operationName: /case/receive + parentSpanId: -1 + spanId: 0 + spanLayer: Http + spanType: Entry + startTime: nq 0 + endTime: nq 0 + componentId: 67 + isError: false + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/case/receive'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + logs: [] + refs: + - {parentEndpoint: /case/receive, networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, + parentService: not null, traceId: not null} + - segmentId: not null + spans: + - operationName: /case/receive + parentSpanId: -1 + spanId: 0 + spanLayer: Http + spanType: Exit + startTime: nq 0 + endTime: nq 0 + componentId: 99 + isError: false + peer: localhost:8080 + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/case/receive'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + logs: [] + refs: + - {parentEndpoint: /case/webflux, networkAddress: '', refType: CrossThread, + parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not null, + parentService: not null, traceId: not null} + - segmentId: not null + spans: + - operationName: /case/webflux + parentSpanId: -1 + spanId: 0 + spanLayer: Http + spanType: Entry + startTime: nq 0 + endTime: nq 0 + componentId: 67 + isError: false + peer: '' + skipAnalysis: false + tags: + - {key: url, value: 'http://localhost:8080/case/webflux'} + - {key: http.method, value: GET} + - {key: http.status_code, value: '200'} + logs: [] diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/configuration.yml b/test/plugin/scenarios/webflux-6.x-7.x-scenario/configuration.yml new file mode 100644 index 0000000000..19165fd220 --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/configuration.yml @@ -0,0 +1,23 @@ +# +# 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. +# + +type: jvm +entryService: http://localhost:8080/case/webflux +healthCheck: http://localhost:8080/case/healthCheck +startScript: ./bin/startup.sh +runningMode: with_optional +withPlugins: apm-spring-webflux-6.x-plugin-*.jar diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/pom.xml b/test/plugin/scenarios/webflux-6.x-7.x-scenario/pom.xml new file mode 100644 index 0000000000..4863d9d580 --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/pom.xml @@ -0,0 +1,104 @@ + + + + 4.0.0 + + org.apache.skywalking + webflux-6.x-7.x-scenario + 5.0.0 + + + UTF-8 + 17 + 3.11.0 + + 3.0.13 + ${test.framework.version} + + + skywalking-webflux-6.x-7.x-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${test.framework.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + webflux-6.x-7.x-scenario + + + org.springframework.boot + spring-boot-maven-plugin + ${test.framework.version} + + + + repackage + + + + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + webflux-6.x-7.x-scenario + false + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000..52f67b1752 --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,39 @@ + + + webflux-6.x-7.x-scenario + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/webflux-6.x-7.x-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/webflux/Application.java b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/webflux/Application.java new file mode 100644 index 0000000000..aab807e32f --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/webflux/Application.java @@ -0,0 +1,30 @@ +/* + * 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.testcase.webflux; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/webflux/controller/TestController.java b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/webflux/controller/TestController.java new file mode 100644 index 0000000000..21961834c8 --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/webflux/controller/TestController.java @@ -0,0 +1,66 @@ +/* + * 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.testcase.webflux.controller; + +import org.apache.skywalking.apm.toolkit.webflux.v6.WebFluxSkyWalkingOperators; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +@RestController +@RequestMapping("/case") +public class TestController { + + @Value("${server.port:8080}") + private String serverPort; + + /** + * Entry case. Calls {@link #receive()} over HTTP so the trace crosses a real request boundary: + * the WebClient plugin writes the sw8 header on the exit span, and the WebFlux DispatcherHandler + * interceptor has to read it back to build the entry span of the downstream segment. That + * downstream segment's cross-process ref is what proves header extraction works. + */ + @GetMapping("/webflux") + public Mono webflux() { + return WebClient.create() + .get() + .uri("http://localhost:" + serverPort + "/case/receive") + .retrieve() + .bodyToMono(String.class); + } + + /** + * Downstream endpoint. Also drives the webflux toolkit through its Reactor 3.5+ package, so the + * toolkit activation is covered by the same request. + */ + @GetMapping("/receive") + public Mono receive() { + return Mono.deferContextual(ctx -> WebFluxSkyWalkingOperators.continueTracing( + Context.of(ctx), () -> Mono.just("Success"))); + } + + @RequestMapping("/healthCheck") + public Mono healthCheck() { + return Mono.just("healthCheck"); + } +} diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingOperators.java b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingOperators.java new file mode 100644 index 0000000000..4cdd43e54a --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/java/org/apache/skywalking/apm/toolkit/webflux/v6/WebFluxSkyWalkingOperators.java @@ -0,0 +1,138 @@ +/* + * 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.toolkit.webflux.v6; + +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Signal; +import reactor.core.publisher.SignalType; +import reactor.util.context.Context; + +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +/** + * WebFlux operators that are capable to reuse tracing context from Reactor's Context. + */ +public final class WebFluxSkyWalkingOperators { + + private WebFluxSkyWalkingOperators() { + throw new IllegalStateException("You can't instantiate a utility class"); + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param signalType - Reactor's signal type + * @param runnable - lambda to execute within the tracing context + * @return consumer of a signal + */ + public static Consumer> continueTracing(SignalType signalType, Runnable runnable) { + return signal -> { + if (signalType != signal.getType()) { + return; + } + continueTracing(runnable).accept(signal); + }; + } + + /** + * Wraps a consumer with a local span and continue tracing context. + * + * @param signalType - Reactor's signal type + * @param consumer - lambda to execute within the tracing context + * @return consumer of a signal + */ + public static Consumer continueTracing(SignalType signalType, Consumer consumer) { + return signal -> { + if (signalType != signal.getType()) { + return; + } + continueTracing(Context.of(signal.getContextView()), () -> consumer.accept(signal)); + }; + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param runnable - lambda to execute within the tracing context + * @return consumer of a signal + */ + public static Consumer continueTracing(Runnable runnable) { + return signal -> { + Context context = Context.of(signal.getContextView()); + continueTracing(context, runnable); + }; + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param context - Reactor context that contains the tracing context + * @param runnable - lambda to execute within the tracing context + */ + public static void continueTracing(Context context, Runnable runnable) { + runnable.run(); + } + + /** + * Wraps a callable with a local span and continue tracing context. + * + * @param context - Reactor context that contains the tracing context + * @param callable - lambda to execute within the tracing context + * @param callable's return type + * @return value from the callable + */ + public static T continueTracing(Context context, Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + return sneakyThrow(e); + } + } + + /** + * Wraps a callable with a local span and continue tracing context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @param callable - lambda to execute within the tracing context + * @param callable's return type + * @return value from the callable + */ + public static T continueTracing(ServerWebExchange serverWebExchange, Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + return sneakyThrow(e); + } + } + + /** + * Wraps a runnable with a local span and continue tracing context. + * + * @param serverWebExchange - EnhancedInstance that contains the tracing context + * @param runnable - lambda to execute within the tracing context + */ + public static void continueTracing(ServerWebExchange serverWebExchange, Runnable runnable) { + runnable.run(); + } + + private static R sneakyThrow(Throwable t) throws T { + throw (T) t; + } +} diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/resources/application.properties b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/resources/application.properties new file mode 100644 index 0000000000..76e70d400b --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/src/main/resources/application.properties @@ -0,0 +1,18 @@ +# +# 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. +# + +server.port=8080 diff --git a/test/plugin/scenarios/webflux-6.x-7.x-scenario/support-version.list b/test/plugin/scenarios/webflux-6.x-7.x-scenario/support-version.list new file mode 100644 index 0000000000..9691bd3086 --- /dev/null +++ b/test/plugin/scenarios/webflux-6.x-7.x-scenario/support-version.list @@ -0,0 +1,27 @@ +# +# 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. +# + +# Spring Boot versions. 3.x -> Spring Framework 6.x, 4.x -> Spring Framework 7.x. +# One version per minor, latest patch. +3.0.13 +3.1.12 +3.2.12 +3.3.13 +3.4.13 +3.5.16 +4.0.8 +4.1.1