Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 4 additions & 0 deletions .github/scripts/check-javaagent-suppression-keys.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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\""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@ 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 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("[4.3.0,]")
assertInverse.set(true)
}
}

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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,7 +23,7 @@ public GatewayInstrumentationModule() {

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new HandlerAdapterInstrumentation());
return singletonList(new HandlerAdapterInstrumentation());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> ROUTE_ID_ATTRIBUTE =
AttributeKey.stringKey("spring-cloud-gateway.route.id");

/** Route URI attribute key. */
private static final AttributeKey<String> ROUTE_URI_ATTRIBUTE =
AttributeKey.stringKey("spring-cloud-gateway.route.uri");

/** Route order attribute key. */
private static final AttributeKey<Long> ROUTE_ORDER_ATTRIBUTE =
AttributeKey.longKey("spring-cloud-gateway.route.order");

/** Route filter size attribute key. */
private static final AttributeKey<Long> 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;
Expand All @@ -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 <a
* href="https://github.com/spring-cloud/spring-cloud-gateway/commit/5002fe2e0a2825ef47dd667cade37b844c276cf6"/>
*/
@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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-starter-test:3.+") // see spring-cloud-gateway-4.3* module
}

val latestDepTest = findProperty("testLatestDeps") as Boolean

tasks.withType<Test>().configureEach {
jvmArgs("-Dotel.instrumentation.spring-cloud-gateway.experimental-span-attributes=true")

Expand All @@ -27,11 +28,9 @@ tasks.withType<Test>().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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,17 @@

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,
Gateway22RouteMappingTest.ForceNettyAutoConfiguration.class
})
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)));
testGatewayRouteMapping();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
id("otel.javaagent-instrumentation")
}

dependencies {
compileOnly("io.opentelemetry:opentelemetry-api")
compileOnly("io.opentelemetry:opentelemetry-api-incubator")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aren't these already available through otel.javaagent-instrumentation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes thank you

}
Original file line number Diff line number Diff line change
@@ -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<String> ROUTE_ID_ATTRIBUTE =
AttributeKey.stringKey("spring-cloud-gateway.route.id");

/** Route URI attribute key. */
public static final AttributeKey<String> ROUTE_URI_ATTRIBUTE =
AttributeKey.stringKey("spring-cloud-gateway.route.uri");

/** Route order attribute key. */
public static final AttributeKey<Long> ROUTE_ORDER_ATTRIBUTE =
AttributeKey.longKey("spring-cloud-gateway.route.order");

/** Route filter size attribute key. */
public static final AttributeKey<Long> 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 <a
* href="https://github.com/spring-cloud/spring-cloud-gateway/commit/5002fe2e0a2825ef47dd667cade37b844c276cf6">Spring
* Cloud Gateway commit</a>
*/
@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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Loading
Loading