Browse Source

fetch route in apache shenyu (#11260)

Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
Liu Ziming 9 months ago
parent
commit
9e48f24486

+ 1 - 0
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),<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]                                                                                         |

+ 5 - 0
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.                                         |

+ 51 - 0
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<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")
+  }
+}

+ 24 - 0
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<TypeInstrumentation> typeInstrumentations() {
+    return Collections.singletonList(new ContextBuilderInstrumentation());
+  }
+}

+ 18 - 0
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<MetaData> httpRouteGetter() {
+    return (context, metaData) -> metaData.getPath();
+  }
+}

+ 56 - 0
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<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);
+    }
+  }
+}

+ 85 - 0
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<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());
+    }
+  }
+}

+ 12 - 0
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 {}

+ 137 - 0
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<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)));
+  }
+}

+ 2 - 0
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/AdditionalLibraryIgnoredTypesConfigurer.java

@@ -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")

+ 1 - 0
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")