fetch route in apache shenyu (#11260)
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com> Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
parent
d37c739365
commit
9e48f24486
|
@ -30,6 +30,7 @@ These are the supported libraries and frameworks:
|
|||
| [Apache Dubbo](https://github.com/apache/dubbo/) | 2.7+ | [opentelemetry-apache-dubbo-2.7](../instrumentation/apache-dubbo-2.7/library-autoconfigure) | [RPC Client Spans], [RPC Server Spans] |
|
||||
| [Apache HttpAsyncClient](https://hc.apache.org/index.html) | 4.1+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
|
||||
| [Apache HttpClient](https://hc.apache.org/index.html) | 2.0+ | [opentelemetry-apache-httpclient-4.3](../instrumentation/apache-httpclient/apache-httpclient-4.3/library),<br>[opentelemetry-apache-httpclient-5.2](../instrumentation/apache-httpclient/apache-httpclient-5.2/library) | [HTTP Client Spans], [HTTP Client Metrics] |
|
||||
| [Apache Shenyu](https://shenyu.apache.org/) | 2.4+ | N/A | Provides `http.route` [2] |
|
||||
| [Apache Kafka Producer/Consumer API](https://kafka.apache.org/documentation/#producerapi) | 0.11+ | [opentelemetry-kafka-clients-2.6](../instrumentation/kafka/kafka-clients/kafka-clients-2.6/library) | [Messaging Spans] |
|
||||
| [Apache Kafka Streams API](https://kafka.apache.org/documentation/streams/) | 0.11+ | N/A | [Messaging Spans] |
|
||||
| [Apache MyFaces](https://myfaces.apache.org/) | 1.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] |
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Settings for the Apache Shenyu instrumentation
|
||||
|
||||
| System property | Type | Default | Description |
|
||||
|---------------------------------------------------------------------| ------- | ------- |---------------------------------------------------------------------------------------------|
|
||||
| `otel.instrumentation.apache-shenyu.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. |
|
|
@ -0,0 +1,51 @@
|
|||
plugins {
|
||||
id("otel.javaagent-instrumentation")
|
||||
}
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group.set("org.apache.shenyu")
|
||||
module.set("shenyu-web")
|
||||
versions.set("[2.4.0,)")
|
||||
assertInverse.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.apache.shenyu:shenyu-web:2.4.0")
|
||||
compileOnly("com.google.auto.value:auto-value-annotations")
|
||||
annotationProcessor("com.google.auto.value:auto-value")
|
||||
|
||||
testLibrary("org.springframework.boot:spring-boot-starter-test:2.0.0.RELEASE")
|
||||
|
||||
// based on apache shenyu 2.4.0 official example
|
||||
testLibrary("org.apache.shenyu:shenyu-spring-boot-starter-gateway:2.4.0") {
|
||||
exclude("org.codehaus.groovy", "groovy")
|
||||
}
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-webflux:2.2.2.RELEASE") {
|
||||
exclude("org.codehaus.groovy", "groovy")
|
||||
}
|
||||
|
||||
// the latest version of apache shenyu uses spring-boot 2.7
|
||||
latestDepTestLibrary("org.springframework.boot:spring-boot-starter-test:2.7.+")
|
||||
|
||||
testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
jvmArgs("-Dotel.instrumentation.apache-shenyu.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)
|
||||
}
|
||||
|
||||
configurations.testRuntimeClasspath {
|
||||
resolutionStrategy {
|
||||
// requires old logback (and therefore also old slf4j)
|
||||
force("ch.qos.logback:logback-classic:1.2.11")
|
||||
force("org.slf4j:slf4j-api:1.7.36")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apacheshenyu.v2_4;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class ApacheShenYuInstrumentationModule extends InstrumentationModule {
|
||||
public ApacheShenYuInstrumentationModule() {
|
||||
super("apache-shenyu", "apache-shenyu-2.4");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return Collections.singletonList(new ContextBuilderInstrumentation());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apacheshenyu.v2_4;
|
||||
|
||||
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteGetter;
|
||||
import org.apache.shenyu.common.dto.MetaData;
|
||||
|
||||
public final class ApacheShenYuSingletons {
|
||||
|
||||
private ApacheShenYuSingletons() {}
|
||||
|
||||
public static HttpServerRouteGetter<MetaData> httpRouteGetter() {
|
||||
return (context, metaData) -> metaData.getPath();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apacheshenyu.v2_4;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
|
||||
import io.opentelemetry.instrumentation.api.semconv.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.apache.shenyu.common.constant.Constants;
|
||||
import org.apache.shenyu.common.dto.MetaData;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
public class ContextBuilderInstrumentation implements TypeInstrumentation {
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return named("org.apache.shenyu.plugin.global.DefaultShenyuContextBuilder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transform(TypeTransformer transformer) {
|
||||
transformer.applyAdviceToMethod(
|
||||
named("build")
|
||||
.and(takesArgument(0, named("org.springframework.web.server.ServerWebExchange")))
|
||||
.and(isPublic()),
|
||||
this.getClass().getName() + "$BuildAdvice");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class BuildAdvice {
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void onExit(@Advice.Argument(0) ServerWebExchange exchange) {
|
||||
Context context = Context.current();
|
||||
MetaData metaData = exchange.getAttribute(Constants.META_DATA);
|
||||
if (metaData == null) {
|
||||
return;
|
||||
}
|
||||
HttpServerRoute.update(
|
||||
context,
|
||||
HttpServerRouteSource.NESTED_CONTROLLER,
|
||||
ApacheShenYuSingletons.httpRouteGetter(),
|
||||
metaData);
|
||||
MetaDataHelper.extractAttributes(metaData, context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apacheshenyu.v2_4;
|
||||
|
||||
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.InstrumentationConfig;
|
||||
import org.apache.shenyu.common.dto.MetaData;
|
||||
|
||||
public final class MetaDataHelper {
|
||||
|
||||
/** ID for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> META_ID_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.id");
|
||||
|
||||
/** App name for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> APP_NAME_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.app-name");
|
||||
|
||||
/** Context path for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> CONTEXT_PATH_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.context-path");
|
||||
|
||||
/** Path for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> PATH_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.path");
|
||||
|
||||
/** Rpc type for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> RPC_TYPE_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.rpc-type");
|
||||
|
||||
/** Service name for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> SERVICE_NAME_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.service-name");
|
||||
|
||||
/** Method name for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> METHOD_NAME_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.method-name");
|
||||
|
||||
/** Parameter types for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> PARAMETER_TYPES_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.param-types");
|
||||
|
||||
/** Rpc extension for apache shenyu metadata * */
|
||||
private static final AttributeKey<String> RPC_EXT_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.rpc-ext");
|
||||
|
||||
/** Rpc extension for apache shenyu metadata * */
|
||||
private static final AttributeKey<Boolean> META_ENABLED_ATTRIBUTE =
|
||||
AttributeKey.booleanKey("apache-shenyu.meta.enabled");
|
||||
|
||||
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES;
|
||||
|
||||
static {
|
||||
CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
|
||||
InstrumentationConfig.get()
|
||||
.getBoolean("otel.instrumentation.apache-shenyu.experimental-span-attributes", false);
|
||||
}
|
||||
|
||||
private MetaDataHelper() {}
|
||||
|
||||
public static void extractAttributes(MetaData metadata, Context context) {
|
||||
if (metadata != null && CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
|
||||
Span serverSpan = LocalRootSpan.fromContextOrNull(context);
|
||||
if (serverSpan == null) {
|
||||
return;
|
||||
}
|
||||
serverSpan.setAttribute(META_ID_ATTRIBUTE, metadata.getId());
|
||||
serverSpan.setAttribute(APP_NAME_ATTRIBUTE, metadata.getAppName());
|
||||
serverSpan.setAttribute(CONTEXT_PATH_ATTRIBUTE, metadata.getContextPath());
|
||||
serverSpan.setAttribute(PATH_ATTRIBUTE, metadata.getPath());
|
||||
serverSpan.setAttribute(RPC_TYPE_ATTRIBUTE, metadata.getRpcType());
|
||||
serverSpan.setAttribute(SERVICE_NAME_ATTRIBUTE, metadata.getServiceName());
|
||||
serverSpan.setAttribute(METHOD_NAME_ATTRIBUTE, metadata.getMethodName());
|
||||
serverSpan.setAttribute(PARAMETER_TYPES_ATTRIBUTE, metadata.getParameterTypes());
|
||||
serverSpan.setAttribute(RPC_EXT_ATTRIBUTE, metadata.getRpcExt());
|
||||
serverSpan.setAttribute(META_ENABLED_ATTRIBUTE, metadata.getEnabled());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apacheshenyu.v2_4;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = {GsonAutoConfiguration.class})
|
||||
public class ShenYuBootstrapApplication {}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apacheshenyu.v2_4;
|
||||
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||
|
||||
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 java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import org.apache.shenyu.common.dto.MetaData;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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.test.context.junit.jupiter.SpringExtension;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(
|
||||
properties = {"shenyu.local.enabled=true", "spring.main.allow-bean-definition-overriding=true"},
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = {ShenYuBootstrapApplication.class})
|
||||
class ShenYuRouteTest {
|
||||
|
||||
private static final AttributeKey<String> META_ID_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.id");
|
||||
|
||||
private static final AttributeKey<String> APP_NAME_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.app-name");
|
||||
|
||||
private static final AttributeKey<String> CONTEXT_PATH_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.context-path");
|
||||
|
||||
private static final AttributeKey<String> PATH_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.path");
|
||||
|
||||
private static final AttributeKey<String> RPC_TYPE_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.rpc-type");
|
||||
|
||||
private static final AttributeKey<String> SERVICE_NAME_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.service-name");
|
||||
|
||||
private static final AttributeKey<String> METHOD_NAME_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.method-name");
|
||||
|
||||
private static final AttributeKey<String> PARAMETER_TYPES_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.param-types");
|
||||
|
||||
private static final AttributeKey<String> RPC_EXT_ATTRIBUTE =
|
||||
AttributeKey.stringKey("apache-shenyu.meta.rpc-ext");
|
||||
|
||||
private static final AttributeKey<Boolean> META_ENABLED_ATTRIBUTE =
|
||||
AttributeKey.booleanKey("apache-shenyu.meta.enabled");
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@RegisterExtension
|
||||
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll()
|
||||
throws ClassNotFoundException,
|
||||
NoSuchMethodException,
|
||||
InvocationTargetException,
|
||||
IllegalAccessException {
|
||||
|
||||
Class<?> metaDataCache = null;
|
||||
try {
|
||||
metaDataCache = Class.forName("org.apache.shenyu.plugin.global.cache.MetaDataCache");
|
||||
} catch (ClassNotFoundException e) {
|
||||
// in 2.5.0, the MetaDataCache turned to be org.apache.shenyu.plugin.base.cache
|
||||
metaDataCache = Class.forName("org.apache.shenyu.plugin.base.cache.MetaDataCache");
|
||||
}
|
||||
|
||||
Object cacheInst = metaDataCache.getMethod("getInstance").invoke(null);
|
||||
Method cacheMethod = metaDataCache.getMethod("cache", MetaData.class);
|
||||
|
||||
cacheMethod.invoke(
|
||||
cacheInst,
|
||||
new MetaData(
|
||||
"123",
|
||||
"test-shenyu",
|
||||
"/",
|
||||
"/a/b/c",
|
||||
"http",
|
||||
"shenyu-service",
|
||||
"hello",
|
||||
"string",
|
||||
"test-ext",
|
||||
true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateRouter() {
|
||||
HttpClient httpClient = HttpClient.create();
|
||||
httpClient.get().uri("http://localhost:" + port + "/a/b/c").response().block();
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("GET").hasKind(SpanKind.CLIENT),
|
||||
span ->
|
||||
span.hasName("GET /a/b/c")
|
||||
.hasKind(SpanKind.SERVER)
|
||||
.hasAttributesSatisfying(
|
||||
equalTo(AttributeKey.stringKey("http.route"), "/a/b/c"),
|
||||
equalTo(META_ID_ATTRIBUTE, "123"),
|
||||
equalTo(META_ENABLED_ATTRIBUTE, true),
|
||||
equalTo(METHOD_NAME_ATTRIBUTE, "hello"),
|
||||
equalTo(PARAMETER_TYPES_ATTRIBUTE, "string"),
|
||||
equalTo(PATH_ATTRIBUTE, "/a/b/c"),
|
||||
equalTo(RPC_EXT_ATTRIBUTE, "test-ext"),
|
||||
equalTo(RPC_TYPE_ATTRIBUTE, "http"),
|
||||
equalTo(SERVICE_NAME_ATTRIBUTE, "shenyu-service"),
|
||||
equalTo(APP_NAME_ATTRIBUTE, "test-shenyu"),
|
||||
equalTo(CONTEXT_PATH_ATTRIBUTE, "/"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnmatchedRouter() {
|
||||
HttpClient httpClient = HttpClient.create();
|
||||
httpClient.get().uri("http://localhost:" + port + "/a/b/c/d").response().block();
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("GET").hasKind(SpanKind.CLIENT),
|
||||
span -> span.hasName("GET").hasKind(SpanKind.SERVER)));
|
||||
}
|
||||
}
|
|
@ -103,6 +103,8 @@ public class AdditionalLibraryIgnoredTypesConfigurer implements IgnoredTypesConf
|
|||
.allowClass("org.springframework.boot.logging.logback.")
|
||||
.allowClass("org.springframework.boot.web.filter.")
|
||||
.allowClass("org.springframework.boot.web.servlet.")
|
||||
.allowClass(
|
||||
"org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter$$Lambda")
|
||||
.allowClass("org.springframework.boot.autoconfigure.BackgroundPreinitializer$")
|
||||
.allowClass(
|
||||
"org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration$$Lambda")
|
||||
|
|
|
@ -165,6 +165,7 @@ include(":instrumentation:apache-httpclient:apache-httpclient-4.3:library")
|
|||
include(":instrumentation:apache-httpclient:apache-httpclient-4.3:testing")
|
||||
include(":instrumentation:apache-httpclient:apache-httpclient-5.0:javaagent")
|
||||
include(":instrumentation:apache-httpclient:apache-httpclient-5.2:library")
|
||||
include(":instrumentation:apache-shenyu-2.4:javaagent")
|
||||
include(":instrumentation:armeria:armeria-1.3:javaagent")
|
||||
include(":instrumentation:armeria:armeria-1.3:library")
|
||||
include(":instrumentation:armeria:armeria-1.3:testing")
|
||||
|
|
Loading…
Reference in New Issue