Browse Source

Test http client stable semconv (#9178)

Lauri Tulmin 1 year ago
parent
commit
b9e459da07
49 changed files with 626 additions and 202 deletions
  1. 0 30
      instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpAttributes.java
  2. 2 1
      instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java
  3. 1 0
      instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpMessageBodySizeUtil.java
  4. 1 0
      instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java
  5. 36 0
      instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/internal/HttpAttributes.java
  6. 1 0
      instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java
  7. 1 0
      instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java
  8. 1 0
      instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java
  9. 1 0
      instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java
  10. 1 0
      instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsStableSemconvTest.java
  11. 1 0
      instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsStableSemconvTest.java
  12. 1 0
      instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java
  13. 1 0
      instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java
  14. 21 6
      instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts
  15. 10 0
      instrumentation/apache-httpasyncclient-4.1/javaagent/build.gradle.kts
  16. 8 0
      instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/build.gradle.kts
  17. 10 0
      instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/build.gradle.kts
  18. 10 0
      instrumentation/apache-httpclient/apache-httpclient-4.3/library/build.gradle.kts
  19. 10 0
      instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/build.gradle.kts
  20. 14 1
      instrumentation/armeria-1.3/javaagent/build.gradle.kts
  21. 14 1
      instrumentation/armeria-1.3/library/build.gradle.kts
  22. 15 5
      instrumentation/async-http-client/async-http-client-1.9/javaagent/build.gradle.kts
  23. 10 0
      instrumentation/async-http-client/async-http-client-2.0/javaagent/build.gradle.kts
  24. 10 0
      instrumentation/google-http-client-1.19/javaagent/build.gradle.kts
  25. 8 6
      instrumentation/google-http-client-1.19/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/AbstractGoogleHttpClientTest.java
  26. 10 0
      instrumentation/http-url-connection/javaagent/build.gradle.kts
  27. 28 6
      instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpMethodAttributeExtractor.java
  28. 3 1
      instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionSingletons.java
  29. 103 42
      instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionTest.java
  30. 0 73
      instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/UrlConnectionTest.java
  31. 10 0
      instrumentation/java-http-client/javaagent/build.gradle.kts
  32. 10 0
      instrumentation/java-http-client/library/build.gradle.kts
  33. 10 0
      instrumentation/jetty-httpclient/jetty-httpclient-9.2/javaagent/build.gradle.kts
  34. 10 0
      instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/build.gradle.kts
  35. 15 0
      instrumentation/jodd-http-4.2/javaagent/build.gradle.kts
  36. 15 0
      instrumentation/netty/netty-3.8/javaagent/build.gradle.kts
  37. 10 0
      instrumentation/netty/netty-4.0/javaagent/build.gradle.kts
  38. 10 0
      instrumentation/netty/netty-4.1/javaagent/build.gradle.kts
  39. 15 0
      instrumentation/netty/netty-4.1/library/build.gradle.kts
  40. 10 0
      instrumentation/okhttp/okhttp-2.2/javaagent/build.gradle.kts
  41. 10 0
      instrumentation/okhttp/okhttp-3.0/javaagent/build.gradle.kts
  42. 10 0
      instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts
  43. 10 0
      instrumentation/play/play-ws/play-ws-1.0/javaagent/build.gradle.kts
  44. 10 0
      instrumentation/play/play-ws/play-ws-2.0/javaagent/build.gradle.kts
  45. 10 0
      instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts
  46. 10 0
      instrumentation/spring/spring-web/spring-web-3.1/library/build.gradle.kts
  47. 9 1
      instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/build.gradle.kts
  48. 10 0
      instrumentation/vertx/vertx-http-client/vertx-http-client-4.0/javaagent/build.gradle.kts
  49. 100 29
      testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java

+ 0 - 30
instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpAttributes.java

@@ -1,30 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.instrumentation.api.instrumenter.http;
-
-import static io.opentelemetry.api.common.AttributeKey.longKey;
-import static io.opentelemetry.api.common.AttributeKey.stringKey;
-
-import io.opentelemetry.api.common.AttributeKey;
-
-final class HttpAttributes {
-
-  // FIXME: remove this class and replace its usages with SemanticAttributes once schema 1.21 is
-  // released
-
-  static final AttributeKey<String> HTTP_REQUEST_METHOD = stringKey("http.request.method");
-
-  static final AttributeKey<String> HTTP_REQUEST_METHOD_ORIGINAL =
-      stringKey("http.request.method_original");
-
-  static final AttributeKey<Long> HTTP_REQUEST_BODY_SIZE = longKey("http.request.body.size");
-
-  static final AttributeKey<Long> HTTP_RESPONSE_BODY_SIZE = longKey("http.response.body.size");
-
-  static final AttributeKey<Long> HTTP_RESPONSE_STATUS_CODE = longKey("http.response.status_code");
-
-  private HttpAttributes() {}
-}

+ 2 - 1
instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpCommonAttributesExtractor.java

@@ -14,6 +14,7 @@ import static io.opentelemetry.instrumentation.api.internal.HttpConstants._OTHER
 import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.internal.SemconvStability;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import java.util.HashSet;
@@ -50,7 +51,7 @@ abstract class HttpCommonAttributesExtractor<
   public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
     String method = getter.getHttpRequestMethod(request);
     if (SemconvStability.emitStableHttpSemconv()) {
-      if (knownMethods.contains(method)) {
+      if (method == null || knownMethods.contains(method)) {
         internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, method);
       } else {
         internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, _OTHER);

+ 1 - 0
instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpMessageBodySizeUtil.java

@@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.api.instrumenter.http;
 
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.internal.SemconvStability;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import javax.annotation.Nullable;

+ 1 - 0
instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java

@@ -8,6 +8,7 @@ package io.opentelemetry.instrumentation.api.instrumenter.http;
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;

+ 36 - 0
instrumentation-api-semconv/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/internal/HttpAttributes.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.instrumenter.http.internal;
+
+import static io.opentelemetry.api.common.AttributeKey.longKey;
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+
+import io.opentelemetry.api.common.AttributeKey;
+
+/**
+ * This class is internal and is hence not for public use. Its APIs are unstable and can change at
+ * any time.
+ */
+public final class HttpAttributes {
+
+  // FIXME: remove this class and replace its usages with SemanticAttributes once schema 1.21 is
+  // released
+
+  public static final AttributeKey<String> HTTP_REQUEST_METHOD = stringKey("http.request.method");
+
+  public static final AttributeKey<String> HTTP_REQUEST_METHOD_ORIGINAL =
+      stringKey("http.request.method_original");
+
+  public static final AttributeKey<Long> HTTP_REQUEST_BODY_SIZE = longKey("http.request.body.size");
+
+  public static final AttributeKey<Long> HTTP_RESPONSE_BODY_SIZE =
+      longKey("http.response.body.size");
+
+  public static final AttributeKey<Long> HTTP_RESPONSE_STATUS_CODE =
+      longKey("http.response.status_code");
+
+  private HttpAttributes() {}
+}

+ 1 - 0
instrumentation-api-semconv/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsViewTest.java

@@ -13,6 +13,7 @@ import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTr
 import static org.assertj.core.api.Assertions.entry;
 
 import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;

+ 1 - 0
instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorBothSemconvTest.java

@@ -16,6 +16,7 @@ import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;

+ 1 - 0
instrumentation-api-semconv/src/testBothHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorBothSemconvTest.java

@@ -16,6 +16,7 @@ import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;

+ 1 - 0
instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientAttributesExtractorStableSemconvTest.java

@@ -18,6 +18,7 @@ import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.instrumentation.api.internal.HttpConstants;

+ 1 - 0
instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientExperimentalMetricsStableSemconvTest.java

@@ -15,6 +15,7 @@ import io.opentelemetry.api.trace.TraceFlags;
 import io.opentelemetry.api.trace.TraceState;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;

+ 1 - 0
instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetricsStableSemconvTest.java

@@ -15,6 +15,7 @@ import io.opentelemetry.api.trace.TraceFlags;
 import io.opentelemetry.api.trace.TraceState;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;

+ 1 - 0
instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerAttributesExtractorStableSemconvTest.java

@@ -18,6 +18,7 @@ import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.instrumentation.api.internal.HttpConstants;

+ 1 - 0
instrumentation-api-semconv/src/testStableHttpSemconv/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetricsStableSemconvTest.java

@@ -15,6 +15,7 @@ import io.opentelemetry.api.trace.TraceFlags;
 import io.opentelemetry.api.trace.TraceState;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
 import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
 import io.opentelemetry.sdk.metrics.SdkMeterProvider;

+ 21 - 6
instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts

@@ -42,14 +42,29 @@ dependencies {
   latestDepTestLibrary("com.typesafe.akka:akka-stream_2.13:+")
 }
 
-tasks.withType<Test>().configureEach {
-  // required on jdk17
-  jvmArgs("--add-exports=java.base/sun.security.util=ALL-UNNAMED")
-  jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("AkkaHttpClientInstrumentationTest")
+    }
+    include("**/AkkaHttpClientInstrumentationTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  withType<Test>().configureEach {
+    // required on jdk17
+    jvmArgs("--add-exports=java.base/sun.security.util=ALL-UNNAMED")
+    jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
 
-  jvmArgs("-Dio.opentelemetry.javaagent.shaded.io.opentelemetry.context.enableStrictContext=false")
+    jvmArgs("-Dio.opentelemetry.javaagent.shaded.io.opentelemetry.context.enableStrictContext=false")
 
-  systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
+    systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
 }
 
 if (findProperty("testLatestDeps") as Boolean) {

+ 10 - 0
instrumentation/apache-httpasyncclient-4.1/javaagent/build.gradle.kts

@@ -16,3 +16,13 @@ muzzle {
 dependencies {
   library("org.apache.httpcomponents:httpasyncclient:4.1")
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 8 - 0
instrumentation/apache-httpclient/apache-httpclient-2.0/javaagent/build.gradle.kts

@@ -17,7 +17,15 @@ dependencies {
 }
 
 tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
   withType<Test>().configureEach {
     systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
   }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
 }

+ 10 - 0
instrumentation/apache-httpclient/apache-httpclient-4.0/javaagent/build.gradle.kts

@@ -27,3 +27,13 @@ dependencies {
   library("org.apache.httpcomponents:httpclient:4.0")
   testCompileOnly("net.jcip:jcip-annotations:1.0")
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/apache-httpclient/apache-httpclient-4.3/library/build.gradle.kts

@@ -11,3 +11,13 @@ dependencies {
 
   latestDepTestLibrary("org.apache.httpcomponents:httpclient:4.+") // see apache-httpclient-5.0 module
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/apache-httpclient/apache-httpclient-5.0/javaagent/build.gradle.kts

@@ -13,3 +13,13 @@ muzzle {
 dependencies {
   library("org.apache.httpcomponents.client5:httpclient5:5.0")
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 14 - 1
instrumentation/armeria-1.3/javaagent/build.gradle.kts

@@ -21,7 +21,20 @@ dependencies {
 }
 
 tasks {
-  test {
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("ArmeriaHttpClientTest")
+    }
+    include("**/ArmeriaHttpClientTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  withType<Test>().configureEach {
     systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
   }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
 }

+ 14 - 1
instrumentation/armeria-1.3/library/build.gradle.kts

@@ -10,7 +10,20 @@ dependencies {
 }
 
 tasks {
-  test {
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("ArmeriaHttpClientTest")
+    }
+    include("**/ArmeriaHttpClientTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  withType<Test>().configureEach {
     systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
   }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
 }

+ 15 - 5
instrumentation/async-http-client/async-http-client-1.9/javaagent/build.gradle.kts

@@ -20,10 +20,20 @@ dependencies {
   testInstrumentation(project(":instrumentation:netty:netty-3.8:javaagent"))
 }
 
-tasks.withType<Test>().configureEach {
-  // required on jdk17
-  jvmArgs("--add-exports=java.base/sun.security.util=ALL-UNNAMED")
-  jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  withType<Test>().configureEach {
+    // required on jdk17
+    jvmArgs("--add-exports=java.base/sun.security.util=ALL-UNNAMED")
+    jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
 
-  systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
+    systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
 }

+ 10 - 0
instrumentation/async-http-client/async-http-client-2.0/javaagent/build.gradle.kts

@@ -23,6 +23,16 @@ otelJava {
   maxJavaVersionForTests.set(JavaVersion.VERSION_1_8)
 }
 
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}
+
 // async-http-client 2.0.0 does not work with Netty versions newer than this due to referencing an
 // internal file.
 if (!(findProperty("testLatestDeps") as Boolean)) {

+ 10 - 0
instrumentation/google-http-client-1.19/javaagent/build.gradle.kts

@@ -15,3 +15,13 @@ muzzle {
 dependencies {
   library("com.google.http-client:google-http-client:1.19.0")
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 8 - 6
instrumentation/google-http-client-1.19/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/googlehttpclient/AbstractGoogleHttpClientTest.java

@@ -95,13 +95,15 @@ public abstract class AbstractGoogleHttpClientTest extends AbstractHttpClientTes
                     span.hasKind(SpanKind.CLIENT)
                         .hasStatus(StatusData.error())
                         .hasAttributesSatisfyingExactly(
-                            equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
-                            satisfies(SemanticAttributes.NET_PEER_PORT, port -> port.isPositive()),
-                            equalTo(SemanticAttributes.HTTP_URL, uri.toString()),
-                            equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
-                            equalTo(SemanticAttributes.HTTP_STATUS_CODE, 500),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PEER_NAME), "localhost"),
                             satisfies(
-                                SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.NET_PEER_PORT),
+                                port -> port.isPositive()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_URL), uri.toString()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_METHOD), "GET"),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), 500),
+                            satisfies(
+                                getAttributeKey(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH),
                                 length -> length.isPositive())),
                 span -> span.hasKind(SpanKind.SERVER).hasParent(trace.getSpan(0))));
   }

+ 10 - 0
instrumentation/http-url-connection/javaagent/build.gradle.kts

@@ -7,3 +7,13 @@ muzzle {
     coreJdk()
   }
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 28 - 6
instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpMethodAttributeExtractor.java

@@ -5,22 +5,33 @@
 
 package io.opentelemetry.javaagent.instrumentation.httpurlconnection;
 
+import static io.opentelemetry.instrumentation.api.internal.AttributesExtractorUtil.internalSet;
+import static io.opentelemetry.instrumentation.api.internal.HttpConstants._OTHER;
+
 import io.opentelemetry.api.common.AttributesBuilder;
 import io.opentelemetry.api.trace.Span;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
+import io.opentelemetry.instrumentation.api.internal.SemconvStability;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import java.net.HttpURLConnection;
+import java.util.Set;
 import javax.annotation.Nullable;
 
 public class HttpMethodAttributeExtractor<
         REQUEST extends HttpURLConnection, RESPONSE extends Integer>
     implements AttributesExtractor<REQUEST, RESPONSE> {
 
-  private HttpMethodAttributeExtractor() {}
+  private final Set<String> knownMethods;
+
+  private HttpMethodAttributeExtractor(Set<String> knownMethods) {
+    this.knownMethods = knownMethods;
+  }
 
-  public static AttributesExtractor<? super HttpURLConnection, ? super Integer> create() {
-    return new HttpMethodAttributeExtractor<>();
+  public static AttributesExtractor<? super HttpURLConnection, ? super Integer> create(
+      Set<String> knownMethods) {
+    return new HttpMethodAttributeExtractor<>(knownMethods);
   }
 
   @Override
@@ -38,11 +49,22 @@ public class HttpMethodAttributeExtractor<
     GetOutputStreamContext getOutputStreamContext = GetOutputStreamContext.get(context);
 
     if (getOutputStreamContext.isOutputStreamMethodOfSunConnectionCalled()) {
-      String requestMethod = connection.getRequestMethod();
+      String method = connection.getRequestMethod();
       // The getOutputStream() has transformed "GET" into "POST"
-      attributes.put(SemanticAttributes.HTTP_METHOD, requestMethod);
+      if (SemconvStability.emitStableHttpSemconv()) {
+        if (knownMethods.contains(method)) {
+          internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, method);
+          attributes.remove(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL);
+        } else {
+          internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, _OTHER);
+          internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, method);
+        }
+      }
+      if (SemconvStability.emitOldHttpSemconv()) {
+        internalSet(attributes, SemanticAttributes.HTTP_METHOD, method);
+      }
       Span span = Span.fromContext(context);
-      span.updateName(requestMethod);
+      span.updateName(method);
     }
   }
 }

+ 3 - 1
instrumentation/http-url-connection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionSingletons.java

@@ -39,7 +39,9 @@ public final class HttpUrlConnectionSingletons {
             .addAttributesExtractor(
                 PeerServiceAttributesExtractor.create(
                     httpAttributesGetter, CommonConfig.get().getPeerServiceMapping()))
-            .addAttributesExtractor(HttpMethodAttributeExtractor.create())
+            .addAttributesExtractor(
+                HttpMethodAttributeExtractor.create(
+                    CommonConfig.get().getKnownHttpRequestMethods()))
             .addContextCustomizer(
                 (context, httpRequestPacket, startAttributes) ->
                     GetOutputStreamContext.init(context))

+ 103 - 42
instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/HttpUrlConnectionTest.java

@@ -6,17 +6,21 @@
 package io.opentelemetry.javaagent.instrumentation.httpurlconnection;
 
 import static io.opentelemetry.api.trace.SpanKind.CLIENT;
+import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
 import static io.opentelemetry.api.trace.SpanKind.SERVER;
 import static io.opentelemetry.javaagent.instrumentation.httpurlconnection.StreamUtils.readLines;
 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 static org.assertj.core.api.Assertions.catchThrowable;
 
 import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.instrumentation.test.utils.PortUtils;
 import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
 import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest;
 import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension;
 import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions;
+import io.opentelemetry.sdk.trace.data.StatusData;
 import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -24,6 +28,7 @@ import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URI;
 import java.net.URL;
+import java.net.URLConnection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -118,15 +123,17 @@ class HttpUrlConnectionTest extends AbstractHttpClientTest<HttpURLConnection> {
                         .hasKind(CLIENT)
                         .hasParent(trace.getSpan(0))
                         .hasAttributesSatisfyingExactly(
-                            equalTo(SemanticAttributes.NET_PROTOCOL_NAME, "http"),
-                            equalTo(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"),
-                            equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
-                            equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()),
-                            equalTo(SemanticAttributes.HTTP_URL, url.toString()),
-                            equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
-                            equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME), "http"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PROTOCOL_VERSION), "1.1"),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PEER_NAME), "localhost"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PEER_PORT), url.getPort()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_URL), url.toString()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_METHOD), "GET"),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), STATUS),
                             satisfies(
-                                SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH),
                                 AbstractLongAssert::isNotNegative)),
                 span ->
                     span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1)),
@@ -135,15 +142,17 @@ class HttpUrlConnectionTest extends AbstractHttpClientTest<HttpURLConnection> {
                         .hasKind(CLIENT)
                         .hasParent(trace.getSpan(0))
                         .hasAttributesSatisfyingExactly(
-                            equalTo(SemanticAttributes.NET_PROTOCOL_NAME, "http"),
-                            equalTo(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"),
-                            equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
-                            equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()),
-                            equalTo(SemanticAttributes.HTTP_URL, url.toString()),
-                            equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
-                            equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME), "http"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PROTOCOL_VERSION), "1.1"),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PEER_NAME), "localhost"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PEER_PORT), url.getPort()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_URL), url.toString()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_METHOD), "GET"),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), STATUS),
                             satisfies(
-                                SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH),
                                 AbstractLongAssert::isNotNegative)),
                 span ->
                     span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(3))));
@@ -173,15 +182,17 @@ class HttpUrlConnectionTest extends AbstractHttpClientTest<HttpURLConnection> {
                         .hasKind(CLIENT)
                         .hasParent(trace.getSpan(0))
                         .hasAttributesSatisfyingExactly(
-                            equalTo(SemanticAttributes.NET_PROTOCOL_NAME, "http"),
-                            equalTo(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"),
-                            equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
-                            equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()),
-                            equalTo(SemanticAttributes.HTTP_URL, url.toString()),
-                            equalTo(SemanticAttributes.HTTP_METHOD, "GET"),
-                            equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME), "http"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PROTOCOL_VERSION), "1.1"),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PEER_NAME), "localhost"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PEER_PORT), url.getPort()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_URL), url.toString()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_METHOD), "GET"),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), STATUS),
                             satisfies(
-                                SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH),
                                 AbstractLongAssert::isNotNegative)),
                 span ->
                     span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1))));
@@ -224,18 +235,20 @@ class HttpUrlConnectionTest extends AbstractHttpClientTest<HttpURLConnection> {
                         .hasKind(CLIENT)
                         .hasParent(trace.getSpan(0))
                         .hasAttributesSatisfyingExactly(
-                            equalTo(SemanticAttributes.NET_PROTOCOL_NAME, "http"),
-                            equalTo(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"),
-                            equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
-                            equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()),
-                            equalTo(SemanticAttributes.HTTP_URL, url.toString()),
-                            equalTo(SemanticAttributes.HTTP_METHOD, "POST"),
-                            equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME), "http"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PROTOCOL_VERSION), "1.1"),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PEER_NAME), "localhost"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PEER_PORT), url.getPort()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_URL), url.toString()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_METHOD), "POST"),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), STATUS),
                             satisfies(
-                                SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH),
                                 AbstractLongAssert::isNotNegative),
                             satisfies(
-                                SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH),
                                 AbstractLongAssert::isNotNegative)),
                 span ->
                     span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1))));
@@ -280,20 +293,68 @@ class HttpUrlConnectionTest extends AbstractHttpClientTest<HttpURLConnection> {
                         .hasKind(CLIENT)
                         .hasParent(trace.getSpan(0))
                         .hasAttributesSatisfyingExactly(
-                            equalTo(SemanticAttributes.NET_PROTOCOL_NAME, "http"),
-                            equalTo(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"),
-                            equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
-                            equalTo(SemanticAttributes.NET_PEER_PORT, url.getPort()),
-                            equalTo(SemanticAttributes.HTTP_URL, url.toString()),
-                            equalTo(SemanticAttributes.HTTP_METHOD, "POST"),
-                            equalTo(SemanticAttributes.HTTP_STATUS_CODE, STATUS),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME), "http"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PROTOCOL_VERSION), "1.1"),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PEER_NAME), "localhost"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PEER_PORT), url.getPort()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_URL), url.toString()),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_METHOD), "POST"),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), STATUS),
                             satisfies(
-                                SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH),
                                 AbstractLongAssert::isNotNegative),
                             satisfies(
-                                SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
+                                getAttributeKey(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH),
                                 AbstractLongAssert::isNotNegative)),
                 span ->
                     span.hasName("test-http-server").hasKind(SERVER).hasParent(trace.getSpan(1))));
   }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"http", "https"})
+  public void traceRequestWithConnectionFailure(String scheme) {
+    String uri = scheme + "://localhost:" + PortUtils.UNUSABLE_PORT;
+
+    Throwable thrown =
+        catchThrowable(
+            () ->
+                testing.runWithSpan(
+                    "someTrace",
+                    () -> {
+                      URL url = new URI(uri).toURL();
+                      URLConnection connection = url.openConnection();
+                      connection.setConnectTimeout(10000);
+                      connection.setReadTimeout(10000);
+                      assertThat(Span.current().getSpanContext().isValid()).isTrue();
+                      connection.getInputStream();
+                    }));
+
+    testing.waitAndAssertTraces(
+        trace ->
+            trace.hasSpansSatisfyingExactly(
+                span ->
+                    span.hasName("someTrace")
+                        .hasKind(INTERNAL)
+                        .hasNoParent()
+                        .hasStatus(StatusData.error())
+                        .hasException(thrown),
+                span ->
+                    span.hasName("GET")
+                        .hasKind(CLIENT)
+                        .hasParent(trace.getSpan(0))
+                        .hasStatus(StatusData.error())
+                        .hasException(thrown)
+                        .hasAttributesSatisfyingExactly(
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME), "http"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PROTOCOL_VERSION), "1.1"),
+                            equalTo(getAttributeKey(SemanticAttributes.NET_PEER_NAME), "localhost"),
+                            equalTo(
+                                getAttributeKey(SemanticAttributes.NET_PEER_PORT),
+                                PortUtils.UNUSABLE_PORT),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_URL), uri),
+                            equalTo(getAttributeKey(SemanticAttributes.HTTP_METHOD), "GET"))));
+  }
 }

+ 0 - 73
instrumentation/http-url-connection/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/httpurlconnection/UrlConnectionTest.java

@@ -1,73 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.httpurlconnection;
-
-import static io.opentelemetry.api.trace.SpanKind.CLIENT;
-import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
-import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.catchThrowable;
-
-import io.opentelemetry.api.trace.Span;
-import io.opentelemetry.instrumentation.test.utils.PortUtils;
-import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
-import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
-import io.opentelemetry.sdk.trace.data.StatusData;
-import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLConnection;
-import org.junit.jupiter.api.extension.RegisterExtension;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
-class UrlConnectionTest {
-  @RegisterExtension
-  static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
-
-  @ParameterizedTest
-  @ValueSource(strings = {"http", "https"})
-  public void traceRequestWithConnectionFailure(String scheme) {
-    String uri = scheme + "://localhost:" + PortUtils.UNUSABLE_PORT;
-
-    Throwable thrown =
-        catchThrowable(
-            () ->
-                testing.runWithSpan(
-                    "someTrace",
-                    () -> {
-                      URL url = new URI(uri).toURL();
-                      URLConnection connection = url.openConnection();
-                      connection.setConnectTimeout(10000);
-                      connection.setReadTimeout(10000);
-                      assertThat(Span.current().getSpanContext().isValid()).isTrue();
-                      connection.getInputStream();
-                    }));
-
-    testing.waitAndAssertTraces(
-        trace ->
-            trace.hasSpansSatisfyingExactly(
-                span ->
-                    span.hasName("someTrace")
-                        .hasKind(INTERNAL)
-                        .hasNoParent()
-                        .hasStatus(StatusData.error())
-                        .hasException(thrown),
-                span ->
-                    span.hasName("GET")
-                        .hasKind(CLIENT)
-                        .hasParent(trace.getSpan(0))
-                        .hasStatus(StatusData.error())
-                        .hasException(thrown)
-                        .hasAttributesSatisfyingExactly(
-                            equalTo(SemanticAttributes.NET_PROTOCOL_NAME, "http"),
-                            equalTo(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1"),
-                            equalTo(SemanticAttributes.NET_PEER_NAME, "localhost"),
-                            equalTo(SemanticAttributes.NET_PEER_PORT, PortUtils.UNUSABLE_PORT),
-                            equalTo(SemanticAttributes.HTTP_URL, uri),
-                            equalTo(SemanticAttributes.HTTP_METHOD, "GET"))));
-  }
-}

+ 10 - 0
instrumentation/java-http-client/javaagent/build.gradle.kts

@@ -16,3 +16,13 @@ dependencies {
   implementation(project(":instrumentation:java-http-client:library"))
   testImplementation(project(":instrumentation:java-http-client:testing"))
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/java-http-client/library/build.gradle.kts

@@ -10,3 +10,13 @@ otelJava {
 dependencies {
   testImplementation(project(":instrumentation:java-http-client:testing"))
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/jetty-httpclient/jetty-httpclient-9.2/javaagent/build.gradle.kts

@@ -22,3 +22,13 @@ dependencies {
 
   latestDepTestLibrary("org.eclipse.jetty:jetty-client:9.+") // documented limitation
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/jetty-httpclient/jetty-httpclient-9.2/library/build.gradle.kts

@@ -12,3 +12,13 @@ dependencies {
 
   latestDepTestLibrary("org.eclipse.jetty:jetty-client:9.+")
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 15 - 0
instrumentation/jodd-http-4.2/javaagent/build.gradle.kts

@@ -17,3 +17,18 @@ dependencies {
   testImplementation(project(":instrumentation:jodd-http-4.2:javaagent"))
   testImplementation(project(":instrumentation-api-semconv"))
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("JoddHttpTest")
+    }
+    include("**/JoddHttpTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 15 - 0
instrumentation/netty/netty-3.8/javaagent/build.gradle.kts

@@ -35,6 +35,21 @@ dependencies {
   latestDepTestLibrary("com.ning:async-http-client:1.9.+")
 }
 
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("*ClientTest")
+    }
+    include("**/*ClientTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}
+
 // We need to force the dependency to the earliest supported version because other libraries declare newer versions.
 if (!(findProperty("testLatestDeps") as Boolean)) {
   configurations.configureEach {

+ 10 - 0
instrumentation/netty/netty-4.0/javaagent/build.gradle.kts

@@ -46,6 +46,15 @@ tasks {
     jvmArgs("-Dotel.instrumentation.netty.ssl-telemetry.enabled=true")
   }
 
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("*ClientTest")
+    }
+    include("**/*ClientTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
   test {
     filter {
       excludeTestsMatching("Netty40ConnectionSpanTest")
@@ -55,6 +64,7 @@ tasks {
 
   check {
     dependsOn(testConnectionSpan)
+    dependsOn(testStableSemconv)
   }
 }
 

+ 10 - 0
instrumentation/netty/netty-4.1/javaagent/build.gradle.kts

@@ -53,6 +53,15 @@ tasks {
     jvmArgs("-Dotel.instrumentation.netty.ssl-telemetry.enabled=true")
   }
 
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("*ClientTest")
+    }
+    include("**/*ClientTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
   test {
     systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
 
@@ -64,6 +73,7 @@ tasks {
 
   check {
     dependsOn(testConnectionSpan)
+    dependsOn(testStableSemconv)
   }
 }
 

+ 15 - 0
instrumentation/netty/netty-4.1/library/build.gradle.kts

@@ -12,3 +12,18 @@ dependencies {
 
   testImplementation(project(":instrumentation:netty:netty-4.1:testing"))
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    filter {
+      includeTestsMatching("*ClientTest")
+    }
+    include("**/*ClientTest.*")
+
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/okhttp/okhttp-2.2/javaagent/build.gradle.kts

@@ -22,3 +22,13 @@ dependencies {
 
   latestDepTestLibrary("com.squareup.okhttp:okhttp:2.+") // see okhttp-3.0 module
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/okhttp/okhttp-3.0/javaagent/build.gradle.kts

@@ -20,3 +20,13 @@ dependencies {
 
   testImplementation(project(":instrumentation:okhttp:okhttp-3.0:testing"))
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/okhttp/okhttp-3.0/library/build.gradle.kts

@@ -9,3 +9,13 @@ dependencies {
 
   testImplementation(project(":instrumentation:okhttp:okhttp-3.0:testing"))
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/play/play-ws/play-ws-1.0/javaagent/build.gradle.kts

@@ -39,3 +39,13 @@ dependencies {
 
   latestDepTestLibrary("com.typesafe.play:play-ahc-ws-standalone_$scalaVersion:1.+")
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/play/play-ws/play-ws-2.0/javaagent/build.gradle.kts

@@ -45,3 +45,13 @@ dependencies {
 
   latestDepTestLibrary("com.typesafe.play:play-ahc-ws-standalone_$scalaVersion:2.0.+")
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 10 - 0
instrumentation/play/play-ws/play-ws-2.1/javaagent/build.gradle.kts

@@ -45,6 +45,16 @@ dependencies {
   latestDepTestLibrary("com.typesafe.play:play-ahc-ws-standalone_2.13:+")
 }
 
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}
+
 if (findProperty("testLatestDeps") as Boolean) {
   configurations {
     // play-ws artifact name is different for regular and latest tests

+ 10 - 0
instrumentation/spring/spring-web/spring-web-3.1/library/build.gradle.kts

@@ -11,6 +11,16 @@ dependencies {
   testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
 }
 
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}
+
 val latestDepTest = findProperty("testLatestDeps") as Boolean
 
 // spring 6 requires java 17

+ 9 - 1
instrumentation/vertx/vertx-http-client/vertx-http-client-3.0/javaagent/build.gradle.kts

@@ -36,7 +36,15 @@ dependencies {
 }
 
 tasks {
-  test {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  withType<Test>().configureEach {
     systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
   }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
 }

+ 10 - 0
instrumentation/vertx/vertx-http-client/vertx-http-client-4.0/javaagent/build.gradle.kts

@@ -21,3 +21,13 @@ dependencies {
 
   testInstrumentation(project(":instrumentation:netty:netty-4.1:javaagent"))
 }
+
+tasks {
+  val testStableSemconv by registering(Test::class) {
+    jvmArgs("-Dotel.semconv-stability.opt-in=http")
+  }
+
+  check {
+    dependsOn(testStableSemconv)
+  }
+}

+ 100 - 29
testing-common/src/main/java/io/opentelemetry/instrumentation/testing/junit/http/AbstractHttpClientTest.java

@@ -17,6 +17,10 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.trace.Span;
 import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
+import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
+import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
+import io.opentelemetry.instrumentation.api.internal.SemconvStability;
 import io.opentelemetry.instrumentation.test.utils.PortUtils;
 import io.opentelemetry.instrumentation.testing.InstrumentationTestRunner;
 import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
@@ -30,6 +34,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -937,32 +942,49 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
       String method,
       @Nullable Integer responseCode,
       @Nullable Integer resendCount) {
-    Set<AttributeKey<?>> httpClientAttributes = options.getHttpAttributes().apply(uri);
+    Set<AttributeKey<?>> httpClientAttributes =
+        getAttributeKeys(options.getHttpAttributes().apply(uri));
     return span.hasName(options.getExpectedClientSpanNameMapper().apply(uri, method))
         .hasKind(SpanKind.CLIENT)
         .hasAttributesSatisfying(
             attrs -> {
               // TODO: Move to test knob rather than always treating as optional
-              if (attrs.get(SemanticAttributes.NET_TRANSPORT) != null) {
+              if (SemconvStability.emitOldHttpSemconv()
+                  && attrs.get(SemanticAttributes.NET_TRANSPORT) != null) {
                 assertThat(attrs).containsEntry(SemanticAttributes.NET_TRANSPORT, IP_TCP);
               }
-              if (httpClientAttributes.contains(SemanticAttributes.NET_PROTOCOL_NAME)) {
-                assertThat(attrs).containsEntry(SemanticAttributes.NET_PROTOCOL_NAME, "http");
+              if (SemconvStability.emitStableHttpSemconv()
+                  && attrs.get(NetworkAttributes.NETWORK_TRANSPORT) != null) {
+                assertThat(attrs).containsEntry(NetworkAttributes.NETWORK_TRANSPORT, "tcp");
               }
-              if (httpClientAttributes.contains(SemanticAttributes.NET_PROTOCOL_VERSION)) {
+              if (SemconvStability.emitStableHttpSemconv()
+                  && attrs.get(NetworkAttributes.NETWORK_TYPE) != null) {
+                assertThat(attrs).containsEntry(NetworkAttributes.NETWORK_TYPE, "ipv4");
+              }
+              AttributeKey<String> netProtocolKey =
+                  getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME);
+              if (httpClientAttributes.contains(netProtocolKey)) {
+                assertThat(attrs).containsEntry(netProtocolKey, "http");
+              }
+              AttributeKey<String> netProtocolVersionKey =
+                  getAttributeKey(SemanticAttributes.NET_PROTOCOL_VERSION);
+              if (httpClientAttributes.contains(netProtocolVersionKey)) {
                 // TODO(anuraaga): Support HTTP/2
-                assertThat(attrs).containsEntry(SemanticAttributes.NET_PROTOCOL_VERSION, "1.1");
+                assertThat(attrs).containsEntry(netProtocolVersionKey, "1.1");
               }
-              if (httpClientAttributes.contains(SemanticAttributes.NET_PEER_NAME)) {
-                assertThat(attrs).containsEntry(SemanticAttributes.NET_PEER_NAME, uri.getHost());
+              AttributeKey<String> netPeerNameKey =
+                  getAttributeKey(SemanticAttributes.NET_PEER_NAME);
+              if (httpClientAttributes.contains(netPeerNameKey)) {
+                assertThat(attrs).containsEntry(netPeerNameKey, uri.getHost());
               }
-              if (httpClientAttributes.contains(SemanticAttributes.NET_PEER_PORT)) {
+              AttributeKey<Long> netPeerPortKey = getAttributeKey(SemanticAttributes.NET_PEER_PORT);
+              if (httpClientAttributes.contains(netPeerPortKey)) {
                 int uriPort = uri.getPort();
                 // default values are ignored
                 if (uriPort <= 0 || uriPort == 80 || uriPort == 443) {
-                  assertThat(attrs).doesNotContainKey(SemanticAttributes.NET_PEER_PORT);
+                  assertThat(attrs).doesNotContainKey(netPeerPortKey);
                 } else {
-                  assertThat(attrs).containsEntry(SemanticAttributes.NET_PEER_PORT, uriPort);
+                  assertThat(attrs).containsEntry(netPeerPortKey, uriPort);
                 }
               }
 
@@ -978,25 +1000,30 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
 
               } else {
                 // TODO: Move to test knob rather than always treating as optional
-                if (attrs.get(SemanticAttributes.NET_SOCK_PEER_ADDR) != null) {
-                  assertThat(attrs)
-                      .containsEntry(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1");
+                AttributeKey<String> netSockPeerAddrKey =
+                    getAttributeKey(SemanticAttributes.NET_SOCK_PEER_ADDR);
+                if (attrs.get(netSockPeerAddrKey) != null) {
+                  assertThat(attrs).containsEntry(netSockPeerAddrKey, "127.0.0.1");
                 }
-                if (attrs.get(SemanticAttributes.NET_SOCK_PEER_PORT) != null) {
+                AttributeKey<Long> netSockPeerPortKey =
+                    getAttributeKey(SemanticAttributes.NET_SOCK_PEER_PORT);
+                if (attrs.get(netSockPeerPortKey) != null) {
                   assertThat(attrs)
                       .containsEntry(
-                          SemanticAttributes.NET_SOCK_PEER_PORT,
+                          netSockPeerPortKey,
                           Objects.equals(uri.getScheme(), "https")
                               ? server.httpsPort()
                               : server.httpPort());
                 }
               }
 
-              if (httpClientAttributes.contains(SemanticAttributes.HTTP_URL)) {
-                assertThat(attrs).containsEntry(SemanticAttributes.HTTP_URL, uri.toString());
+              AttributeKey<String> httpUrlKey = getAttributeKey(SemanticAttributes.HTTP_URL);
+              if (httpClientAttributes.contains(httpUrlKey)) {
+                assertThat(attrs).containsEntry(httpUrlKey, uri.toString());
               }
-              if (httpClientAttributes.contains(SemanticAttributes.HTTP_METHOD)) {
-                assertThat(attrs).containsEntry(SemanticAttributes.HTTP_METHOD, method);
+              AttributeKey<String> httpMethodKey = getAttributeKey(SemanticAttributes.HTTP_METHOD);
+              if (httpClientAttributes.contains(httpMethodKey)) {
+                assertThat(attrs).containsEntry(httpMethodKey, method);
               }
               if (httpClientAttributes.contains(SemanticAttributes.USER_AGENT_ORIGINAL)) {
                 String userAgent = options.getUserAgent();
@@ -1014,24 +1041,27 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
                           });
                 }
               }
-              if (attrs.get(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH) != null) {
+              AttributeKey<Long> httpRequestLengthKey =
+                  getAttributeKey(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH);
+              if (attrs.get(httpRequestLengthKey) != null) {
                 assertThat(attrs)
                     .hasEntrySatisfying(
-                        SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH,
-                        length -> assertThat(length).isNotNegative());
+                        httpRequestLengthKey, length -> assertThat(length).isNotNegative());
               }
-              if (attrs.get(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH) != null) {
+              AttributeKey<Long> httpResponseLengthKey =
+                  getAttributeKey(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH);
+              if (attrs.get(httpResponseLengthKey) != null) {
                 assertThat(attrs)
                     .hasEntrySatisfying(
-                        SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
-                        length -> assertThat(length).isNotNegative());
+                        httpResponseLengthKey, length -> assertThat(length).isNotNegative());
               }
 
+              AttributeKey<Long> httpResponseStatusKey =
+                  getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE);
               if (responseCode != null) {
-                assertThat(attrs)
-                    .containsEntry(SemanticAttributes.HTTP_STATUS_CODE, (long) responseCode);
+                assertThat(attrs).containsEntry(httpResponseStatusKey, (long) responseCode);
               } else {
-                assertThat(attrs).doesNotContainKey(SemanticAttributes.HTTP_STATUS_CODE);
+                assertThat(attrs).doesNotContainKey(httpResponseStatusKey);
               }
 
               if (resendCount != null) {
@@ -1043,6 +1073,47 @@ public abstract class AbstractHttpClientTest<REQUEST> implements HttpClientTypeA
             });
   }
 
+  @SuppressWarnings("unchecked")
+  protected static <T> AttributeKey<T> getAttributeKey(AttributeKey<T> oldKey) {
+    if (SemconvStability.emitStableHttpSemconv()) {
+      if (SemanticAttributes.NET_PROTOCOL_NAME == oldKey) {
+        return (AttributeKey<T>) NetworkAttributes.NETWORK_PROTOCOL_NAME;
+      } else if (SemanticAttributes.NET_PROTOCOL_VERSION == oldKey) {
+        return (AttributeKey<T>) NetworkAttributes.NETWORK_PROTOCOL_VERSION;
+      } else if (SemanticAttributes.NET_PEER_NAME == oldKey) {
+        return (AttributeKey<T>) NetworkAttributes.SERVER_ADDRESS;
+      } else if (SemanticAttributes.NET_PEER_PORT == oldKey) {
+        return (AttributeKey<T>) NetworkAttributes.SERVER_PORT;
+      } else if (SemanticAttributes.NET_SOCK_PEER_ADDR == oldKey) {
+        return (AttributeKey<T>) NetworkAttributes.CLIENT_SOCKET_ADDRESS;
+      } else if (SemanticAttributes.NET_SOCK_PEER_PORT == oldKey) {
+        return (AttributeKey<T>) NetworkAttributes.CLIENT_SOCKET_PORT;
+      } else if (SemanticAttributes.HTTP_URL == oldKey) {
+        return (AttributeKey<T>) UrlAttributes.URL_FULL;
+      } else if (SemanticAttributes.HTTP_METHOD == oldKey) {
+        return (AttributeKey<T>) HttpAttributes.HTTP_REQUEST_METHOD;
+      } else if (SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH == oldKey) {
+        return (AttributeKey<T>) HttpAttributes.HTTP_REQUEST_BODY_SIZE;
+      } else if (SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH == oldKey) {
+        return (AttributeKey<T>) HttpAttributes.HTTP_RESPONSE_BODY_SIZE;
+      } else if (SemanticAttributes.HTTP_STATUS_CODE == oldKey) {
+        return (AttributeKey<T>) HttpAttributes.HTTP_RESPONSE_STATUS_CODE;
+      }
+    }
+    return oldKey;
+  }
+
+  private static Set<AttributeKey<?>> getAttributeKeys(Set<AttributeKey<?>> oldKeys) {
+    if (!SemconvStability.emitStableHttpSemconv()) {
+      return oldKeys;
+    }
+    Set<AttributeKey<?>> result = new HashSet<>();
+    for (AttributeKey<?> key : oldKeys) {
+      result.add(getAttributeKey(key));
+    }
+    return result;
+  }
+
   // Visible for spock bridge.
   static SpanDataAssert assertServerSpan(SpanDataAssert span) {
     return span.hasName("test-http-server").hasKind(SpanKind.SERVER);