diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 47d625414a9e..9ca9c3d7069d 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -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),
[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] | diff --git a/instrumentation/apache-shenyu-2.4/README.md b/instrumentation/apache-shenyu-2.4/README.md new file mode 100644 index 000000000000..603d16c91a72 --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/README.md @@ -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. | diff --git a/instrumentation/apache-shenyu-2.4/javaagent/build.gradle.kts b/instrumentation/apache-shenyu-2.4/javaagent/build.gradle.kts new file mode 100644 index 000000000000..c80be7addd0a --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/javaagent/build.gradle.kts @@ -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().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") + } +} diff --git a/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ApacheShenYuInstrumentationModule.java b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ApacheShenYuInstrumentationModule.java new file mode 100644 index 000000000000..75e4f2d9c40e --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ApacheShenYuInstrumentationModule.java @@ -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 typeInstrumentations() { + return Collections.singletonList(new ContextBuilderInstrumentation()); + } +} diff --git a/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ApacheShenYuSingletons.java b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ApacheShenYuSingletons.java new file mode 100644 index 000000000000..a26a11623f3f --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ApacheShenYuSingletons.java @@ -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 httpRouteGetter() { + return (context, metaData) -> metaData.getPath(); + } +} diff --git a/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ContextBuilderInstrumentation.java b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ContextBuilderInstrumentation.java new file mode 100644 index 000000000000..c858eb1bad33 --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ContextBuilderInstrumentation.java @@ -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 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); + } + } +} diff --git a/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/MetaDataHelper.java b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/MetaDataHelper.java new file mode 100644 index 000000000000..15645b8ea6f7 --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/MetaDataHelper.java @@ -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 META_ID_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.id"); + + /** App name for apache shenyu metadata * */ + private static final AttributeKey APP_NAME_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.app-name"); + + /** Context path for apache shenyu metadata * */ + private static final AttributeKey CONTEXT_PATH_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.context-path"); + + /** Path for apache shenyu metadata * */ + private static final AttributeKey PATH_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.path"); + + /** Rpc type for apache shenyu metadata * */ + private static final AttributeKey RPC_TYPE_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.rpc-type"); + + /** Service name for apache shenyu metadata * */ + private static final AttributeKey SERVICE_NAME_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.service-name"); + + /** Method name for apache shenyu metadata * */ + private static final AttributeKey METHOD_NAME_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.method-name"); + + /** Parameter types for apache shenyu metadata * */ + private static final AttributeKey PARAMETER_TYPES_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.param-types"); + + /** Rpc extension for apache shenyu metadata * */ + private static final AttributeKey RPC_EXT_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.rpc-ext"); + + /** Rpc extension for apache shenyu metadata * */ + private static final AttributeKey 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()); + } + } +} diff --git a/instrumentation/apache-shenyu-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ShenYuBootstrapApplication.java b/instrumentation/apache-shenyu-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ShenYuBootstrapApplication.java new file mode 100644 index 000000000000..6048d8ca97d2 --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ShenYuBootstrapApplication.java @@ -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 {} diff --git a/instrumentation/apache-shenyu-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ShenYuRouteTest.java b/instrumentation/apache-shenyu-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ShenYuRouteTest.java new file mode 100644 index 000000000000..06cd346e8f03 --- /dev/null +++ b/instrumentation/apache-shenyu-2.4/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/apacheshenyu/v2_4/ShenYuRouteTest.java @@ -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 META_ID_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.id"); + + private static final AttributeKey APP_NAME_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.app-name"); + + private static final AttributeKey CONTEXT_PATH_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.context-path"); + + private static final AttributeKey PATH_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.path"); + + private static final AttributeKey RPC_TYPE_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.rpc-type"); + + private static final AttributeKey SERVICE_NAME_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.service-name"); + + private static final AttributeKey METHOD_NAME_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.method-name"); + + private static final AttributeKey PARAMETER_TYPES_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.param-types"); + + private static final AttributeKey RPC_EXT_ATTRIBUTE = + AttributeKey.stringKey("apache-shenyu.meta.rpc-ext"); + + private static final AttributeKey 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))); + } +} 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 939941efc414..d326d56e6e02 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 @@ -103,6 +103,8 @@ public void configure(IgnoredTypesBuilder builder) { .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") diff --git a/settings.gradle.kts b/settings.gradle.kts index b6a44633fef0..59b227536d85 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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")