-
Notifications
You must be signed in to change notification settings - Fork 873
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
95972e9
commit 982569c
Showing
16 changed files
with
347 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Settings for the Spring Cloud Gateway instrumentation | ||
|
||
| System property | Type | Default | Description | | ||
|--------------------------------------------------------------------------| ------- | ------- |---------------------------------------------------------------------------------------------| | ||
| `otel.instrumentation.spring-cloud-gateway.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
.../opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/ServerWebExchangeHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0; | ||
|
||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; | ||
|
||
import io.opentelemetry.api.common.AttributeKey; | ||
import io.opentelemetry.api.internal.StringUtils; | ||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig; | ||
import java.util.regex.Pattern; | ||
import org.springframework.cloud.gateway.route.Route; | ||
import org.springframework.web.server.ServerWebExchange; | ||
|
||
public final class ServerWebExchangeHelper { | ||
|
||
/** Route info key. */ | ||
private static final AttributeKey<String> ROUTE_INFO_ATTRIBUTES = | ||
AttributeKey.stringKey("ROUTE_INFO"); | ||
|
||
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES; | ||
|
||
static { | ||
CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES = | ||
InstrumentationConfig.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}$"); | ||
|
||
/* Route ID for route whose routeID is unset */ | ||
private static final String UNSET_ROUTE_ID = "UNSET_ROUTE_ID"; | ||
|
||
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) { | ||
Span currentSpan = Span.fromContext(context); | ||
if (currentSpan == null) { | ||
return; | ||
} | ||
currentSpan.setAttribute(ROUTE_INFO_ATTRIBUTES, summarizeRoute(route)); | ||
} | ||
} | ||
|
||
public static String extractServerRoute(ServerWebExchange exchange) { | ||
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); | ||
if (route != null) { | ||
return convergeRouteId(route); | ||
} | ||
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"/> | ||
*/ | ||
private static String convergeRouteId(Route route) { | ||
String routeId = route.getId(); | ||
if (StringUtils.isNullOrEmpty(routeId)) { | ||
return UNSET_ROUTE_ID; | ||
} | ||
if (UUID_REGEX.matcher(routeId).matches()) { | ||
return UNSET_ROUTE_ID; | ||
} | ||
if (routeId.startsWith(INVALID_RANDOM_ROUTE_ID)) { | ||
return UNSET_ROUTE_ID; | ||
} | ||
return routeId; | ||
} | ||
|
||
private static String summarizeRoute(Route route) { | ||
return "id='" | ||
+ route.getId() | ||
+ '\'' | ||
+ ", uri=" | ||
+ route.getUri() | ||
+ ", order=" | ||
+ route.getOrder() | ||
+ ", filterSize=" | ||
+ route.getFilters().size() | ||
+ '}'; | ||
} | ||
} |
39 changes: 0 additions & 39 deletions
39
.../java/io/opentelemetry/javaagent/instrumentation/spring/v2_0/ServerWebExchangeHelper.java
This file was deleted.
Oops, something went wrong.
112 changes: 112 additions & 0 deletions
112
...st/java/io/opentelemetry/instrumentation/spring/gateway/v2_0/GatewayRouteMappingTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.spring.gateway.v2_0; | ||
|
||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; | ||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import io.opentelemetry.api.common.AttributeKey; | ||
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; | ||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; | ||
import io.opentelemetry.semconv.SemanticAttributes; | ||
import io.opentelemetry.testing.internal.armeria.client.WebClient; | ||
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.boot.test.context.TestConfiguration; | ||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.test.context.junit.jupiter.SpringExtension; | ||
|
||
@ExtendWith(SpringExtension.class) | ||
@SpringBootTest( | ||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, | ||
classes = { | ||
GatewayTestApplication.class, | ||
GatewayRouteMappingTest.ForceNettyAutoConfiguration.class | ||
}) | ||
class GatewayRouteMappingTest { | ||
|
||
private static final AttributeKey<String> ROUTE_INFO_ATTRIBUTES = | ||
AttributeKey.stringKey("ROUTE_INFO"); | ||
|
||
private static final String UNSET_ROUTE_ID = "UNSET_ROUTE_ID"; | ||
|
||
@TestConfiguration | ||
static class ForceNettyAutoConfiguration { | ||
@Bean | ||
NettyReactiveWebServerFactory nettyFactory() { | ||
return new NettyReactiveWebServerFactory(); | ||
} | ||
} | ||
|
||
@RegisterExtension | ||
static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); | ||
|
||
@Value("${local.server.port}") | ||
private int port; | ||
|
||
private WebClient client; | ||
|
||
@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.hasAttribute(equalTo(SemanticAttributes.HTTP_ROUTE, "path_route")), | ||
span -> | ||
span.hasAttributesSatisfying( | ||
satisfies(ROUTE_INFO_ATTRIBUTES, s -> s.contains("id='path_route'")), | ||
satisfies( | ||
ROUTE_INFO_ATTRIBUTES, s -> s.contains("uri=h1c://mock.response"))))); | ||
} | ||
|
||
@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.hasAttribute(equalTo(SemanticAttributes.HTTP_ROUTE, UNSET_ROUTE_ID)), | ||
span -> | ||
span.hasAttributesSatisfying( | ||
satisfies(ROUTE_INFO_ATTRIBUTES, s -> s.contains("uri=h1c://mock.uuid"))))); | ||
} | ||
|
||
@Test | ||
void gatewayFakeUUIDRouteMappingTest() { | ||
String requestBody = "gateway"; | ||
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.hasAttribute( | ||
equalTo(SemanticAttributes.HTTP_ROUTE, "ffffffff-ffff-ffff-ffff-ffff")), | ||
span -> | ||
span.hasAttributesSatisfying( | ||
satisfies(ROUTE_INFO_ATTRIBUTES, s -> s.contains("uri=h1c://mock.fake"))))); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
...est/java/io/opentelemetry/instrumentation/spring/gateway/v2_0/GatewayTestApplication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.spring.gateway.v2_0; | ||
|
||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.cloud.gateway.route.RouteLocator; | ||
import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; | ||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; | ||
import org.springframework.cloud.gateway.route.builder.UriSpec; | ||
import org.springframework.context.annotation.Bean; | ||
|
||
@SpringBootApplication | ||
public class GatewayTestApplication { | ||
|
||
@Bean | ||
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { | ||
// A simple echo gateway. | ||
return builder | ||
.routes() | ||
.route( | ||
"path_route", | ||
r -> | ||
r.path("/gateway/**") | ||
.filters(GatewayTestApplication::echoFunc) | ||
.uri("h1c://mock.response")) | ||
// The routeID should be a random UUID. | ||
.route( | ||
r -> | ||
r.path("/uuid/**").filters(GatewayTestApplication::echoFunc).uri("h1c://mock.uuid")) | ||
// Seems like an uuid but not. | ||
.route( | ||
"ffffffff-ffff-ffff-ffff-ffff", | ||
r -> | ||
r.path("/fake/**").filters(GatewayTestApplication::echoFunc).uri("h1c://mock.fake")) | ||
.build(); | ||
} | ||
|
||
private static UriSpec echoFunc(GatewayFilterSpec f) { | ||
return f.filter( | ||
(exchange, chain) -> exchange.getResponse().writeWith(exchange.getRequest().getBody())); | ||
} | ||
} |
35 changes: 0 additions & 35 deletions
35
...o/opentelemetry/javaagent/instrumentation/spring/gateway/v2_0/GatewayTestApplication.java
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.