From f9189a9f1387d129e607ae11fbd5c7a4bce53961 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 27 Nov 2025 07:52:06 -0500 Subject: [PATCH 1/8] spring boot 4 compatibility --- .../spring/gateway/v2_0/GatewayRouteMappingTest.java | 5 +---- .../spring-cloud-gateway-2.2/testing/build.gradle.kts | 9 ++++----- .../gateway/v2_2/Gateway22RouteMappingTest.java | 5 +---- .../gateway/common/AbstractRouteMappingTest.java | 11 ----------- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java index d3a907ec959b..0a2100ec8a3f 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java @@ -18,10 +18,7 @@ @ExtendWith(SpringExtension.class) @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = { - GatewayTestApplication.class, - GatewayRouteMappingTest.ForceNettyAutoConfiguration.class - }) + classes = {GatewayTestApplication.class}) class GatewayRouteMappingTest extends AbstractRouteMappingTest { @Test diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts index 37a67d795322..ae0ca55a61ec 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts @@ -14,10 +14,11 @@ dependencies { testLibrary("org.springframework.cloud:spring-cloud-starter-gateway:2.2.0.RELEASE") testLibrary("org.springframework.boot:spring-boot-starter-test:2.2.0.RELEASE") - // tests don't work with spring boot 4 yet - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // documented limitation + latestDepTestLibrary("org.springframework.boot:spring-boot-webmvc:latest.release") } +val latestDepTest = findProperty("testLatestDeps") as Boolean + tasks.withType().configureEach { jvmArgs("-Dotel.instrumentation.spring-cloud-gateway.experimental-span-attributes=true") @@ -27,11 +28,9 @@ tasks.withType().configureEach { jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") - systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + systemProperty("testLatestDeps", latestDepTest) } -val latestDepTest = findProperty("testLatestDeps") as Boolean - if (latestDepTest) { // spring 6 requires java 17 otelJava { diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java index 9f3279be7f1d..0c3e1d0bc77a 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java @@ -15,10 +15,7 @@ @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = { - Gateway22TestApplication.class, - Gateway22RouteMappingTest.ForceNettyAutoConfiguration.class - }) + classes = {Gateway22TestApplication.class}) class Gateway22RouteMappingTest extends AbstractRouteMappingTest { @Test diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java index d9b6c94fa8da..4ea817586ee8 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java @@ -18,20 +18,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; -import org.springframework.context.annotation.Bean; import org.springframework.util.StringUtils; public abstract class AbstractRouteMappingTest { - @TestConfiguration - public static class ForceNettyAutoConfiguration { - @Bean - NettyReactiveWebServerFactory nettyFactory() { - return new NettyReactiveWebServerFactory(); - } - } - @RegisterExtension protected static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); From 56cfa76c98b9b8077157cf60bece6e28006cbba2 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 4 Dec 2025 13:11:25 -0500 Subject: [PATCH 2/8] split out module --- .../javaagent/build.gradle.kts | 11 +- .../v2_0/GatewayInstrumentationModule.java | 4 +- .../HandlerFilterFunctionInstrumentation.java | 61 ++++++++ .../gateway/v2_0/ServerRequestHelper.java | 137 ++++++++++++++++++ .../testing/build.gradle.kts | 2 +- .../v2_2/Gateway22RouteMappingTest.java | 26 +--- .../v2_2/Gateway22TestApplication.java | 10 +- .../testing/build.gradle.kts | 1 + .../common/AbstractRouteMappingTest.java | 45 +++++- .../common/GatewayTestApplication.java | 16 ++ .../testing/build.gradle.kts | 32 ++++ .../v5_0/Gateway50RouteMappingTest.java | 14 ++ .../v5_0/Gateway50TestApplication.java | 12 ++ .../src/test/resources/application.yml | 10 ++ .../javaagent/build.gradle.kts | 39 +++++ ...legatingRouterFunctionInstrumentation.java | 64 ++++++++ .../GatewayWebMvcInstrumentationModule.java | 49 +++++++ .../webmvc/v5_0/ServerRequestHelper.java | 94 ++++++++++++ .../testing/build.gradle.kts | 32 ++++ .../Gateway50MvcRouteMappingTest.java | 45 ++++++ .../v5_0_mvc/Gateway50MvcTestApplication.java | 41 ++++++ .../src/test/resources/application.yml | 10 ++ ...ditionalLibraryIgnoredTypesConfigurer.java | 1 + settings.gradle.kts | 3 + 24 files changed, 716 insertions(+), 43 deletions(-) create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/GatewayTestApplication.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/build.gradle.kts create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/resources/application.yml create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts index 8e0e217bfbc1..bb98a04fc8ed 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts @@ -7,7 +7,16 @@ muzzle { pass { group.set("org.springframework.cloud") module.set("spring-cloud-starter-gateway") - versions.set("[2.0.0.RELEASE,]") + versions.set("[2.0.0.RELEASE,)") + assertInverse.set(true) + } + + // Spring Cloud Gateway 5.0+ split into separate artifacts + // see spring-cloud-starter-gateway-server-webmvc-5.0 for mvc + pass { + group.set("org.springframework.cloud") + module.set("spring-cloud-starter-gateway-server-webflux") + versions.set("[5.0.0,]") assertInverse.set(true) } } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayInstrumentationModule.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayInstrumentationModule.java index 567f54f0c332..695bf2f7be81 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayInstrumentationModule.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayInstrumentationModule.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; -import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; @@ -23,7 +23,7 @@ public GatewayInstrumentationModule() { @Override public List typeInstrumentations() { - return asList(new HandlerAdapterInstrumentation()); + return singletonList(new HandlerAdapterInstrumentation()); } @Override diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java new file mode 100644 index 000000000000..e5ee10520b25 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class HandlerFilterFunctionInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed( + "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); + } + + @Override + public ElementMatcher typeMatcher() { + return named( + "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(named("route")) + .and(takesArgument(0, named("org.springframework.web.servlet.function.ServerRequest"))) + .and(takesArguments(1)), + this.getClass().getName() + "$RouteAdvice"); + } + + @SuppressWarnings("unused") + public static class RouteAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter(@Advice.Argument(0) Object request) { + Context context = Java8BytecodeBridge.currentContext(); + // HttpServerRoute.update( + // context, + // HttpServerRouteSource.NESTED_CONTROLLER, + // (ctx, req) -> ServerRequestHelper.extractServerRoute(req), + // request); + // ServerRequestHelper.extractAttributes(request, context); + } + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java new file mode 100644 index 000000000000..20df0ec67388 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java @@ -0,0 +1,137 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; +import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.Optional; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +/** + * Helper class for extracting Spring Cloud Gateway Server WebMVC route information from + * ServerRequest and adding it to spans. + */ +final class ServerRequestHelper { + + /** Route ID attribute key. */ + private static final AttributeKey ROUTE_ID_ATTRIBUTE = + AttributeKey.stringKey("spring-cloud-gateway.route.id"); + + /** Route URI attribute key. */ + private static final AttributeKey ROUTE_URI_ATTRIBUTE = + AttributeKey.stringKey("spring-cloud-gateway.route.uri"); + + private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; + + static { + CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = + AgentInstrumentationConfig.get() + .getBoolean( + "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false); + } + + /* Regex for UUID */ + private static final Pattern UUID_REGEX = + Pattern.compile( + "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); + + // WebMVC variant uses these attribute keys (see MvcUtils class) + private static final String GATEWAY_ROUTE_ID_ATTR = "GatewayServerMvc.gatewayRouteId"; + private static final String GATEWAY_REQUEST_URL_ATTR = "GatewayServerMvc.gatewayRequestUrl"; + + private ServerRequestHelper() {} + + public static void extractAttributes(Object request, Context context) { + if (!CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { + return; + } + + try { + System.out.println( + "[DEBUG] ServerRequestHelper.extractAttributes called with request: " + + request.getClass().getName()); + + Span serverSpan = LocalRootSpan.fromContextOrNull(context); + if (serverSpan == null) { + System.out.println("[DEBUG] No server span found"); + return; + } + + // Use reflection to get attributes from ServerRequest + Method attributeMethod = request.getClass().getMethod("attribute", String.class); + + // Get route ID + Optional routeIdOpt = (Optional) attributeMethod.invoke(request, GATEWAY_ROUTE_ID_ATTR); + System.out.println( + "[DEBUG] routeId present: " + (routeIdOpt != null && routeIdOpt.isPresent())); + if (routeIdOpt != null && routeIdOpt.isPresent()) { + String routeId = (String) routeIdOpt.get(); + if (routeId != null) { + serverSpan.setAttribute(ROUTE_ID_ATTRIBUTE, routeId); + System.out.println("[DEBUG] Set route ID: " + routeId); + } + } + + // Get request URL (the target URI set by uri() filter) + Optional requestUrlOpt = + (Optional) attributeMethod.invoke(request, GATEWAY_REQUEST_URL_ATTR); + System.out.println( + "[DEBUG] requestUrl present: " + (requestUrlOpt != null && requestUrlOpt.isPresent())); + if (requestUrlOpt != null && requestUrlOpt.isPresent()) { + URI uri = (URI) requestUrlOpt.get(); + if (uri != null) { + serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, uri.toASCIIString()); + System.out.println("[DEBUG] Set route URI: " + uri.toASCIIString()); + } + } + + // Note: WebMVC variant doesn't store route order or filters in attributes + // Only route ID and request URL are available + } catch (Exception e) { + System.out.println("[DEBUG] Exception in extractAttributes: " + e.getMessage()); + e.printStackTrace(); + // Silently ignore - classes may not be present + } + } + + @Nullable + public static String extractServerRoute(@Nullable Object request) { + if (request == null) { + return null; + } + + try { + Method attributeMethod = request.getClass().getMethod("attribute", String.class); + Optional routeIdOpt = (Optional) attributeMethod.invoke(request, GATEWAY_ROUTE_ID_ATTR); + + if (routeIdOpt != null && routeIdOpt.isPresent()) { + String routeId = (String) routeIdOpt.get(); + return convergeRouteId(routeId); + } + } catch (Exception e) { + // Silently ignore + } + return null; + } + + /** To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. */ + @Nullable + private static String convergeRouteId(String routeId) { + if (routeId == null || routeId.isEmpty()) { + return null; + } + if (UUID_REGEX.matcher(routeId).matches()) { + return null; + } + return routeId; + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts index ae0ca55a61ec..7e61560c0ac6 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts @@ -14,7 +14,7 @@ dependencies { testLibrary("org.springframework.cloud:spring-cloud-starter-gateway:2.2.0.RELEASE") testLibrary("org.springframework.boot:spring-boot-starter-test:2.2.0.RELEASE") - latestDepTestLibrary("org.springframework.boot:spring-boot-webmvc:latest.release") + latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // see spring-cloud-gateway-5.0* module } val latestDepTest = findProperty("testLatestDeps") as Boolean diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java index 0c3e1d0bc77a..c79bf41698bc 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java @@ -5,34 +5,10 @@ package io.opentelemetry.instrumentation.spring.gateway.v2_2; -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Gateway22TestApplication.class}) -class Gateway22RouteMappingTest extends AbstractRouteMappingTest { - - @Test - void gatewayRouteMappingTest() { - String requestBody = "gateway"; - AggregatedHttpResponse response = client.post("/gateway/echo", requestBody).aggregate().join(); - assertThat(response.status().code()).isEqualTo(200); - assertThat(response.contentUtf8()).isEqualTo(requestBody); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("POST") - .hasKind(SpanKind.SERVER) - .hasAttributesSatisfying( - // Global filter is not route filter, so filter size should be 0. - buildAttributeAssertions("h1c://mock.response", 2023, 0)), - span -> span.hasName(WEBFLUX_SPAN_NAME).hasKind(SpanKind.INTERNAL))); - } -} +class Gateway22RouteMappingTest extends AbstractRouteMappingTest {} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22TestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22TestApplication.java index b8fd00860440..f47685d374a4 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22TestApplication.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22TestApplication.java @@ -5,14 +5,8 @@ package io.opentelemetry.instrumentation.spring.gateway.v2_2; +import io.opentelemetry.instrumentation.spring.gateway.common.GatewayTestApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.context.annotation.Bean; @SpringBootApplication -public class Gateway22TestApplication { - @Bean - public GlobalFilter echoFilter() { - return (exchange, chain) -> exchange.getResponse().writeWith(exchange.getRequest().getBody()); - } -} +public class Gateway22TestApplication extends GatewayTestApplication {} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/build.gradle.kts index 7e8f0a0dd6d5..95af1a7be3bb 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/build.gradle.kts @@ -7,4 +7,5 @@ dependencies { implementation("io.opentelemetry.javaagent:opentelemetry-testing-common") compileOnly("org.springframework.boot:spring-boot-starter-test:2.0.0.RELEASE") + compileOnly("org.springframework.cloud:spring-cloud-starter-gateway:2.2.0.RELEASE") } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java index 4ea817586ee8..19bc2042be99 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java @@ -5,17 +5,22 @@ package io.opentelemetry.instrumentation.spring.gateway.common; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import io.opentelemetry.testing.internal.armeria.client.WebClient; +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; @@ -31,21 +36,49 @@ public abstract class AbstractRouteMappingTest { protected static final String WEBFLUX_SPAN_NAME = "FilteringWebHandler.handle"; + protected String getSpanName() { + return "POST"; + } + + protected String getInternalSpanName() { + return WEBFLUX_SPAN_NAME; + } + + protected List getExpectedAttributes() { + // Global filter is not route filter, so filter size should be 0. + return buildAttributeAssertions("h1c://mock.response", 2023, 0); + } + @BeforeEach void beforeEach() { client = WebClient.builder("h1c://localhost:" + port).followRedirects().build(); } + @Test + void gatewayRouteMappingTest() { + String requestBody = "gateway"; + AggregatedHttpResponse response = client.post("/gateway/echo", requestBody).aggregate().join(); + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8()).isEqualTo(requestBody); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(getSpanName()) + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfying(getExpectedAttributes()), + span -> span.hasName(getInternalSpanName()).hasKind(SpanKind.INTERNAL))); + } + protected List buildAttributeAssertions( @Nullable String routeId, String uri, int order, int filterSize) { List assertions = new ArrayList<>(); if (!StringUtils.isEmpty(routeId)) { - assertions.add(equalTo(AttributeKey.stringKey("spring-cloud-gateway.route.id"), routeId)); + assertions.add(equalTo(stringKey("spring-cloud-gateway.route.id"), routeId)); } - assertions.add(equalTo(AttributeKey.stringKey("spring-cloud-gateway.route.uri"), uri)); - assertions.add(equalTo(AttributeKey.longKey("spring-cloud-gateway.route.order"), order)); - assertions.add( - equalTo(AttributeKey.longKey("spring-cloud-gateway.route.filter.size"), filterSize)); + assertions.add(equalTo(stringKey("spring-cloud-gateway.route.uri"), uri)); + assertions.add(equalTo(longKey("spring-cloud-gateway.route.order"), order)); + assertions.add(equalTo(longKey("spring-cloud-gateway.route.filter.size"), filterSize)); return assertions; } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/GatewayTestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/GatewayTestApplication.java new file mode 100644 index 000000000000..9a6d35a59c62 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/GatewayTestApplication.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.gateway.common; + +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.context.annotation.Bean; + +public abstract class GatewayTestApplication { + @Bean + public GlobalFilter echoFilter() { + return (exchange, chain) -> exchange.getResponse().writeWith(exchange.getRequest().getBody()); + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/build.gradle.kts new file mode 100644 index 000000000000..d562c0c1d964 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("otel.javaagent-testing") +} + +dependencies { + testInstrumentation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.0:javaagent")) + + testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) + testInstrumentation(project(":instrumentation:reactor:reactor-3.1:javaagent")) + testInstrumentation(project(":instrumentation:reactor:reactor-netty:reactor-netty-1.0:javaagent")) + testInstrumentation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.0:javaagent")) + + testImplementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing")) + + testLibrary("org.springframework.cloud:spring-cloud-starter-gateway-server-webflux:5.0.0") + testLibrary("org.springframework.boot:spring-boot-starter-test:4.0.0") +} + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.spring-cloud-gateway.experimental-span-attributes=true") + + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") +} + +// spring 7 requires java 17 +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java new file mode 100644 index 000000000000..aca42bffa755 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.gateway.v5_0; + +import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = {Gateway50TestApplication.class}) +class Gateway50RouteMappingTest extends AbstractRouteMappingTest {} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java new file mode 100644 index 000000000000..f72f732e9c54 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java @@ -0,0 +1,12 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.gateway.v5_0; + +import io.opentelemetry.instrumentation.spring.gateway.common.GatewayTestApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Gateway50TestApplication extends GatewayTestApplication {} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/resources/application.yml b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/resources/application.yml new file mode 100644 index 000000000000..141a92f9cc8a --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/resources/application.yml @@ -0,0 +1,10 @@ +spring: + cloud: + gateway: + server: + webflux: + routes: + - uri: h1c://mock.response + predicates: + - Path=/gateway/echo + order: 2023 \ No newline at end of file diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..2e83c91ae6c6 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.springframework.cloud") + module.set("spring-cloud-starter-gateway-server-webmvc") + versions.set("[5.0.0,)") + assertInverse.set(true) + } +} + +dependencies { + library("org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc:5.0.0") + + testInstrumentation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:javaagent")) + testInstrumentation(project(":instrumentation:tomcat:tomcat-10.0:javaagent")) + + testImplementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing")) + + testLibrary("org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc:5.0.0") + testLibrary("org.springframework.boot:spring-boot-starter-test:4.0.0") +} + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.spring-cloud-gateway.experimental-span-attributes=true") + + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") +} + +// spring 7 requires java 17 +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java new file mode 100644 index 000000000000..36ffcfb14ad0 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.springframework.web.servlet.function.ServerRequest; + +/** + * Instrumentation for Spring Cloud Gateway Server WebMVC. This instruments + * GatewayDelegatingRouterFunction which wraps routes and sets route attributes. + */ +public class GatewayDelegatingRouterFunctionInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed( + "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); + } + + @Override + public ElementMatcher typeMatcher() { + return named( + "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); + } + + @Override + public void transform(TypeTransformer transformer) { + // Instrument the route method that processes requests + transformer.applyAdviceToMethod( + isMethod() + .and(isPublic()) + .and(named("route")) + .and(takesArgument(0, named("org.springframework.web.servlet.function.ServerRequest"))) + .and(takesArguments(1)), + this.getClass().getName() + "$RouteAdvice"); + } + + @SuppressWarnings("unused") + public static class RouteAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void methodExit( + @Advice.This Object thisObj, @Advice.Argument(0) ServerRequest request) { + Context context = Context.current(); + // Record gateway route info as attributes (not as HTTP route) + // The HTTP route should remain the actual path pattern from Spring WebMVC + ServerRequestHelper.extractAttributes(thisObj, request, context); + } + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java new file mode 100644 index 000000000000..6b59cbcf4993 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static java.util.Collections.singletonList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule; +import java.util.List; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class GatewayWebMvcInstrumentationModule extends InstrumentationModule + implements ExperimentalInstrumentationModule { + + public GatewayWebMvcInstrumentationModule() { + super("spring-cloud-gateway", "spring-cloud-gateway-webmvc-5.0"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + // Spring Cloud Gateway Server WebMVC 5.0+ + return hasClassesNamed( + "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new GatewayDelegatingRouterFunctionInstrumentation()); + } + + @Override + public String getModuleGroup() { + // relies on servlet + return "servlet"; + } + + @Override + public int order() { + // Later than Spring WebMVC + return 1; + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java new file mode 100644 index 000000000000..28adb2754173 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java @@ -0,0 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; +import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; +import java.net.URI; +import java.util.Optional; +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; +import org.springframework.web.servlet.function.ServerRequest; + +/** + * Helper class for extracting Spring Cloud Gateway Server WebMVC route information from + * ServerRequest and adding it to spans. + */ +public final class ServerRequestHelper { + + /** Route ID attribute key. */ + private static final AttributeKey ROUTE_ID_ATTRIBUTE = + AttributeKey.stringKey("spring-cloud-gateway.route.id"); + + /** Route URI attribute key. */ + private static final AttributeKey ROUTE_URI_ATTRIBUTE = + AttributeKey.stringKey("spring-cloud-gateway.route.uri"); + + private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; + + static { + CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = + AgentInstrumentationConfig.get() + .getBoolean( + "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false); + } + + /* Regex for UUID */ + private static final Pattern UUID_REGEX = + Pattern.compile( + "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); + + private ServerRequestHelper() {} + + public static void extractAttributes( + Object gatewayRouterFunction, ServerRequest request, Context context) { + if (!CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { + return; + } + + Span serverSpan = LocalRootSpan.fromContextOrNull(context); + if (serverSpan == null) { + return; + } + + // Get route ID from the GatewayDelegatingRouterFunction field + try { + java.lang.reflect.Field routeIdField = + gatewayRouterFunction.getClass().getDeclaredField("routeId"); + routeIdField.setAccessible(true); + String routeId = (String) routeIdField.get(gatewayRouterFunction); + String convergedRouteId = convergeRouteId(routeId); + if (convergedRouteId != null) { + serverSpan.setAttribute(ROUTE_ID_ATTRIBUTE, convergedRouteId); + } + } catch (Exception e) { + // Silently ignore - field may not be accessible + } + + // Get request URL (the target URI set by uri() filter) + // Note: This is typically not available at the point when route() is called, + // as filters execute later in the request processing chain + Optional requestUrlOpt = request.attribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR); + requestUrlOpt.ifPresent( + uri -> serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, ((URI) uri).toASCIIString())); + } + + /** To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. */ + @Nullable + private static String convergeRouteId(String routeId) { + if (routeId == null || routeId.isEmpty()) { + return null; + } + if (UUID_REGEX.matcher(routeId).matches()) { + return null; + } + return routeId; + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts new file mode 100644 index 000000000000..56457b3d72f7 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + id("otel.javaagent-testing") +} + +dependencies { + testInstrumentation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-5.0:javaagent")) + + // WebMVC instrumentations + testInstrumentation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:javaagent")) + testInstrumentation(project(":instrumentation:tomcat:tomcat-10.0:javaagent")) + + testImplementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing")) + + // Spring Cloud Gateway 5.0 WebMVC variant + testLibrary("org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc:5.0.0") + testLibrary("org.springframework.boot:spring-boot-starter-test:4.0.0") +} + +tasks.withType().configureEach { + jvmArgs("-Dotel.instrumentation.spring-cloud-gateway.experimental-span-attributes=true") + + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") +} + +// spring 7 requires java 17 +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java new file mode 100644 index 000000000000..6b1ac6977a58 --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.gateway.v5_0_mvc; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import java.util.ArrayList; +import java.util.List; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = {Gateway50MvcTestApplication.class}) +class Gateway50MvcRouteMappingTest extends AbstractRouteMappingTest { + + @Override + protected String getSpanName() { + return "POST /gateway/echo"; + } + + @Override + protected String getInternalSpanName() { + // WebMVC creates an internal span for the handler filter function + return "HandlerFilterFunction$$Lambda."; + } + + /** + * WebMVC variant only has access to route ID at the point of instrumentation. Route URI is set by + * filters that execute later and is not available when the route method is called. Order and + * filter size are not available in request attributes. + */ + @Override + protected List getExpectedAttributes() { + List assertions = new ArrayList<>(); + assertions.add( + equalTo(AttributeKey.stringKey("spring-cloud-gateway.route.id"), "test-route-id")); + return assertions; + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java new file mode 100644 index 000000000000..21797f7c708f --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.gateway.v5_0_mvc; + +import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri; +import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; + +import java.io.IOException; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.function.HandlerFunction; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; + +@SpringBootApplication +public class Gateway50MvcTestApplication { + @Bean + public RouterFunction gatewayRouterFunction() { + HandlerFunction echoHandler = + request -> { + try { + String body = request.body(String.class); + return ServerResponse.status(HttpStatus.OK) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(body); + } catch (IOException e) { + return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + }; + + return route("test-route-id") + .POST("/gateway/echo", echoHandler) + .before(uri("http://mock.response")) + .build(); + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml new file mode 100644 index 000000000000..141a92f9cc8a --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml @@ -0,0 +1,10 @@ +spring: + cloud: + gateway: + server: + webflux: + routes: + - uri: h1c://mock.response + predicates: + - Path=/gateway/echo + order: 2023 \ No newline at end of file diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java index 3ef71a9ab6d2..32491266e876 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java @@ -126,6 +126,7 @@ public void configure(IgnoredTypesBuilder builder) { .allowClass( "org.springframework.boot.autoconfigure.web.WebProperties$Resources$Cache$Cachecontrol$$Lambda") .allowClass("org.springframework.boot.web.embedded.netty.NettyWebServer$") + .allowClass("org.springframework.boot.reactor.netty.NettyWebServer$") .allowClass("org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext$$Lambda") .allowClass("org.springframework.boot.tomcat.TomcatEmbeddedContext$$Lambda") .allowClass("org.springframework.boot.tomcat.TomcatWebServer$") diff --git a/settings.gradle.kts b/settings.gradle.kts index c175394c2dd4..9584011b7783 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -618,6 +618,9 @@ include(":instrumentation:spring:spring-boot-resources:javaagent-unit-tests") include(":instrumentation:spring:spring-cloud-aws-3.0:javaagent") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.0:javaagent") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.2:testing") +include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webflux-5.0:testing") +include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-5.0:javaagent") +include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-5.0:testing") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing") include(":instrumentation:spring:spring-core-2.0:javaagent") include(":instrumentation:spring:spring-data:spring-data-1.8:javaagent") From c03e214ad7b1471c07032cc3469259e1480b25c3 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 4 Dec 2025 13:52:43 -0500 Subject: [PATCH 3/8] consolidate --- .../javaagent/build.gradle.kts | 2 + .../HandlerFilterFunctionInstrumentation.java | 61 -------- .../gateway/v2_0/ServerRequestHelper.java | 137 ------------------ .../gateway/v2_0/ServerWebExchangeHelper.java | 67 +-------- .../v2_2/Gateway22RouteMappingTest.java | 9 +- .../javaagent/build.gradle.kts | 8 + .../gateway/common/GatewayRouteHelper.java | 80 ++++++++++ .../common/AbstractRouteMappingTest.java | 4 +- .../v5_0/Gateway50RouteMappingTest.java | 9 +- .../javaagent/build.gradle.kts | 2 + ...legatingRouterFunctionInstrumentation.java | 7 +- .../GatewayWebMvcInstrumentationModule.java | 7 - .../webmvc/v5_0/ServerRequestHelper.java | 56 ++----- .../Gateway50MvcRouteMappingTest.java | 6 + .../v5_0_mvc/Gateway50MvcTestApplication.java | 0 .../testing/build.gradle.kts | 32 ---- .../src/test/resources/application.yml | 10 -- settings.gradle.kts | 2 +- 18 files changed, 133 insertions(+), 366 deletions(-) delete mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java delete mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts create mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/common/GatewayRouteHelper.java rename instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/{testing => javaagent}/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java (93%) rename instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/{testing => javaagent}/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java (100%) delete mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts delete mode 100644 instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts index bb98a04fc8ed..a2920ece822f 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts @@ -24,6 +24,8 @@ muzzle { dependencies { library("org.springframework.cloud:spring-cloud-starter-gateway:2.0.0.RELEASE") + implementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:javaagent")) + testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent")) testInstrumentation(project(":instrumentation:reactor:reactor-3.1:javaagent")) testInstrumentation(project(":instrumentation:reactor:reactor-netty:reactor-netty-1.0:javaagent")) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java deleted file mode 100644 index e5ee10520b25..000000000000 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/HandlerFilterFunctionInstrumentation.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; - -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; -import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; -import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; -import static net.bytebuddy.matcher.ElementMatchers.takesArguments; - -import io.opentelemetry.context.Context; -import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; -import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; -import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; -import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.type.TypeDescription; -import net.bytebuddy.matcher.ElementMatcher; - -public class HandlerFilterFunctionInstrumentation implements TypeInstrumentation { - - @Override - public ElementMatcher classLoaderOptimization() { - return hasClassesNamed( - "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); - } - - @Override - public ElementMatcher typeMatcher() { - return named( - "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); - } - - @Override - public void transform(TypeTransformer transformer) { - transformer.applyAdviceToMethod( - isMethod() - .and(isPublic()) - .and(named("route")) - .and(takesArgument(0, named("org.springframework.web.servlet.function.ServerRequest"))) - .and(takesArguments(1)), - this.getClass().getName() + "$RouteAdvice"); - } - - @SuppressWarnings("unused") - public static class RouteAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void methodEnter(@Advice.Argument(0) Object request) { - Context context = Java8BytecodeBridge.currentContext(); - // HttpServerRoute.update( - // context, - // HttpServerRouteSource.NESTED_CONTROLLER, - // (ctx, req) -> ServerRequestHelper.extractServerRoute(req), - // request); - // ServerRequestHelper.extractAttributes(request, context); - } - } -} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java deleted file mode 100644 index 20df0ec67388..000000000000 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerRequestHelper.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.context.Context; -import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; -import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Optional; -import java.util.regex.Pattern; -import javax.annotation.Nullable; - -/** - * Helper class for extracting Spring Cloud Gateway Server WebMVC route information from - * ServerRequest and adding it to spans. - */ -final class ServerRequestHelper { - - /** Route ID attribute key. */ - private static final AttributeKey ROUTE_ID_ATTRIBUTE = - AttributeKey.stringKey("spring-cloud-gateway.route.id"); - - /** Route URI attribute key. */ - private static final AttributeKey ROUTE_URI_ATTRIBUTE = - AttributeKey.stringKey("spring-cloud-gateway.route.uri"); - - private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; - - static { - CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = - AgentInstrumentationConfig.get() - .getBoolean( - "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false); - } - - /* Regex for UUID */ - private static final Pattern UUID_REGEX = - Pattern.compile( - "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); - - // WebMVC variant uses these attribute keys (see MvcUtils class) - private static final String GATEWAY_ROUTE_ID_ATTR = "GatewayServerMvc.gatewayRouteId"; - private static final String GATEWAY_REQUEST_URL_ATTR = "GatewayServerMvc.gatewayRequestUrl"; - - private ServerRequestHelper() {} - - public static void extractAttributes(Object request, Context context) { - if (!CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { - return; - } - - try { - System.out.println( - "[DEBUG] ServerRequestHelper.extractAttributes called with request: " - + request.getClass().getName()); - - Span serverSpan = LocalRootSpan.fromContextOrNull(context); - if (serverSpan == null) { - System.out.println("[DEBUG] No server span found"); - return; - } - - // Use reflection to get attributes from ServerRequest - Method attributeMethod = request.getClass().getMethod("attribute", String.class); - - // Get route ID - Optional routeIdOpt = (Optional) attributeMethod.invoke(request, GATEWAY_ROUTE_ID_ATTR); - System.out.println( - "[DEBUG] routeId present: " + (routeIdOpt != null && routeIdOpt.isPresent())); - if (routeIdOpt != null && routeIdOpt.isPresent()) { - String routeId = (String) routeIdOpt.get(); - if (routeId != null) { - serverSpan.setAttribute(ROUTE_ID_ATTRIBUTE, routeId); - System.out.println("[DEBUG] Set route ID: " + routeId); - } - } - - // Get request URL (the target URI set by uri() filter) - Optional requestUrlOpt = - (Optional) attributeMethod.invoke(request, GATEWAY_REQUEST_URL_ATTR); - System.out.println( - "[DEBUG] requestUrl present: " + (requestUrlOpt != null && requestUrlOpt.isPresent())); - if (requestUrlOpt != null && requestUrlOpt.isPresent()) { - URI uri = (URI) requestUrlOpt.get(); - if (uri != null) { - serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, uri.toASCIIString()); - System.out.println("[DEBUG] Set route URI: " + uri.toASCIIString()); - } - } - - // Note: WebMVC variant doesn't store route order or filters in attributes - // Only route ID and request URL are available - } catch (Exception e) { - System.out.println("[DEBUG] Exception in extractAttributes: " + e.getMessage()); - e.printStackTrace(); - // Silently ignore - classes may not be present - } - } - - @Nullable - public static String extractServerRoute(@Nullable Object request) { - if (request == null) { - return null; - } - - try { - Method attributeMethod = request.getClass().getMethod("attribute", String.class); - Optional routeIdOpt = (Optional) attributeMethod.invoke(request, GATEWAY_ROUTE_ID_ATTR); - - if (routeIdOpt != null && routeIdOpt.isPresent()) { - String routeId = (String) routeIdOpt.get(); - return convergeRouteId(routeId); - } - } catch (Exception e) { - // Silently ignore - } - return null; - } - - /** To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. */ - @Nullable - private static String convergeRouteId(String routeId) { - if (routeId == null || routeId.isEmpty()) { - return null; - } - if (UUID_REGEX.matcher(routeId).matches()) { - return null; - } - return routeId; - } -} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerWebExchangeHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerWebExchangeHelper.java index f93f7f4362f2..ab557ca9ddda 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerWebExchangeHelper.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerWebExchangeHelper.java @@ -5,59 +5,28 @@ package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; +import static io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper.ROUTE_FILTER_SIZE_ATTRIBUTE; +import static io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper.ROUTE_ID_ATTRIBUTE; +import static io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper.ROUTE_ORDER_ATTRIBUTE; +import static io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper.ROUTE_URI_ATTRIBUTE; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; -import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; -import java.util.regex.Pattern; +import io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper; import javax.annotation.Nullable; import org.springframework.cloud.gateway.route.Route; import org.springframework.web.server.ServerWebExchange; public final class ServerWebExchangeHelper { - /** Route ID attribute key. */ - private static final AttributeKey ROUTE_ID_ATTRIBUTE = - AttributeKey.stringKey("spring-cloud-gateway.route.id"); - - /** Route URI attribute key. */ - private static final AttributeKey ROUTE_URI_ATTRIBUTE = - AttributeKey.stringKey("spring-cloud-gateway.route.uri"); - - /** Route order attribute key. */ - private static final AttributeKey ROUTE_ORDER_ATTRIBUTE = - AttributeKey.longKey("spring-cloud-gateway.route.order"); - - /** Route filter size attribute key. */ - private static final AttributeKey ROUTE_FILTER_SIZE_ATTRIBUTE = - AttributeKey.longKey("spring-cloud-gateway.route.filter.size"); - - private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; - - static { - CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = - AgentInstrumentationConfig.get() - .getBoolean( - "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false); - } - - /* Regex for UUID */ - private static final Pattern UUID_REGEX = - Pattern.compile( - "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); - - private static final String INVALID_RANDOM_ROUTE_ID = - "org.springframework.util.AlternativeJdkIdGenerator@"; - private ServerWebExchangeHelper() {} public static void extractAttributes(ServerWebExchange exchange, Context context) { // Record route info Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - if (route != null && CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { + if (route != null && GatewayRouteHelper.shouldCaptureExperimentalSpanAttributes()) { Span serverSpan = LocalRootSpan.fromContextOrNull(context); if (serverSpan == null) { return; @@ -76,30 +45,8 @@ public static String extractServerRoute(@Nullable ServerWebExchange exchange) { } Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); if (route != null) { - return convergeRouteId(route); + return GatewayRouteHelper.convergeRouteId(route.getId()); } return null; } - - /** - * To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. Spring - * Cloud Gateway generate invalid random routeID, and it is fixed until 3.1.x - * - * @see - */ - @Nullable - private static String convergeRouteId(Route route) { - String routeId = route.getId(); - if (routeId == null || routeId.isEmpty()) { - return null; - } - if (UUID_REGEX.matcher(routeId).matches()) { - return null; - } - if (routeId.startsWith(INVALID_RANDOM_ROUTE_ID)) { - return null; - } - return routeId; - } } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java index c79bf41698bc..0055db65509a 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java @@ -6,9 +6,16 @@ package io.opentelemetry.instrumentation.spring.gateway.v2_2; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Gateway22TestApplication.class}) -class Gateway22RouteMappingTest extends AbstractRouteMappingTest {} +class Gateway22RouteMappingTest extends AbstractRouteMappingTest { + + @Test + void gatewayRouteMappingTest() { + testGatewayRouteMapping(); + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts new file mode 100644 index 000000000000..7aaf0ba68c7d --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +dependencies { + compileOnly("io.opentelemetry:opentelemetry-api") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/common/GatewayRouteHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/common/GatewayRouteHelper.java new file mode 100644 index 000000000000..89d8b72e917e --- /dev/null +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/common/GatewayRouteHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spring.gateway.common; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +/** + * Shared helper class for Spring Cloud Gateway instrumentation across different versions (WebFlux + * and WebMVC). + */ +public final class GatewayRouteHelper { + + /** Route ID attribute key. */ + public static final AttributeKey ROUTE_ID_ATTRIBUTE = + AttributeKey.stringKey("spring-cloud-gateway.route.id"); + + /** Route URI attribute key. */ + public static final AttributeKey ROUTE_URI_ATTRIBUTE = + AttributeKey.stringKey("spring-cloud-gateway.route.uri"); + + /** Route order attribute key. */ + public static final AttributeKey ROUTE_ORDER_ATTRIBUTE = + AttributeKey.longKey("spring-cloud-gateway.route.order"); + + /** Route filter size attribute key. */ + public static final AttributeKey ROUTE_FILTER_SIZE_ATTRIBUTE = + AttributeKey.longKey("spring-cloud-gateway.route.filter.size"); + + private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; + + static { + CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = + AgentInstrumentationConfig.get() + .getBoolean( + "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false); + } + + /* Regex for UUID */ + private static final Pattern UUID_REGEX = + Pattern.compile( + "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); + + private static final String INVALID_RANDOM_ROUTE_ID = + "org.springframework.util.AlternativeJdkIdGenerator@"; + + private GatewayRouteHelper() {} + + /** Returns whether experimental span attributes should be captured. */ + public static boolean shouldCaptureExperimentalSpanAttributes() { + return CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; + } + + /** + * To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. Spring + * Cloud Gateway generates invalid random routeID, and it is fixed until 3.1.x + * + * @see Spring + * Cloud Gateway commit + */ + @Nullable + public static String convergeRouteId(@Nullable String routeId) { + if (routeId == null || routeId.isEmpty()) { + return null; + } + if (UUID_REGEX.matcher(routeId).matches()) { + return null; + } + if (routeId.startsWith(INVALID_RANDOM_ROUTE_ID)) { + return null; + } + return routeId; + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java index 19bc2042be99..e2aa5c621d9b 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java @@ -20,7 +20,6 @@ import java.util.List; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; @@ -54,8 +53,7 @@ void beforeEach() { client = WebClient.builder("h1c://localhost:" + port).followRedirects().build(); } - @Test - void gatewayRouteMappingTest() { + protected void testGatewayRouteMapping() { String requestBody = "gateway"; AggregatedHttpResponse response = client.post("/gateway/echo", requestBody).aggregate().join(); assertThat(response.status().code()).isEqualTo(200); diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java index aca42bffa755..e3549011f5f0 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java @@ -6,9 +6,16 @@ package io.opentelemetry.instrumentation.spring.gateway.v5_0; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Gateway50TestApplication.class}) -class Gateway50RouteMappingTest extends AbstractRouteMappingTest {} +class Gateway50RouteMappingTest extends AbstractRouteMappingTest { + + @Test + void gatewayRouteMappingTest() { + testGatewayRouteMapping(); + } +} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts index 2e83c91ae6c6..c3ecb5e9ab6c 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts @@ -14,6 +14,8 @@ muzzle { dependencies { library("org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc:5.0.0") + implementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:javaagent")) + testInstrumentation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:javaagent")) testInstrumentation(project(":instrumentation:tomcat:tomcat-10.0:javaagent")) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java index 36ffcfb14ad0..d6df535d04a8 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java @@ -20,10 +20,6 @@ import net.bytebuddy.matcher.ElementMatcher; import org.springframework.web.servlet.function.ServerRequest; -/** - * Instrumentation for Spring Cloud Gateway Server WebMVC. This instruments - * GatewayDelegatingRouterFunction which wraps routes and sets route attributes. - */ public class GatewayDelegatingRouterFunctionInstrumentation implements TypeInstrumentation { @Override @@ -40,7 +36,6 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { - // Instrument the route method that processes requests transformer.applyAdviceToMethod( isMethod() .and(isPublic()) @@ -56,7 +51,7 @@ public static class RouteAdvice { public static void methodExit( @Advice.This Object thisObj, @Advice.Argument(0) ServerRequest request) { Context context = Context.current(); - // Record gateway route info as attributes (not as HTTP route) + // Record gateway route info as attributes // The HTTP route should remain the actual path pattern from Spring WebMVC ServerRequestHelper.extractAttributes(thisObj, request, context); } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java index 6b59cbcf4993..2a7f4100f11a 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java @@ -25,7 +25,6 @@ public GatewayWebMvcInstrumentationModule() { @Override public ElementMatcher.Junction classLoaderMatcher() { - // Spring Cloud Gateway Server WebMVC 5.0+ return hasClassesNamed( "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); } @@ -35,12 +34,6 @@ public List typeInstrumentations() { return singletonList(new GatewayDelegatingRouterFunctionInstrumentation()); } - @Override - public String getModuleGroup() { - // relies on servlet - return "servlet"; - } - @Override public int order() { // Later than Spring WebMVC diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java index 28adb2754173..c74d2ee32d2f 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java @@ -5,15 +5,16 @@ package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; -import io.opentelemetry.api.common.AttributeKey; +import static io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper.ROUTE_ID_ATTRIBUTE; +import static io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper.ROUTE_URI_ATTRIBUTE; + import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; -import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig; +import io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper; +import java.lang.reflect.Field; import java.net.URI; import java.util.Optional; -import java.util.regex.Pattern; -import javax.annotation.Nullable; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.web.servlet.function.ServerRequest; @@ -23,33 +24,11 @@ */ public final class ServerRequestHelper { - /** Route ID attribute key. */ - private static final AttributeKey ROUTE_ID_ATTRIBUTE = - AttributeKey.stringKey("spring-cloud-gateway.route.id"); - - /** Route URI attribute key. */ - private static final AttributeKey ROUTE_URI_ATTRIBUTE = - AttributeKey.stringKey("spring-cloud-gateway.route.uri"); - - private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; - - static { - CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = - AgentInstrumentationConfig.get() - .getBoolean( - "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false); - } - - /* Regex for UUID */ - private static final Pattern UUID_REGEX = - Pattern.compile( - "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); - private ServerRequestHelper() {} public static void extractAttributes( Object gatewayRouterFunction, ServerRequest request, Context context) { - if (!CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { + if (!GatewayRouteHelper.shouldCaptureExperimentalSpanAttributes()) { return; } @@ -58,37 +37,20 @@ public static void extractAttributes( return; } - // Get route ID from the GatewayDelegatingRouterFunction field try { - java.lang.reflect.Field routeIdField = - gatewayRouterFunction.getClass().getDeclaredField("routeId"); + Field routeIdField = gatewayRouterFunction.getClass().getDeclaredField("routeId"); routeIdField.setAccessible(true); String routeId = (String) routeIdField.get(gatewayRouterFunction); - String convergedRouteId = convergeRouteId(routeId); + String convergedRouteId = GatewayRouteHelper.convergeRouteId(routeId); if (convergedRouteId != null) { serverSpan.setAttribute(ROUTE_ID_ATTRIBUTE, convergedRouteId); } } catch (Exception e) { - // Silently ignore - field may not be accessible + // Silently ignore } - // Get request URL (the target URI set by uri() filter) - // Note: This is typically not available at the point when route() is called, - // as filters execute later in the request processing chain Optional requestUrlOpt = request.attribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR); requestUrlOpt.ifPresent( uri -> serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, ((URI) uri).toASCIIString())); } - - /** To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. */ - @Nullable - private static String convergeRouteId(String routeId) { - if (routeId == null || routeId.isEmpty()) { - return null; - } - if (UUID_REGEX.matcher(routeId).matches()) { - return null; - } - return routeId; - } } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java similarity index 93% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java index 6b1ac6977a58..36c3d1fb46f2 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java @@ -12,6 +12,7 @@ import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( @@ -19,6 +20,11 @@ classes = {Gateway50MvcTestApplication.class}) class Gateway50MvcRouteMappingTest extends AbstractRouteMappingTest { + @Test + void gatewayRouteMappingTest() { + testGatewayRouteMapping(); + } + @Override protected String getSpanName() { return "POST /gateway/echo"; diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java similarity index 100% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts deleted file mode 100644 index 56457b3d72f7..000000000000 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - id("otel.javaagent-testing") -} - -dependencies { - testInstrumentation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-5.0:javaagent")) - - // WebMVC instrumentations - testInstrumentation(project(":instrumentation:spring:spring-webmvc:spring-webmvc-6.0:javaagent")) - testInstrumentation(project(":instrumentation:tomcat:tomcat-10.0:javaagent")) - - testImplementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing")) - - // Spring Cloud Gateway 5.0 WebMVC variant - testLibrary("org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc:5.0.0") - testLibrary("org.springframework.boot:spring-boot-starter-test:4.0.0") -} - -tasks.withType().configureEach { - jvmArgs("-Dotel.instrumentation.spring-cloud-gateway.experimental-span-attributes=true") - - // required on jdk17 - jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") - jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") - - jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") -} - -// spring 7 requires java 17 -otelJava { - minJavaVersionSupported.set(JavaVersion.VERSION_17) -} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml deleted file mode 100644 index 141a92f9cc8a..000000000000 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/testing/src/test/resources/application.yml +++ /dev/null @@ -1,10 +0,0 @@ -spring: - cloud: - gateway: - server: - webflux: - routes: - - uri: h1c://mock.response - predicates: - - Path=/gateway/echo - order: 2023 \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 9584011b7783..211b81fa9a06 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -620,7 +620,7 @@ include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.0:j include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.2:testing") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webflux-5.0:testing") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-5.0:javaagent") -include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-5.0:testing") +include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:javaagent") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing") include(":instrumentation:spring:spring-core-2.0:javaagent") include(":instrumentation:spring:spring-data:spring-data-1.8:javaagent") From 7e7221424dd194ac4c9ef4ec6a2a52d9d65a2bce Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 4 Dec 2025 14:48:57 -0500 Subject: [PATCH 4/8] fossa and change minimum version --- .fossa.yml | 6 ++++++ .github/scripts/check-javaagent-suppression-keys.sh | 4 ++++ .../spring-cloud-gateway-2.0/javaagent/build.gradle.kts | 6 +++--- .../spring-cloud-gateway-2.2/testing/build.gradle.kts | 2 +- .../testing/build.gradle.kts | 1 + .../spring/gateway/v4_3/Gateway43RouteMappingTest.java} | 6 +++--- .../spring/gateway/v4_3/Gateway43TestApplication.java} | 4 ++-- .../testing/src/test/resources/application.yml | 0 .../javaagent/build.gradle.kts | 4 ++-- .../GatewayDelegatingRouterFunctionInstrumentation.java | 0 .../webmvc/v5_0/GatewayWebMvcInstrumentationModule.java | 2 +- .../spring/gateway/webmvc/v5_0/ServerRequestHelper.java | 0 .../gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java} | 4 ++-- .../gateway/v5_0_mvc/Gateway43MvcTestApplication.java} | 2 +- settings.gradle.kts | 4 ++-- 15 files changed, 28 insertions(+), 17 deletions(-) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webflux-5.0 => spring-cloud-gateway-webflux-4.3}/testing/build.gradle.kts (96%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java => spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java} (71%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java => spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43TestApplication.java} (68%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webflux-5.0 => spring-cloud-gateway-webflux-4.3}/testing/src/test/resources/application.yml (100%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webmvc-5.0 => spring-cloud-gateway-webmvc-4.3}/javaagent/build.gradle.kts (96%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webmvc-5.0 => spring-cloud-gateway-webmvc-4.3}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java (100%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webmvc-5.0 => spring-cloud-gateway-webmvc-4.3}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java (95%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webmvc-5.0 => spring-cloud-gateway-webmvc-4.3}/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java (100%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java => spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java} (93%) rename instrumentation/spring/spring-cloud-gateway/{spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java => spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcTestApplication.java} (97%) diff --git a/.fossa.yml b/.fossa.yml index 7422e7ce8718..97aee67024cb 100644 --- a/.fossa.yml +++ b/.fossa.yml @@ -1138,6 +1138,12 @@ targets: - type: gradle path: ./ target: ':instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.0:javaagent' + - type: gradle + path: ./ + target: ':instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:javaagent' + - type: gradle + path: ./ + target: ':instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-4.3:javaagent' - type: gradle path: ./ target: ':instrumentation:spring:spring-data:spring-data-1.8:javaagent' diff --git a/.github/scripts/check-javaagent-suppression-keys.sh b/.github/scripts/check-javaagent-suppression-keys.sh index 23528e194983..5dce5f2dbdfa 100755 --- a/.github/scripts/check-javaagent-suppression-keys.sh +++ b/.github/scripts/check-javaagent-suppression-keys.sh @@ -33,6 +33,10 @@ for file in $(find instrumentation -name "*Module.java"); do # TODO module is missing a base version continue fi + if [[ "$simple_module_name" == spring-cloud-gateway-webmvc ]]; then + # webmvc variant uses spring-cloud-gateway as base name + simple_module_name="spring-cloud-gateway" + fi if [ "$module_name" == "$simple_module_name" ]; then expected="super\(\n? *\"$simple_module_name\"" diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts index a2920ece822f..cd64c197c41f 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/build.gradle.kts @@ -11,12 +11,12 @@ muzzle { assertInverse.set(true) } - // Spring Cloud Gateway 5.0+ split into separate artifacts - // see spring-cloud-starter-gateway-server-webmvc-5.0 for mvc + // Spring Cloud Gateway 4.3.0+ split into separate artifacts + // see spring-cloud-starter-gateway-server-webmvc-4.3 for mvc pass { group.set("org.springframework.cloud") module.set("spring-cloud-starter-gateway-server-webflux") - versions.set("[5.0.0,]") + versions.set("[4.3.0,]") assertInverse.set(true) } } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts index 7e61560c0ac6..24b5812b74bb 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/build.gradle.kts @@ -14,7 +14,7 @@ dependencies { testLibrary("org.springframework.cloud:spring-cloud-starter-gateway:2.2.0.RELEASE") testLibrary("org.springframework.boot:spring-boot-starter-test:2.2.0.RELEASE") - latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // see spring-cloud-gateway-5.0* module + latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:3.+") // see spring-cloud-gateway-4.3* module } val latestDepTest = findProperty("testLatestDeps") as Boolean diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/build.gradle.kts similarity index 96% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/build.gradle.kts rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/build.gradle.kts index d562c0c1d964..4c2cd5a6aa21 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { testImplementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing")) + // classes in test setup require 5.0+ testLibrary("org.springframework.cloud:spring-cloud-starter-gateway-server-webflux:5.0.0") testLibrary("org.springframework.boot:spring-boot-starter-test:4.0.0") } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java similarity index 71% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java index e3549011f5f0..f971581ccadd 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50RouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.gateway.v5_0; +package io.opentelemetry.instrumentation.spring.gateway.v4_3; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; import org.junit.jupiter.api.Test; @@ -11,8 +11,8 @@ @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = {Gateway50TestApplication.class}) -class Gateway50RouteMappingTest extends AbstractRouteMappingTest { + classes = {Gateway43TestApplication.class}) +class Gateway43RouteMappingTest extends AbstractRouteMappingTest { @Test void gatewayRouteMappingTest() { diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43TestApplication.java similarity index 68% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43TestApplication.java index f72f732e9c54..b8a246b80acf 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0/Gateway50TestApplication.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43TestApplication.java @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.gateway.v5_0; +package io.opentelemetry.instrumentation.spring.gateway.v4_3; import io.opentelemetry.instrumentation.spring.gateway.common.GatewayTestApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class Gateway50TestApplication extends GatewayTestApplication {} +public class Gateway43TestApplication extends GatewayTestApplication {} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/resources/application.yml b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/resources/application.yml similarity index 100% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-5.0/testing/src/test/resources/application.yml rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/resources/application.yml diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/build.gradle.kts similarity index 96% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/build.gradle.kts index c3ecb5e9ab6c..8bcd88c894a9 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/build.gradle.kts @@ -6,13 +6,13 @@ muzzle { pass { group.set("org.springframework.cloud") module.set("spring-cloud-starter-gateway-server-webmvc") - versions.set("[5.0.0,)") + versions.set("[4.3.0,)") assertInverse.set(true) } } dependencies { - library("org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc:5.0.0") + library("org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc:4.3.0") implementation(project(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:javaagent")) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java similarity index 100% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java similarity index 95% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java index 2a7f4100f11a..7e47f1194b6f 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayWebMvcInstrumentationModule.java @@ -20,7 +20,7 @@ public class GatewayWebMvcInstrumentationModule extends InstrumentationModule implements ExperimentalInstrumentationModule { public GatewayWebMvcInstrumentationModule() { - super("spring-cloud-gateway", "spring-cloud-gateway-webmvc-5.0"); + super("spring-cloud-gateway", "spring-cloud-gateway-webmvc-4.3"); } @Override diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java similarity index 100% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java similarity index 93% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java index 36c3d1fb46f2..6247e0e8c8c2 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java @@ -17,8 +17,8 @@ @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - classes = {Gateway50MvcTestApplication.class}) -class Gateway50MvcRouteMappingTest extends AbstractRouteMappingTest { + classes = {Gateway43MvcTestApplication.class}) +class Gateway43MvcRouteMappingTest extends AbstractRouteMappingTest { @Test void gatewayRouteMappingTest() { diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcTestApplication.java similarity index 97% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcTestApplication.java index 21797f7c708f..7f3f40e9928c 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-5.0/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway50MvcTestApplication.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcTestApplication.java @@ -18,7 +18,7 @@ import org.springframework.web.servlet.function.ServerResponse; @SpringBootApplication -public class Gateway50MvcTestApplication { +public class Gateway43MvcTestApplication { @Bean public RouterFunction gatewayRouterFunction() { HandlerFunction echoHandler = diff --git a/settings.gradle.kts b/settings.gradle.kts index 211b81fa9a06..8cbdb5fd2265 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -618,8 +618,8 @@ include(":instrumentation:spring:spring-boot-resources:javaagent-unit-tests") include(":instrumentation:spring:spring-cloud-aws-3.0:javaagent") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.0:javaagent") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-2.2:testing") -include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webflux-5.0:testing") -include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-5.0:javaagent") +include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webflux-4.3:testing") +include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-webmvc-4.3:javaagent") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:javaagent") include(":instrumentation:spring:spring-cloud-gateway:spring-cloud-gateway-common:testing") include(":instrumentation:spring:spring-core-2.0:javaagent") From d9d24bd2db62926bc68cbdab8cc39396b9afafe2 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 4 Dec 2025 15:29:17 -0500 Subject: [PATCH 5/8] fix package --- .../gateway/webmvc/v5_0}/Gateway43MvcRouteMappingTest.java | 2 +- .../gateway/webmvc/v5_0}/Gateway43MvcTestApplication.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/{instrumentation/spring/gateway/v5_0_mvc => javaagent/instrumentation/spring/gateway/webmvc/v5_0}/Gateway43MvcRouteMappingTest.java (95%) rename instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/{instrumentation/spring/gateway/v5_0_mvc => javaagent/instrumentation/spring/gateway/webmvc/v5_0}/Gateway43MvcTestApplication.java (94%) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java similarity index 95% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java index 6247e0e8c8c2..7908cea0354e 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.gateway.v5_0_mvc; +package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcTestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcTestApplication.java similarity index 94% rename from instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcTestApplication.java rename to instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcTestApplication.java index 7f3f40e9928c..0b3e4fd071b8 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v5_0_mvc/Gateway43MvcTestApplication.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcTestApplication.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.spring.gateway.v5_0_mvc; +package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.uri; import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; From 1cfd3b9b95b661353e682deca372ece5605a6cf3 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 5 Dec 2025 13:33:04 -0500 Subject: [PATCH 6/8] code review updates --- .../v2_2/Gateway22RouteMappingTest.java | 9 +---- .../javaagent/build.gradle.kts | 5 --- .../common/AbstractRouteMappingTest.java | 2 ++ .../v4_3/Gateway43RouteMappingTest.java | 9 +---- ...legatingRouterFunctionInstrumentation.java | 7 ---- .../webmvc/v5_0/ServerRequestHelper.java | 35 ++++++++++++++----- .../v5_0/Gateway43MvcRouteMappingTest.java | 7 ---- 7 files changed, 30 insertions(+), 44 deletions(-) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java index 0055db65509a..c79bf41698bc 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v2_2/Gateway22RouteMappingTest.java @@ -6,16 +6,9 @@ package io.opentelemetry.instrumentation.spring.gateway.v2_2; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Gateway22TestApplication.class}) -class Gateway22RouteMappingTest extends AbstractRouteMappingTest { - - @Test - void gatewayRouteMappingTest() { - testGatewayRouteMapping(); - } -} +class Gateway22RouteMappingTest extends AbstractRouteMappingTest {} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts index 7aaf0ba68c7d..0b6bd5f67942 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/javaagent/build.gradle.kts @@ -1,8 +1,3 @@ plugins { id("otel.javaagent-instrumentation") } - -dependencies { - compileOnly("io.opentelemetry:opentelemetry-api") - compileOnly("io.opentelemetry:opentelemetry-api-incubator") -} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java index e2aa5c621d9b..1136b1738197 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java @@ -20,6 +20,7 @@ import java.util.List; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; @@ -53,6 +54,7 @@ void beforeEach() { client = WebClient.builder("h1c://localhost:" + port).followRedirects().build(); } + @Test protected void testGatewayRouteMapping() { String requestBody = "gateway"; AggregatedHttpResponse response = client.post("/gateway/echo", requestBody).aggregate().join(); diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java index f971581ccadd..c0038646aa44 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/java/io/opentelemetry/instrumentation/spring/gateway/v4_3/Gateway43RouteMappingTest.java @@ -6,16 +6,9 @@ package io.opentelemetry.instrumentation.spring.gateway.v4_3; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Gateway43TestApplication.class}) -class Gateway43RouteMappingTest extends AbstractRouteMappingTest { - - @Test - void gatewayRouteMappingTest() { - testGatewayRouteMapping(); - } -} +class Gateway43RouteMappingTest extends AbstractRouteMappingTest {} diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java index d6df535d04a8..f647e468eed9 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/GatewayDelegatingRouterFunctionInstrumentation.java @@ -5,7 +5,6 @@ package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; -import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; @@ -22,12 +21,6 @@ public class GatewayDelegatingRouterFunctionInstrumentation implements TypeInstrumentation { - @Override - public ElementMatcher classLoaderOptimization() { - return hasClassesNamed( - "org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction"); - } - @Override public ElementMatcher typeMatcher() { return named( diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java index c74d2ee32d2f..0e3afda96547 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java @@ -13,9 +13,8 @@ import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; import io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper; import java.lang.reflect.Field; -import java.net.URI; -import java.util.Optional; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; +import org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction; import org.springframework.web.servlet.function.ServerRequest; /** @@ -23,12 +22,23 @@ * ServerRequest and adding it to spans. */ public final class ServerRequestHelper { + private static final Field routeIdField; - private ServerRequestHelper() {} + static { + Field routeIdField1 = null; + try { + routeIdField1 = GatewayDelegatingRouterFunction.class.getDeclaredField("routeId"); + routeIdField1.setAccessible(true); + } catch (NoSuchFieldException ignored) { + // Ignored + } + + routeIdField = routeIdField1; + } public static void extractAttributes( Object gatewayRouterFunction, ServerRequest request, Context context) { - if (!GatewayRouteHelper.shouldCaptureExperimentalSpanAttributes()) { + if (routeIdField == null || !GatewayRouteHelper.shouldCaptureExperimentalSpanAttributes()) { return; } @@ -37,9 +47,12 @@ public static void extractAttributes( return; } + setRouteIdAttribute(gatewayRouterFunction, serverSpan); + setRouteUriAttribute(request, serverSpan); + } + + private static void setRouteIdAttribute(Object gatewayRouterFunction, Span serverSpan) { try { - Field routeIdField = gatewayRouterFunction.getClass().getDeclaredField("routeId"); - routeIdField.setAccessible(true); String routeId = (String) routeIdField.get(gatewayRouterFunction); String convergedRouteId = GatewayRouteHelper.convergeRouteId(routeId); if (convergedRouteId != null) { @@ -48,9 +61,13 @@ public static void extractAttributes( } catch (Exception e) { // Silently ignore } + } - Optional requestUrlOpt = request.attribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR); - requestUrlOpt.ifPresent( - uri -> serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, ((URI) uri).toASCIIString())); + private static void setRouteUriAttribute(ServerRequest request, Span serverSpan) { + request + .attribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR) + .ifPresent(uri -> serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, uri.toString())); } + + private ServerRequestHelper() {} } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java index 7908cea0354e..6c9d62efe163 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java @@ -12,19 +12,12 @@ import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Gateway43MvcTestApplication.class}) class Gateway43MvcRouteMappingTest extends AbstractRouteMappingTest { - - @Test - void gatewayRouteMappingTest() { - testGatewayRouteMapping(); - } - @Override protected String getSpanName() { return "POST /gateway/echo"; From 5bf09b167cfd9d3c35109f5f3b1a1c18c9e55dd0 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 5 Dec 2025 14:54:24 -0500 Subject: [PATCH 7/8] fix tests --- .../gateway/v2_0/GatewayRouteMappingTest.java | 66 +++++-------------- .../src/test/resources/application.yml | 11 ++++ .../common/AbstractRouteMappingTest.java | 49 ++++++++++++++ .../src/test/resources/application.yml | 13 +++- .../v5_0/Gateway43MvcRouteMappingTest.java | 29 +++++++- .../v5_0/Gateway43MvcTestApplication.java | 20 ++++-- 6 files changed, 130 insertions(+), 58 deletions(-) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java index 0a2100ec8a3f..26f49d36ab37 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java @@ -5,12 +5,9 @@ package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; -import org.junit.jupiter.api.Test; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import java.util.List; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -21,54 +18,23 @@ classes = {GatewayTestApplication.class}) class GatewayRouteMappingTest extends AbstractRouteMappingTest { - @Test - void gatewayRouteMappingTest() { - String requestBody = "gateway"; - AggregatedHttpResponse response = client.post("/gateway/echo", requestBody).aggregate().join(); - assertThat(response.status().code()).isEqualTo(200); - assertThat(response.contentUtf8()).isEqualTo(requestBody); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("POST path_route") - .hasKind(SpanKind.SERVER) - .hasAttributesSatisfying( - buildAttributeAssertions("path_route", "h1c://mock.response", 0, 1)), - span -> span.hasName(WEBFLUX_SPAN_NAME).hasKind(SpanKind.INTERNAL))); + @Override + protected String getSpanName() { + return "POST path_route"; + } + + @Override + protected List getExpectedAttributes() { + return buildAttributeAssertions("path_route", "h1c://mock.response", 0, 1); } - @Test - void gatewayRandomUuidRouteMappingTest() { - String requestBody = "gateway"; - AggregatedHttpResponse response = client.post("/uuid/echo", requestBody).aggregate().join(); - assertThat(response.status().code()).isEqualTo(200); - assertThat(response.contentUtf8()).isEqualTo(requestBody); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("POST") - .hasKind(SpanKind.SERVER) - .hasAttributesSatisfying(buildAttributeAssertions("h1c://mock.uuid", 0, 1)), - span -> span.hasName(WEBFLUX_SPAN_NAME).hasKind(SpanKind.INTERNAL))); + @Override + protected List getRandomUuidExpectedAttributes() { + return buildAttributeAssertions("h1c://mock.uuid", 0, 1); } - @Test - void gatewayFakeUuidRouteMappingTest() { - String requestBody = "gateway"; - String routeId = "ffffffff-ffff-ffff-ffff-ffff"; - AggregatedHttpResponse response = client.post("/fake/echo", requestBody).aggregate().join(); - assertThat(response.status().code()).isEqualTo(200); - assertThat(response.contentUtf8()).isEqualTo(requestBody); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("POST " + routeId) - .hasKind(SpanKind.SERVER) - .hasAttributesSatisfying( - buildAttributeAssertions(routeId, "h1c://mock.fake", 0, 1)), - span -> span.hasName(WEBFLUX_SPAN_NAME).hasKind(SpanKind.INTERNAL))); + @Override + protected List getFakeUuidExpectedAttributes(String routeId) { + return buildAttributeAssertions(routeId, "h1c://mock.fake", 0, 1); } } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/resources/application.yml b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/resources/application.yml index 495bde2f5c16..9b5287472180 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/resources/application.yml +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.2/testing/src/test/resources/application.yml @@ -6,3 +6,14 @@ spring: predicates: - Path=/gateway/echo order: 2023 + # Route without ID - will get random UUID (should be filtered out) + - uri: h1c://mock.uuid + predicates: + - Path=/uuid/echo + order: 0 + # Route with fake UUID ID (should NOT be filtered out) + - id: ffffffff-ffff-ffff-ffff-ffff + uri: h1c://mock.fake + predicates: + - Path=/fake/echo + order: 0 diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java index 1136b1738197..21ecc541e2dd 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-common/testing/src/main/java/io/opentelemetry/instrumentation/spring/gateway/common/AbstractRouteMappingTest.java @@ -70,6 +70,55 @@ protected void testGatewayRouteMapping() { span -> span.hasName(getInternalSpanName()).hasKind(SpanKind.INTERNAL))); } + @Test + protected void testRandomUuidRouteFiltering() { + String requestBody = "gateway"; + AggregatedHttpResponse response = client.post("/uuid/echo", requestBody).aggregate().join(); + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8()).isEqualTo(requestBody); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(getRandomUuidSpanName()) + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfying(getRandomUuidExpectedAttributes()), + span -> span.hasName(getInternalSpanName()).hasKind(SpanKind.INTERNAL))); + } + + @Test + protected void testFakeUuidRouteNotFiltered() { + String requestBody = "gateway"; + String routeId = "ffffffff-ffff-ffff-ffff-ffff"; + AggregatedHttpResponse response = client.post("/fake/echo", requestBody).aggregate().join(); + assertThat(response.status().code()).isEqualTo(200); + assertThat(response.contentUtf8()).isEqualTo(requestBody); + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(getFakeUuidSpanName(routeId)) + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfying(getFakeUuidExpectedAttributes(routeId)), + span -> span.hasName(getInternalSpanName()).hasKind(SpanKind.INTERNAL))); + } + + protected String getRandomUuidSpanName() { + return "POST"; + } + + protected List getRandomUuidExpectedAttributes() { + return buildAttributeAssertions("h1c://mock.uuid", 0, 0); + } + + protected String getFakeUuidSpanName(String routeId) { + return "POST " + routeId; + } + + protected List getFakeUuidExpectedAttributes(String routeId) { + return buildAttributeAssertions(routeId, "h1c://mock.fake", 0, 0); + } + protected List buildAttributeAssertions( @Nullable String routeId, String uri, int order, int filterSize) { List assertions = new ArrayList<>(); diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/resources/application.yml b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/resources/application.yml index 141a92f9cc8a..df348f4b5bc7 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/resources/application.yml +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webflux-4.3/testing/src/test/resources/application.yml @@ -7,4 +7,15 @@ spring: - uri: h1c://mock.response predicates: - Path=/gateway/echo - order: 2023 \ No newline at end of file + order: 2023 + # Route without ID - will get random UUID (should be filtered out) + - uri: h1c://mock.uuid + predicates: + - Path=/uuid/echo + order: 0 + # Route with fake UUID ID (should NOT be filtered out) + - id: ffffffff-ffff-ffff-ffff-ffff + uri: h1c://mock.fake + predicates: + - Path=/fake/echo + order: 0 \ No newline at end of file diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java index 6c9d62efe163..54ddc7eb7d2e 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcRouteMappingTest.java @@ -5,9 +5,9 @@ package io.opentelemetry.javaagent.instrumentation.spring.gateway.webmvc.v5_0; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.instrumentation.spring.gateway.common.AbstractRouteMappingTest; import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; import java.util.ArrayList; @@ -37,8 +37,31 @@ protected String getInternalSpanName() { @Override protected List getExpectedAttributes() { List assertions = new ArrayList<>(); - assertions.add( - equalTo(AttributeKey.stringKey("spring-cloud-gateway.route.id"), "test-route-id")); + assertions.add(equalTo(stringKey("spring-cloud-gateway.route.id"), "test-route-id")); + return assertions; + } + + @Override + protected String getRandomUuidSpanName() { + // WebMVC uses HTTP route in span name, not gateway route ID + return "POST /uuid/echo"; + } + + @Override + protected List getRandomUuidExpectedAttributes() { + return new ArrayList<>(); + } + + @Override + protected String getFakeUuidSpanName(String routeId) { + // WebMVC uses HTTP route in span name, not gateway route ID + return "POST /fake/echo"; + } + + @Override + protected List getFakeUuidExpectedAttributes(String routeId) { + List assertions = new ArrayList<>(); + assertions.add(equalTo(stringKey("spring-cloud-gateway.route.id"), routeId)); return assertions; } } diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcTestApplication.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcTestApplication.java index 0b3e4fd071b8..e13344f88299 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcTestApplication.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/Gateway43MvcTestApplication.java @@ -33,9 +33,21 @@ public RouterFunction gatewayRouterFunction() { } }; - return route("test-route-id") - .POST("/gateway/echo", echoHandler) - .before(uri("http://mock.response")) - .build(); + RouterFunction mainRoute = + route("test-route-id") + .POST("/gateway/echo", echoHandler) + .before(uri("http://mock.response")) + .build(); + + RouterFunction uuidRoute = + route().POST("/uuid/echo", echoHandler).before(uri("http://mock.uuid")).build(); + + RouterFunction fakeUuidRoute = + route("ffffffff-ffff-ffff-ffff-ffff") + .POST("/fake/echo", echoHandler) + .before(uri("http://mock.fake")) + .build(); + + return mainRoute.and(uuidRoute).and(fakeUuidRoute); } } From 0c0e37f0462db3a364bfdd16fe197f8e5577a9c8 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 5 Dec 2025 15:46:46 -0500 Subject: [PATCH 8/8] add nullaway to new module --- .../javaagent/build.gradle.kts | 1 + .../gateway/webmvc/v5_0/ServerRequestHelper.java | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/build.gradle.kts b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/build.gradle.kts index 8bcd88c894a9..45ca0524c4cd 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/build.gradle.kts +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("otel.javaagent-instrumentation") + id("otel.nullaway-conventions") } muzzle { diff --git a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java index 0e3afda96547..2d53091ded72 100644 --- a/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java +++ b/instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-webmvc-4.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/webmvc/v5_0/ServerRequestHelper.java @@ -13,6 +13,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan; import io.opentelemetry.javaagent.instrumentation.spring.gateway.common.GatewayRouteHelper; import java.lang.reflect.Field; +import javax.annotation.Nullable; import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; import org.springframework.cloud.gateway.server.mvc.handler.GatewayDelegatingRouterFunction; import org.springframework.web.servlet.function.ServerRequest; @@ -22,7 +23,7 @@ * ServerRequest and adding it to spans. */ public final class ServerRequestHelper { - private static final Field routeIdField; + @Nullable private static final Field routeIdField; static { Field routeIdField1 = null; @@ -47,11 +48,6 @@ public static void extractAttributes( return; } - setRouteIdAttribute(gatewayRouterFunction, serverSpan); - setRouteUriAttribute(request, serverSpan); - } - - private static void setRouteIdAttribute(Object gatewayRouterFunction, Span serverSpan) { try { String routeId = (String) routeIdField.get(gatewayRouterFunction); String convergedRouteId = GatewayRouteHelper.convergeRouteId(routeId); @@ -61,9 +57,7 @@ private static void setRouteIdAttribute(Object gatewayRouterFunction, Span serve } catch (Exception e) { // Silently ignore } - } - private static void setRouteUriAttribute(ServerRequest request, Span serverSpan) { request .attribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR) .ifPresent(uri -> serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, uri.toString()));