-
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.
feat: get route info in spring-cloud-gateway
- Loading branch information
1 parent
d0523fd
commit bbd5d11
Showing
9 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
42 changes: 42 additions & 0 deletions
42
instrumentation/spring/spring-cloud-gateway/javaagent/build.gradle.kts
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,42 @@ | ||
plugins { | ||
id("otel.javaagent-instrumentation") | ||
} | ||
|
||
dependencies { | ||
compileOnly("org.springframework.cloud:spring-cloud-starter-gateway:2.0.0.RELEASE") | ||
|
||
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")) | ||
|
||
testLibrary("org.springframework.cloud:spring-cloud-starter-gateway:2.0.0.RELEASE") | ||
testLibrary("org.springframework.boot:spring-boot-starter-test:2.0.0.RELEASE") | ||
} | ||
|
||
tasks.withType<Test>().configureEach { | ||
// TODO run tests both with and without experimental span attributes | ||
jvmArgs("-Dotel.instrumentation.spring-webflux.experimental-span-attributes=true") | ||
// required on jdk17 | ||
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") | ||
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") | ||
|
||
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) | ||
} | ||
|
||
val latestDepTest = findProperty("testLatestDeps") as Boolean | ||
|
||
if (latestDepTest) { | ||
// spring 6 requires java 17 | ||
otelJava { | ||
minJavaVersionSupported.set(JavaVersion.VERSION_17) | ||
} | ||
} else { | ||
// spring 5 requires old logback (and therefore also old slf4j) | ||
configurations.testRuntimeClasspath { | ||
resolutionStrategy { | ||
force("ch.qos.logback:logback-classic:1.2.11") | ||
force("org.slf4j:slf4j-api:1.7.36") | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
.../opentelemetry/javaagent/instrumentation/spring/gateway/GatewayInstrumentationModule.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,32 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.spring.gateway; | ||
|
||
import static java.util.Arrays.asList; | ||
|
||
import com.google.auto.service.AutoService; | ||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; | ||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; | ||
import java.util.List; | ||
|
||
@AutoService(InstrumentationModule.class) | ||
public class GatewayInstrumentationModule extends InstrumentationModule { | ||
|
||
public GatewayInstrumentationModule() { | ||
super("spring-cloud-gateway"); | ||
} | ||
|
||
@Override | ||
public List<TypeInstrumentation> typeInstrumentations() { | ||
return asList(new HandlerAdapterInstrumentation()); | ||
} | ||
|
||
@Override | ||
public int order() { | ||
// Later than Spring Webflux. | ||
return 1; | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...ain/java/io/opentelemetry/javaagent/instrumentation/spring/gateway/GatewaySingletons.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,20 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.spring.gateway; | ||
|
||
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteGetter; | ||
import org.springframework.web.server.ServerWebExchange; | ||
|
||
public final class GatewaySingletons { | ||
|
||
private GatewaySingletons() { | ||
|
||
} | ||
|
||
public static HttpServerRouteGetter<ServerWebExchange> httpRouteGetter() { | ||
return (context, exchange) -> ServerWebExchangeHelper.extractServerRoute(exchange); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
...opentelemetry/javaagent/instrumentation/spring/gateway/HandlerAdapterInstrumentation.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,68 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.spring.gateway; | ||
|
||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; | ||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; | ||
import static net.bytebuddy.matcher.ElementMatchers.isAbstract; | ||
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.not; | ||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument; | ||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments; | ||
|
||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; | ||
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteSource; | ||
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.server.ServerWebExchange; | ||
|
||
public class HandlerAdapterInstrumentation implements TypeInstrumentation { | ||
|
||
@Override | ||
public ElementMatcher<ClassLoader> classLoaderOptimization() { | ||
return hasClassesNamed("org.springframework.web.reactive.HandlerAdapter"); | ||
} | ||
|
||
@Override | ||
public ElementMatcher<TypeDescription> typeMatcher() { | ||
return not(isAbstract()) | ||
.and(implementsInterface(named("org.springframework.web.reactive.HandlerAdapter"))); | ||
} | ||
|
||
@Override | ||
public void transform(TypeTransformer transformer) { | ||
transformer.applyAdviceToMethod( | ||
isMethod() | ||
.and(isPublic()) | ||
.and(named("handle")) | ||
.and(takesArgument(0, named("org.springframework.web.server.ServerWebExchange"))) | ||
.and(takesArgument(1, Object.class)) | ||
.and(takesArguments(2)), | ||
this.getClass().getName() + "$HandleAdvice"); | ||
} | ||
|
||
@SuppressWarnings("unused") | ||
public static class HandleAdvice { | ||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static void methodEnter( | ||
@Advice.Argument(0) ServerWebExchange exchange) { | ||
Context context = Context.current(); | ||
// Update route info for server span. | ||
HttpServerRoute.update( | ||
context, HttpServerRouteSource.NESTED_CONTROLLER, GatewaySingletons.httpRouteGetter(), | ||
exchange); | ||
// Record route info in current span, should be webflux's span. | ||
ServerWebExchangeHelper.extractAttributes(exchange, context); | ||
|
||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
...va/io/opentelemetry/javaagent/instrumentation/spring/gateway/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,46 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.spring.gateway; | ||
|
||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; | ||
|
||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.context.Context; | ||
import org.springframework.cloud.gateway.route.Route; | ||
import org.springframework.web.server.ServerWebExchange; | ||
|
||
public final class ServerWebExchangeHelper { | ||
|
||
/** | ||
* Route info key. | ||
*/ | ||
public static final String ROUTE_INFO_ATTRIBUTES = "ROUTE_INFO"; | ||
|
||
|
||
private ServerWebExchangeHelper() { | ||
|
||
} | ||
|
||
public static void extractAttributes(ServerWebExchange exchange, Context context) { | ||
// Record route info | ||
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); | ||
if (route != null) { | ||
Span currentSpan = Span.fromContext(context); | ||
assert currentSpan != null; | ||
currentSpan.setAttribute(ROUTE_INFO_ATTRIBUTES, route.toString()); | ||
} | ||
} | ||
|
||
|
||
public static String extractServerRoute(ServerWebExchange exchange) { | ||
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); | ||
if (route != null) { | ||
return "Route@" + route.getId(); | ||
} | ||
return null; | ||
} | ||
|
||
} |
76 changes: 76 additions & 0 deletions
76
...va/io/opentelemetry/javaagent/instrumentation/spring/gateway/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,76 @@ | ||
package io.opentelemetry.javaagent.instrumentation.spring.gateway;/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
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; | ||
|
||
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; | ||
|
||
@ExtendWith(SpringExtension.class) | ||
@SpringBootTest( | ||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, | ||
classes = { | ||
GatewayTestApplication.class, | ||
GatewayRouteMappingTest.ForceNettyAutoConfiguration.class | ||
}) | ||
public class GatewayRouteMappingTest { | ||
|
||
@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"; | ||
String expectRoute = "Route@path_route"; | ||
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, expectRoute)), | ||
span -> span.hasAttributesSatisfying( | ||
satisfies(AttributeKey.stringKey(ServerWebExchangeHelper.ROUTE_INFO_ATTRIBUTES), | ||
s -> s.contains("id='path_route'")), | ||
satisfies(AttributeKey.stringKey(ServerWebExchangeHelper.ROUTE_INFO_ATTRIBUTES), | ||
s -> s.contains("uri=h1c://mock.response") | ||
) | ||
))); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
...ava/io/opentelemetry/javaagent/instrumentation/spring/gateway/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,24 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.javaagent.instrumentation.spring.gateway; | ||
|
||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.cloud.gateway.route.RouteLocator; | ||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; | ||
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(f -> f.filter( | ||
(exchange, chain) -> exchange.getResponse().writeWith(exchange.getRequest().getBody()))).uri("h1c://mock.response")) | ||
.build(); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
instrumentation/spring/spring-cloud-gateway/javaagent/src/test/resources/logback.xml
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,20 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<configuration> | ||
|
||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender"> | ||
<layout class="ch.qos.logback.classic.PatternLayout"> | ||
<Pattern> | ||
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n | ||
</Pattern> | ||
</layout> | ||
</appender> | ||
|
||
<root level="WARN"> | ||
<appender-ref ref="console"/> | ||
</root> | ||
|
||
<!-- this is needed because when Spring Boot starts it overrides the debug log level that was | ||
configured in AgentTestRunner --> | ||
<logger name="io.opentelemetry" level="debug"/> | ||
|
||
</configuration> |
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