Repository: open-telemetry/opentelemetry-js
Pull request #5665: refactor(instrumentation-http): Add back support for http semconv
Author: login: JamieDanielson
Created: 2025-05-08T23:50:17Z | Merged: 2025-05-12T20:26:01Z

## Which problem is this PR solving?

- Closes #5657
- Closes #4897 

## Short description of the changes

See #5646. This is re-implementing the support for dual-emit HTTP semantic conventions. I couldn't do a revert, because other changes have gone in since legacy attributes were removed. Much of this is copy-pasted though from the previous implementation, with some additional changes related to the new synthetic attributes, stable outgoing metrics, and shared utility for SemconvStability.

- Updated local semconv constants file to include the experimental semantic conventions in use
- Updated usages of unstable/experimental semantic conventions to use imports from local semconv package
- Updated README to include table of semantic convention attributes emitted, including details on the migration
- Added SemconvStability and set attributes accordingly
- Update changelog to try to capture the changes that happened with the package, including moving the previous entry to a refactor.

## Type of change

- [x] Sort of a revert, but sort of a re-implementation

## How Has This Been Tested?

Unit tests

## Checklist:

- [x] Followed the style guidelines of this project
- [x] Unit tests have been added
- [x] Documentation has been updated


Linked issue #5657: [instr-http] Add (back) dual-emit option for HTTP stable attributes
- Part of #4897 
- See #5646 

Open a new PR for instrumentation-http that re-implements the OTEL_SEMCONV_STABILITY_OPT_IN option for dual-emitting old and new(stable) http attributes.

Diff:
diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md
index 037cfe49227..e68a41321bc 100644
--- a/experimental/CHANGELOG.md
+++ b/experimental/CHANGELOG.md
@@ -8,8 +8,6 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
 
 ### :boom: Breaking Changes
 
-* feat(instrumentation-http)!: Remove legacy http span attributes and metrics [#5552](https://github.com/open-telemetry/opentelemetry-js/pull/5552) @svetlanabrennan
-
 ### :rocket: Features
 
 * feat(instrumentation-fetch): support migration to stable HTTP semconv, v1.23.1  [#5651](https://github.com/open-telemetry/opentelemetry-js/pull/5651) @trentm
@@ -29,6 +27,9 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
 
 ### :house: Internal
 
+* refactor(instrumentation-http): Remove legacy http span attributes and metrics [#5552](https://github.com/open-telemetry/opentelemetry-js/pull/5552) @svetlanabrennan
+* refactor(instrumentation-http): Add back support for http semconv [#5665](https://github.com/open-telemetry/opentelemetry-js/pull/5665) @JamieDanielson
+  * Note: We initially removed support for legacy http attributes and metrics, but then added back for an additional 6 months to ensure all instrumentations could be updated and kept consistent. There should be no net new change in this instrumentation related to these semantic conventions. See [#5646](https://github.com/open-telemetry/opentelemetry-js/issues/5646) for details.
 * refactor(sdk-node): update semconv usage to `ATTR_` exports [#5668](https://github.com/open-telemetry/opentelemetry-js/pull/5668) @trentm
 * chore(sdk-node): Refactored using `get*FromEnv` utility function instead of `process.env` for NodeSDK's resource detector setup. [#5582](https://github.com/open-telemetry/opentelemetry-js/pull/5582) @beeme1mr
 * chore(sdk-node): Refactored using `get*FromEnv` utility function instead of `process.env` for NodeSDK's logging setup. [#5563](https://github.com/open-telemetry/opentelemetry-js/issues/5563) @weyert
diff --git a/experimental/packages/opentelemetry-instrumentation-http/README.md b/experimental/packages/opentelemetry-instrumentation-http/README.md
index de73b2be106..fa330c47560 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/README.md
+++ b/experimental/packages/opentelemetry-instrumentation-http/README.md
@@ -51,34 +51,83 @@ See [examples/http](https://github.com/open-telemetry/opentelemetry-js/tree/main
 
 ### Http instrumentation Options
 
-Http instrumentation has few options available to choose from. You can set the following:
-
-| Options | Type | Description |
-| ------- | ---- | ----------- |
-| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
-| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#93) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
-| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L95) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
-| [`startIncomingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L97) | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest |
-| [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
-| `ignoreIncomingRequestHook` | `IgnoreIncomingRequestFunction` | Http instrumentation will not trace all incoming requests that matched with custom function |
-| `ignoreOutgoingRequestHook` | `IgnoreOutgoingRequestFunction` | Http instrumentation will not trace all outgoing requests that matched with custom function |
-| `disableOutgoingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests. |
-| `disableIncomingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests. |
-| [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
-| [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
-| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L107) | `object` | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
+Http instrumentation has a few [configuration options](https://github.com/open-telemetry/opentelemetry-js/blob/e1ec4026edae53a2dea3a9a604d6d21bb5e8d99f/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L60-L93) available to choose from.
+You can set the following:
+
+| Options                                 | Type                                       | Description                                                                                                                                                                                                                                                                                                                  |
+| --------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `applyCustomAttributesOnSpan`           | `HttpCustomAttributeFunction`              | Function for adding custom attributes                                                                                                                                                                                                                                                                                        |
+| `requestHook`                           | `HttpRequestCustomAttributeFunction`       | Function for adding custom attributes before request is handled                                                                                                                                                                                                                                                              |
+| `responseHook`                          | `HttpResponseCustomAttributeFunction`      | Function for adding custom attributes before response is handled                                                                                                                                                                                                                                                             |
+| `startIncomingSpanHook`                 | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest                                                                                                                                                                                                                                            |
+| `startOutgoingSpanHook`                 | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest                                                                                                                                                                                                                                            |
+| `ignoreIncomingRequestHook`             | `IgnoreIncomingRequestFunction`            | Http instrumentation will not trace all incoming requests that matched with custom function                                                                                                                                                                                                                                  |
+| `ignoreOutgoingRequestHook`             | `IgnoreOutgoingRequestFunction`            | Http instrumentation will not trace all outgoing requests that matched with custom function                                                                                                                                                                                                                                  |
+| `disableOutgoingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests.                                                                                                                                                                                     |
+| `disableIncomingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests.                                                                                                                                                                                     |
+| `serverName`                            | `string`                                   | The primary server name of the matched virtual host.                                                                                                                                                                                                                                                                         |
+| `requireParentforOutgoingSpans`         | Boolean                                    | Require that is a parent span to create new span for outgoing requests.                                                                                                                                                                                                                                                      |
+| `requireParentforIncomingSpans`         | Boolean                                    | Require that is a parent span to create new span for incoming requests.                                                                                                                                                                                                                                                      |
+| `headersToSpanAttributes`               | `object`                                   | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
 
 ## Semantic Conventions
 
-This package emits Semantic Convention [Version 1.27.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md).
-
-Follow all requirements and recommendations of HTTP Client and Server Semantic Conventions Version 1.27.0 for [spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md) and [metrics](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md), including all required and recommended attributes.
+Prior to version `0.54.0`, this instrumentation created spans targeting an experimental semantic convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
+
+HTTP semantic conventions (semconv) were stabilized in v1.23.0, and a [migration process](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md#http-semantic-convention-stability-migration) was defined.
+`instrumentation-http` versions 0.54.0 and later include support for migrating to stable HTTP semantic conventions, as described below.
+The intent is to provide an approximate 6 month time window for users of this instrumentation to migrate to the new HTTP semconv, after which a new minor version will use the *new* semconv by default and drop support for the old semconv.
+See the [HTTP semconv migration plan for OpenTelemetry JS instrumentations](https://github.com/open-telemetry/opentelemetry-js/issues/5646).
+
+To select which semconv version(s) is emitted from this instrumentation, use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable.
+
+- `http`: emit the new (stable) v1.23.0+ semantics
+- `http/dep`: emit **both** the old v1.7.0 and the new (stable) v1.23.0+ semantics
+- By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.7.0 semconv is used.
+
+### Attributes collected
+
+| v1.7.0 semconv                              | v1.23.0 semconv                     | Short Description                                                                                                                                                                 |
+| ------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `http.client_ip`                            | `client.address`                    | The IP address of the original client behind all proxies, if known                                                                                                                |
+| `http.flavor`                               | `network.protocol.version`          | Kind of HTTP protocol used                                                                                                                                                        |
+| `http.host`                                 | `server.address`                    | The value of the HTTP host header                                                                                                                                                 |
+| `http.method`                               | `http.request.method`               | HTTP request method                                                                                                                                                               |
+| `http.request_content_length`               | (opt-in, `headersToSpanAttributes`) | The size of the request payload body in bytes. For newer semconv, use the `headersToSpanAttributes:` option to capture this as `http.request.header.content_length`.              |
+| `http.request_content_length_uncompressed`  | (not included)                      | The size of the uncompressed request payload body after transport decoding. (In semconv v1.23.0 this is defined by `http.request.body.size`, which is experimental and opt-in.)   |
+| `http.response_content_length`              | (opt-in, `headersToSpanAttributes`) | The size of the response payload body in bytes. For newer semconv, use the `headersToSpanAttributes:` option to capture this as `http.response.header.content_length`.            |
+| `http.response_content_length_uncompressed` | (not included)                      | The size of the uncompressed response payload body after transport decoding. (In semconv v1.23.0 this is defined by `http.response.body.size`, which is experimental and opt-in.) |
+| `http.route`                                | no change                           | The matched route (path template).                                                                                                                                                |
+| `http.scheme`                               | `url.scheme`                        | The URI scheme identifying the used protocol                                                                                                                                      |
+| `http.server_name`                          | `server.address`                    | The primary server name of the matched virtual host                                                                                                                               |
+| `http.status_code`                          | `http.response.status_code`         | HTTP response status code                                                                                                                                                         |
+| `http.target`                               | `url.path` and `url.query`          | The URI path and query component                                                                                                                                                  |
+| `http.url`                                  | `url.full`                          | Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`                                                                                                    |
+| `http.user_agent`                           | `user_agent.original`               | Value of the HTTP User-Agent header sent by the client                                                                                                                            |
+| `net.host.ip`                               | `network.local.address`             | Like net.peer.ip but for the host IP. Useful in case of a multi-IP host                                                                                                           |
+| `net.host.name`                             | `server.address`                    | Local hostname or similar                                                                                                                                                         |
+| `net.host.port`                             | `server.port`                       | Like net.peer.port but for the host port                                                                                                                                          |
+| `net.peer.ip.`                              | `network.peer.address`              | Remote address of the peer (dotted decimal for IPv4 or RFC5952 for IPv6)                                                                                                          |
+| `net.peer.name`                             | `server.address`                    | Server domain name if available without reverse DNS lookup                                                                                                                        |
+| `net.peer.port`                             | `server.port`                       | Server port number                                                                                                                                                                |
+| `net.transport`                             | `network.transport`                 | Transport protocol used                                                                                                                                                           |
 
 Metrics Exported:
 
 - [`http.server.request.duration`](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md#metric-httpserverrequestduration)
 - [`http.client.request.duration`](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md#metric-httpclientrequestduration)
 
+### Upgrading Semantic Conventions
+
+When upgrading to the new semantic conventions, it is recommended to do so in the following order:
+
+1. Upgrade `@opentelemetry/instrumentation-http` to the latest version
+2. Set `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` to emit both old and new semantic conventions
+3. Modify alerts, dashboards, metrics, and other processes to expect the new semantic conventions
+4. Set `OTEL_SEMCONV_STABILITY_OPT_IN=http` to emit only the new semantic conventions
+
+This will cause both the old and new semantic conventions to be emitted during the transition period.
+
 ## Useful links
 
 - For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/enums/AttributeNames.ts b/experimental/packages/opentelemetry-instrumentation-http/src/enums/AttributeNames.ts
new file mode 100644
index 00000000000..f9b8be3c8ea
--- /dev/null
+++ b/experimental/packages/opentelemetry-instrumentation-http/src/enums/AttributeNames.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md
+ */
+export enum AttributeNames {
+  HTTP_ERROR_NAME = 'http.error_name',
+  HTTP_ERROR_MESSAGE = 'http.error_message',
+  HTTP_STATUS_TEXT = 'http.status_text',
+}
diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/http.ts b/experimental/packages/opentelemetry-instrumentation-http/src/http.ts
index 2104a77a2f1..1737fb11556 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/src/http.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/src/http.ts
@@ -47,14 +47,16 @@ import { VERSION } from './version';
 import {
   InstrumentationBase,
   InstrumentationNodeModuleDefinition,
+  SemconvStability,
+  httpSemconvStabilityFromStr,
   safeExecuteInTheMiddle,
 } from '@opentelemetry/instrumentation';
 import { errorMonitor } from 'events';
 import {
   ATTR_HTTP_REQUEST_METHOD,
   ATTR_HTTP_RESPONSE_STATUS_CODE,
-  ATTR_HTTP_ROUTE,
   ATTR_NETWORK_PROTOCOL_VERSION,
+  ATTR_HTTP_ROUTE,
   ATTR_SERVER_ADDRESS,
   ATTR_SERVER_PORT,
   ATTR_URL_SCHEME,
@@ -65,10 +67,14 @@ import {
   extractHostnameAndPort,
   getIncomingRequestAttributes,
   getIncomingRequestAttributesOnResponse,
+  getIncomingRequestMetricAttributes,
+  getIncomingRequestMetricAttributesOnResponse,
   getIncomingStableRequestMetricAttributesOnResponse,
   getOutgoingRequestAttributes,
   getOutgoingRequestAttributesOnResponse,
+  getOutgoingRequestMetricAttributes,
   getOutgoingRequestMetricAttributesOnResponse,
+  getOutgoingStableRequestMetricAttributesOnResponse,
   getRequestInfo,
   headerCapture,
   isValidOptionsType,
@@ -84,15 +90,38 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
   /** keep track on spans not ended */
   private readonly _spanNotEnded: WeakSet<Span> = new WeakSet<Span>();
   private _headerCapture;
+  declare private _oldHttpServerDurationHistogram: Histogram;
   declare private _stableHttpServerDurationHistogram: Histogram;
+  declare private _oldHttpClientDurationHistogram: Histogram;
   declare private _stableHttpClientDurationHistogram: Histogram;
 
+  private _semconvStability: SemconvStability = SemconvStability.OLD;
+
   constructor(config: HttpInstrumentationConfig = {}) {
     super('@opentelemetry/instrumentation-http', VERSION, config);
     this._headerCapture = this._createHeaderCapture();
+    this._semconvStability = httpSemconvStabilityFromStr(
+      process.env.OTEL_SEMCONV_STABILITY_OPT_IN
+    );
   }
 
   protected override _updateMetricInstruments() {
+    this._oldHttpServerDurationHistogram = this.meter.createHistogram(
+      'http.server.duration',
+      {
+        description: 'Measures the duration of inbound HTTP requests.',
+        unit: 'ms',
+        valueType: ValueType.DOUBLE,
+      }
+    );
+    this._oldHttpClientDurationHistogram = this.meter.createHistogram(
+      'http.client.duration',
+      {
+        description: 'Measures the duration of outbound HTTP requests.',
+        unit: 'ms',
+        valueType: ValueType.DOUBLE,
+      }
+    );
     this._stableHttpServerDurationHistogram = this.meter.createHistogram(
       METRIC_HTTP_SERVER_REQUEST_DURATION,
       {
@@ -125,22 +154,40 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
 
   private _recordServerDuration(
     durationMs: number,
+    oldAttributes: Attributes,
     stableAttributes: Attributes
   ) {
-    this._stableHttpServerDurationHistogram.record(
-      durationMs / 1000,
-      stableAttributes
-    );
+    if (this._semconvStability & SemconvStability.OLD) {
+      // old histogram is counted in MS
+      this._oldHttpServerDurationHistogram.record(durationMs, oldAttributes);
+    }
+
+    if (this._semconvStability & SemconvStability.STABLE) {
+      // stable histogram is counted in S
+      this._stableHttpServerDurationHistogram.record(
+        durationMs / 1000,
+        stableAttributes
+      );
+    }
   }
 
   private _recordClientDuration(
     durationMs: number,
+    oldAttributes: Attributes,
     stableAttributes: Attributes
   ) {
-    this._stableHttpClientDurationHistogram.record(
-      durationMs / 1000,
-      stableAttributes
-    );
+    if (this._semconvStability & SemconvStability.OLD) {
+      // old histogram is counted in MS
+      this._oldHttpClientDurationHistogram.record(durationMs, oldAttributes);
+    }
+
+    if (this._semconvStability & SemconvStability.STABLE) {
+      // stable histogram is counted in S
+      this._stableHttpClientDurationHistogram.record(
+        durationMs / 1000,
+        stableAttributes
+      );
+    }
   }
 
   override setConfig(config: HttpInstrumentationConfig = {}): void {
@@ -355,12 +402,14 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
    * @param request The original request object.
    * @param span representing the current operation
    * @param startTime representing the start time of the request to calculate duration in Metric
+   * @param oldMetricAttributes metric attributes for old semantic conventions
    * @param stableMetricAttributes metric attributes for new semantic conventions
    */
   private _traceClientRequest(
     request: http.ClientRequest,
     span: Span,
     startTime: HrTime,
+    oldMetricAttributes: Attributes,
     stableMetricAttributes: Attributes
   ): http.ClientRequest {
     if (this.getConfig().requestHook) {
@@ -384,12 +433,18 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
         if (request.listenerCount('response') <= 1) {
           response.resume();
         }
-        const responseAttributes =
-          getOutgoingRequestAttributesOnResponse(response);
+        const responseAttributes = getOutgoingRequestAttributesOnResponse(
+          response,
+          this._semconvStability
+        );
         span.setAttributes(responseAttributes);
+        oldMetricAttributes = Object.assign(
+          oldMetricAttributes,
+          getOutgoingRequestMetricAttributesOnResponse(responseAttributes)
+        );
         stableMetricAttributes = Object.assign(
           stableMetricAttributes,
-          getOutgoingRequestMetricAttributesOnResponse(responseAttributes)
+          getOutgoingStableRequestMetricAttributesOnResponse(responseAttributes)
         );
 
         if (this.getConfig().responseHook) {
@@ -442,6 +497,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
             span,
             SpanKind.CLIENT,
             startTime,
+            oldMetricAttributes,
             stableMetricAttributes
           );
         };
@@ -453,7 +509,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
             return;
           }
           responseFinished = true;
-          setSpanWithError(span, error);
+          setSpanWithError(span, error, this._semconvStability);
           span.setStatus({
             code: SpanStatusCode.ERROR,
             message: error.message,
@@ -462,6 +518,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
             span,
             SpanKind.CLIENT,
             startTime,
+            oldMetricAttributes,
             stableMetricAttributes
           );
         });
@@ -477,6 +534,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
         span,
         SpanKind.CLIENT,
         startTime,
+        oldMetricAttributes,
         stableMetricAttributes
       );
     });
@@ -486,12 +544,13 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
         return;
       }
       responseFinished = true;
-      setSpanWithError(span, error);
+      setSpanWithError(span, error, this._semconvStability);
 
       this._closeHttpSpan(
         span,
         SpanKind.CLIENT,
         startTime,
+        oldMetricAttributes,
         stableMetricAttributes
       );
     });
@@ -551,10 +610,12 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
         request,
         {
           component: component,
+          serverName: instrumentation.getConfig().serverName,
           hookAttributes: instrumentation._callStartSpanHook(
             request,
             instrumentation.getConfig().startIncomingSpanHook
           ),
+          semconvStability: instrumentation._semconvStability,
           enableSyntheticSourceDetection:
             instrumentation.getConfig().enableSyntheticSourceDetection || false,
         },
@@ -567,6 +628,8 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
       };
 
       const startTime = hrTime();
+      const oldMetricAttributes =
+        getIncomingRequestMetricAttributes(spanAttributes);
 
       // request method and url.scheme are both required span attributes
       const stableMetricAttributes: Attributes = {
@@ -615,6 +678,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
               request,
               response,
               span,
+              oldMetricAttributes,
               stableMetricAttributes,
               startTime
             );
@@ -623,6 +687,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
             hasError = true;
             instrumentation._onServerResponseError(
               span,
+              oldMetricAttributes,
               stableMetricAttributes,
               startTime,
               err
@@ -633,11 +698,16 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
             () => original.apply(this, [event, ...args]),
             error => {
               if (error) {
-                setSpanWithError(span, error);
+                setSpanWithError(
+                  span,
+                  error,
+                  instrumentation._semconvStability
+                );
                 instrumentation._closeHttpSpan(
                   span,
                   SpanKind.SERVER,
                   startTime,
+                  oldMetricAttributes,
                   stableMetricAttributes
                 );
                 throw error;
@@ -705,10 +775,13 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
             instrumentation.getConfig().startOutgoingSpanHook
           ),
         },
+        instrumentation._semconvStability,
         instrumentation.getConfig().enableSyntheticSourceDetection || false
       );
 
       const startTime = hrTime();
+      const oldMetricAttributes: Attributes =
+        getOutgoingRequestMetricAttributes(attributes);
 
       // request method, server address, and server port are both required span attributes
       const stableMetricAttributes: Attributes = {
@@ -770,12 +843,13 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
           },
           error => {
             if (error) {
-              setSpanWithError(span, error);
+              setSpanWithError(span, error, instrumentation._semconvStability);
 
               instrumentation._closeHttpSpan(
                 span,
                 SpanKind.CLIENT,
                 startTime,
+                oldMetricAttributes,
                 stableMetricAttributes
               );
               throw error;
@@ -791,6 +865,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
           request,
           span,
           startTime,
+          oldMetricAttributes,
           stableMetricAttributes
         );
       });
@@ -801,12 +876,18 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
     request: http.IncomingMessage,
     response: http.ServerResponse,
     span: Span,
+    oldMetricAttributes: Attributes,
     stableMetricAttributes: Attributes,
     startTime: HrTime
   ) {
     const attributes = getIncomingRequestAttributesOnResponse(
       request,
-      response
+      response,
+      this._semconvStability
+    );
+    oldMetricAttributes = Object.assign(
+      oldMetricAttributes,
+      getIncomingRequestMetricAttributesOnResponse(attributes)
     );
     stableMetricAttributes = Object.assign(
       stableMetricAttributes,
@@ -843,22 +924,25 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
       span,
       SpanKind.SERVER,
       startTime,
+      oldMetricAttributes,
       stableMetricAttributes
     );
   }
 
   private _onServerResponseError(
     span: Span,
+    oldMetricAttributes: Attributes,
     stableMetricAttributes: Attributes,
     startTime: HrTime,
     error: Err
   ) {
-    setSpanWithError(span, error);
+    setSpanWithError(span, error, this._semconvStability);
     // TODO get error attributes for metrics
     this._closeHttpSpan(
       span,
       SpanKind.SERVER,
       startTime,
+      oldMetricAttributes,
       stableMetricAttributes
     );
   }
@@ -895,6 +979,7 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
     span: Span,
     spanKind: SpanKind,
     startTime: HrTime,
+    oldMetricAttributes: Attributes,
     stableMetricAttributes: Attributes
   ) {
     if (!this._spanNotEnded.has(span)) {
@@ -907,9 +992,17 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
     // Record metrics
     const duration = hrTimeToMilliseconds(hrTimeDuration(startTime, hrTime()));
     if (spanKind === SpanKind.SERVER) {
-      this._recordServerDuration(duration, stableMetricAttributes);
+      this._recordServerDuration(
+        duration,
+        oldMetricAttributes,
+        stableMetricAttributes
+      );
     } else if (spanKind === SpanKind.CLIENT) {
-      this._recordClientDuration(duration, stableMetricAttributes);
+      this._recordClientDuration(
+        duration,
+        oldMetricAttributes,
+        stableMetricAttributes
+      );
     }
   }
 
diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/semconv.ts b/experimental/packages/opentelemetry-instrumentation-http/src/semconv.ts
index 5546fbcb4c0..38774846e12 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/src/semconv.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/src/semconv.ts
@@ -39,3 +39,253 @@ export const USER_AGENT_SYNTHETIC_TYPE_VALUE_BOT = 'bot' as const;
  * Enum value "test" for attribute {@link ATTR_USER_AGENT_SYNTHETIC_TYPE}.
  */
 export const USER_AGENT_SYNTHETIC_TYPE_VALUE_TEST = 'test' as const;
+
+/**
+ * Deprecated, use `client.address` instead.
+ *
+ * @example "83.164.160.102"
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `client.address`.
+ */
+export const ATTR_HTTP_CLIENT_IP = 'http.client_ip' as const;
+
+/**
+ * Deprecated, use `network.protocol.name` instead.
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `network.protocol.name`.
+ */
+export const ATTR_HTTP_FLAVOR = 'http.flavor' as const;
+
+/**
+ * Deprecated, use one of `server.address`, `client.address` or `http.request.header.host` instead, depending on the usage.
+ *
+ * @example www.example.org
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by one of `server.address`, `client.address` or `http.request.header.host`, depending on the usage.
+ */
+export const ATTR_HTTP_HOST = 'http.host' as const;
+
+/**
+ * Deprecated, use `http.request.method` instead.
+ *
+ * @example GET
+ * @example POST
+ * @example HEAD
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `http.request.method`.
+ */
+export const ATTR_HTTP_METHOD = 'http.method' as const;
+
+/**
+ * Deprecated, use `http.request.header.<key>` instead.
+ *
+ * @example 3495
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `http.request.header.<key>`.
+ */
+export const ATTR_HTTP_REQUEST_CONTENT_LENGTH =
+  'http.request_content_length' as const;
+
+/**
+ * Deprecated, use `http.request.body.size` instead.
+ *
+ * @example 5493
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `http.request.body.size`.
+ */
+export const ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED =
+  'http.request_content_length_uncompressed' as const;
+
+/**
+ * Deprecated, use `http.response.header.<key>` instead.
+ *
+ * @example 3495
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `http.response.header.<key>`.
+ */
+export const ATTR_HTTP_RESPONSE_CONTENT_LENGTH =
+  'http.response_content_length' as const;
+
+/**
+ * Deprecated, use `http.response.body.size` instead.
+ *
+ * @example 5493
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replace by `http.response.body.size`.
+ */
+export const ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED =
+  'http.response_content_length_uncompressed' as const;
+
+/**
+ * Deprecated, use `url.scheme` instead.
+ *
+ * @example http
+ * @example https
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `url.scheme` instead.
+ */
+export const ATTR_HTTP_SCHEME = 'http.scheme' as const;
+
+/**
+ * Deprecated, use `server.address` instead.
+ *
+ * @example example.com
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `server.address`.
+ */
+export const ATTR_HTTP_SERVER_NAME = 'http.server_name' as const;
+
+/**
+ * Deprecated, use `http.response.status_code` instead.
+ *
+ * @example 200
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `http.response.status_code`.
+ */
+export const ATTR_HTTP_STATUS_CODE = 'http.status_code' as const;
+
+/**
+ * Deprecated, use `url.path` and `url.query` instead.
+ *
+ * @example /search?q=OpenTelemetry#SemConv
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Split to `url.path` and `url.query.
+ */
+export const ATTR_HTTP_TARGET = 'http.target' as const;
+
+/**
+ * Deprecated, use `url.full` instead.
+ *
+ * @example https://www.foo.bar/search?q=OpenTelemetry#SemConv
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `url.full`.
+ */
+export const ATTR_HTTP_URL = 'http.url' as const;
+
+/**
+ * Deprecated, use `user_agent.original` instead.
+ *
+ * @example CERN-LineMode/2.15 libwww/2.17b3
+ * @example Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `user_agent.original`.
+ */
+export const ATTR_HTTP_USER_AGENT = 'http.user_agent' as const;
+
+/**
+ * Deprecated, use `network.local.address`.
+ *
+ * @example "192.168.0.1"
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `network.local.address`.
+ */
+export const ATTR_NET_HOST_IP = 'net.host.ip' as const;
+
+/**
+ * Deprecated, use `server.address`.
+ *
+ * @example example.com
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `server.address`.
+ */
+export const ATTR_NET_HOST_NAME = 'net.host.name' as const;
+
+/**
+ * Deprecated, use `server.port`.
+ *
+ * @example 8080
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `server.port`.
+ */
+export const ATTR_NET_HOST_PORT = 'net.host.port' as const;
+
+/**
+ * Deprecated, use `network.peer.address`.
+ *
+ * @example "127.0.0.1"
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `network.peer.address`.
+ */
+export const ATTR_NET_PEER_IP = 'net.peer.ip' as const;
+
+/**
+ * Deprecated, use `server.address` on client spans and `client.address` on server spans.
+ *
+ * @example example.com
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `server.address` on client spans and `client.address` on server spans.
+ */
+export const ATTR_NET_PEER_NAME = 'net.peer.name' as const;
+
+/**
+ * Deprecated, use `server.port` on client spans and `client.port` on server spans.
+ *
+ * @example 8080
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `server.port` on client spans and `client.port` on server spans.
+ */
+export const ATTR_NET_PEER_PORT = 'net.peer.port' as const;
+
+/**
+ * Deprecated, use `network.transport`.
+ *
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
+ *
+ * @deprecated Replaced by `network.transport`.
+ */
+export const ATTR_NET_TRANSPORT = 'net.transport' as const;
+
+/**
+ * Enum value "ip_tcp" for attribute {@link ATTR_NET_TRANSPORT}.
+ */
+export const NET_TRANSPORT_VALUE_IP_TCP = 'ip_tcp' as const;
+
+/**
+ * Enum value "ip_udp" for attribute {@link ATTR_NET_TRANSPORT}.
+ */
+export const NET_TRANSPORT_VALUE_IP_UDP = 'ip_udp' as const;
+
+/**
+ * Enum value "1.1" for attribute {@link ATTR_HTTP_FLAVOR}.
+ */
+export const HTTP_FLAVOR_VALUE_HTTP_1_1 = '1.1' as const;
diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/types.ts b/experimental/packages/opentelemetry-instrumentation-http/src/types.ts
index 3bc55debd42..4811c4a7500 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/src/types.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/src/types.ts
@@ -76,6 +76,8 @@ export interface HttpInstrumentationConfig extends InstrumentationConfig {
   startIncomingSpanHook?: StartIncomingSpanCustomAttributeFunction;
   /** Function for adding custom attributes before a span is started in outgoingRequest */
   startOutgoingSpanHook?: StartOutgoingSpanCustomAttributeFunction;
+  /** The primary server name of the matched virtual host. */
+  serverName?: string;
   /** Require parent to create span for outgoing requests */
   requireParentforOutgoingSpans?: boolean;
   /** Require parent to create span for incoming requests */
diff --git a/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts b/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts
index 6ef6b858dae..e92c9030224 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/src/utils.ts
@@ -40,6 +40,34 @@ import {
   ATTR_URL_SCHEME,
   ATTR_USER_AGENT_ORIGINAL,
 } from '@opentelemetry/semantic-conventions';
+import {
+  ATTR_HTTP_CLIENT_IP,
+  ATTR_HTTP_FLAVOR,
+  ATTR_HTTP_HOST,
+  ATTR_HTTP_METHOD,
+  ATTR_HTTP_REQUEST_CONTENT_LENGTH,
+  ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
+  ATTR_HTTP_RESPONSE_CONTENT_LENGTH,
+  ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
+  ATTR_HTTP_SCHEME,
+  ATTR_HTTP_SERVER_NAME,
+  ATTR_HTTP_STATUS_CODE,
+  ATTR_HTTP_TARGET,
+  ATTR_HTTP_URL,
+  ATTR_HTTP_USER_AGENT,
+  ATTR_NET_HOST_IP,
+  ATTR_NET_HOST_NAME,
+  ATTR_NET_HOST_PORT,
+  ATTR_NET_PEER_IP,
+  ATTR_NET_PEER_NAME,
+  ATTR_NET_PEER_PORT,
+  ATTR_NET_TRANSPORT,
+  NET_TRANSPORT_VALUE_IP_TCP,
+  NET_TRANSPORT_VALUE_IP_UDP,
+  ATTR_USER_AGENT_SYNTHETIC_TYPE,
+  USER_AGENT_SYNTHETIC_TYPE_VALUE_BOT,
+  USER_AGENT_SYNTHETIC_TYPE_VALUE_TEST,
+} from './semconv';
 import {
   IncomingHttpHeaders,
   IncomingMessage,
@@ -49,14 +77,11 @@ import {
   ServerResponse,
 } from 'http';
 import { getRPCMetadata, RPCType } from '@opentelemetry/core';
+import { SemconvStability } from '@opentelemetry/instrumentation';
 import * as url from 'url';
+import { AttributeNames } from './enums/AttributeNames';
 import { Err, IgnoreMatcher, ParsedRequestOptions } from './internal-types';
 import { SYNTHETIC_BOT_NAMES, SYNTHETIC_TEST_NAMES } from './internal-types';
-import {
-  ATTR_USER_AGENT_SYNTHETIC_TYPE,
-  USER_AGENT_SYNTHETIC_TYPE_VALUE_BOT,
-  USER_AGENT_SYNTHETIC_TYPE_VALUE_TEST,
-} from './semconv';
 import forwardedParse = require('forwarded-parse');
 
 /**
@@ -130,14 +155,78 @@ export const satisfiesPattern = (
  * Sets the span with the error passed in params
  * @param {Span} span the span that need to be set
  * @param {Error} error error that will be set to span
+ * @param {SemconvStability} semconvStability determines which semconv version to use
  */
-export const setSpanWithError = (span: Span, error: Err): void => {
+export const setSpanWithError = (
+  span: Span,
+  error: Err,
+  semconvStability: SemconvStability
+): void => {
   const message = error.message;
-  span.setAttribute(ATTR_ERROR_TYPE, error.name);
+
+  if (semconvStability & SemconvStability.OLD) {
+    span.setAttribute(AttributeNames.HTTP_ERROR_NAME, error.name);
+    span.setAttribute(AttributeNames.HTTP_ERROR_MESSAGE, message);
+  }
+
+  if (semconvStability & SemconvStability.STABLE) {
+    span.setAttribute(ATTR_ERROR_TYPE, error.name);
+  }
 
   span.setStatus({ code: SpanStatusCode.ERROR, message });
   span.recordException(error);
 };
+/**
+ * Adds attributes for request content-length and content-encoding HTTP headers
+ * @param { IncomingMessage } Request object whose headers will be analyzed
+ * @param { Attributes } Attributes object to be modified
+ */
+export const setRequestContentLengthAttribute = (
+  request: IncomingMessage,
+  attributes: Attributes
+): void => {
+  const length = getContentLength(request.headers);
+  if (length === null) return;
+
+  if (isCompressed(request.headers)) {
+    attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH] = length;
+  } else {
+    attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = length;
+  }
+};
+
+/**
+ * Adds attributes for response content-length and content-encoding HTTP headers
+ * @param { IncomingMessage } Response object whose headers will be analyzed
+ * @param { Attributes } Attributes object to be modified
+ *
+ * @deprecated this is for an older version of semconv. It is retained for compatibility using OTEL_SEMCONV_STABILITY_OPT_IN
+ */
+export const setResponseContentLengthAttribute = (
+  response: IncomingMessage,
+  attributes: Attributes
+): void => {
+  const length = getContentLength(response.headers);
+  if (length === null) return;
+
+  if (isCompressed(response.headers)) {
+    attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH] = length;
+  } else {
+    attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = length;
+  }
+};
+
+function getContentLength(
+  headers: OutgoingHttpHeaders | IncomingHttpHeaders
+): number | null {
+  const contentLengthHeader = headers['content-length'];
+  if (contentLengthHeader === undefined) return null;
+
+  const contentLength = parseInt(contentLengthHeader as string, 10);
+  if (isNaN(contentLength)) return null;
+
+  return contentLength;
+}
 
 export const isCompressed = (
   headers: OutgoingHttpHeaders | IncomingHttpHeaders
@@ -343,6 +432,7 @@ export const extractHostnameAndPort = (
  * Returns outgoing request attributes scoped to the options passed to the request
  * @param {ParsedRequestOptions} requestOptions the same options used to make the request
  * @param {{ component: string, hostname: string, hookAttributes?: Attributes }} options used to pass data needed to create attributes
+ * @param {SemconvStability} semconvStability determines which semconv version to use
  */
 export const getOutgoingRequestAttributes = (
   requestOptions: ParsedRequestOptions,
@@ -352,6 +442,7 @@ export const getOutgoingRequestAttributes = (
     port: string | number;
     hookAttributes?: Attributes;
   },
+  semconvStability: SemconvStability,
   enableSyntheticSourceDetection: boolean
 ): Attributes => {
   const hostname = options.hostname;
@@ -365,6 +456,15 @@ export const getOutgoingRequestAttributes = (
     headers,
     `${options.component}:`
   );
+
+  const oldAttributes: Attributes = {
+    [ATTR_HTTP_URL]: urlFull,
+    [ATTR_HTTP_METHOD]: method,
+    [ATTR_HTTP_TARGET]: requestOptions.path || '/',
+    [ATTR_NET_PEER_NAME]: hostname,
+    [ATTR_HTTP_HOST]: headers.host ?? `${hostname}:${port}`,
+  };
+
   const newAttributes: Attributes = {
     // Required attributes
     [ATTR_HTTP_REQUEST_METHOD]: normalizedMethod,
@@ -387,7 +487,50 @@ export const getOutgoingRequestAttributes = (
   if (enableSyntheticSourceDetection && userAgent) {
     newAttributes[ATTR_USER_AGENT_SYNTHETIC_TYPE] = getSyntheticType(userAgent);
   }
-  return Object.assign(newAttributes, options.hookAttributes);
+  if (userAgent !== undefined) {
+    oldAttributes[ATTR_HTTP_USER_AGENT] = userAgent;
+  }
+
+  switch (semconvStability) {
+    case SemconvStability.STABLE:
+      return Object.assign(newAttributes, options.hookAttributes);
+    case SemconvStability.OLD:
+      return Object.assign(oldAttributes, options.hookAttributes);
+  }
+
+  return Object.assign(oldAttributes, newAttributes, options.hookAttributes);
+};
+
+/**
+ * Returns outgoing request Metric attributes scoped to the request data
+ * @param {Attributes} spanAttributes the span attributes
+ */
+export const getOutgoingRequestMetricAttributes = (
+  spanAttributes: Attributes
+): Attributes => {
+  const metricAttributes: Attributes = {};
+  metricAttributes[ATTR_HTTP_METHOD] = spanAttributes[ATTR_HTTP_METHOD];
+  metricAttributes[ATTR_NET_PEER_NAME] = spanAttributes[ATTR_NET_PEER_NAME];
+  //TODO: http.url attribute, it should substitute any parameters to avoid high cardinality.
+  return metricAttributes;
+};
+
+/**
+ * Returns attributes related to the kind of HTTP protocol used
+ * @param {string} [kind] Kind of HTTP protocol used: "1.0", "1.1", "2", "SPDY" or "QUIC".
+ */
+export const setAttributesFromHttpKind = (
+  kind: string | undefined,
+  attributes: Attributes
+): void => {
+  if (kind) {
+    attributes[ATTR_HTTP_FLAVOR] = kind;
+    if (kind.toUpperCase() !== 'QUIC') {
+      attributes[ATTR_NET_TRANSPORT] = NET_TRANSPORT_VALUE_IP_TCP;
+    } else {
+      attributes[ATTR_NET_TRANSPORT] = NET_TRANSPORT_VALUE_IP_UDP;
+    }
+  }
 };
 
 /**
@@ -414,11 +557,14 @@ const getSyntheticType = (
 /**
  * Returns outgoing request attributes scoped to the response data
  * @param {IncomingMessage} response the response object
+ * @param {SemconvStability} semconvStability determines which semconv version to use
  */
 export const getOutgoingRequestAttributesOnResponse = (
-  response: IncomingMessage
+  response: IncomingMessage,
+  semconvStability: SemconvStability
 ): Attributes => {
-  const { statusCode, socket } = response;
+  const { statusCode, statusMessage, httpVersion, socket } = response;
+  const oldAttributes: Attributes = {};
   const stableAttributes: Attributes = {};
 
   if (statusCode != null) {
@@ -427,14 +573,33 @@ export const getOutgoingRequestAttributesOnResponse = (
 
   if (socket) {
     const { remoteAddress, remotePort } = socket;
+    oldAttributes[ATTR_NET_PEER_IP] = remoteAddress;
+    oldAttributes[ATTR_NET_PEER_PORT] = remotePort;
 
     // Recommended
     stableAttributes[ATTR_NETWORK_PEER_ADDRESS] = remoteAddress;
     stableAttributes[ATTR_NETWORK_PEER_PORT] = remotePort;
     stableAttributes[ATTR_NETWORK_PROTOCOL_VERSION] = response.httpVersion;
   }
+  setResponseContentLengthAttribute(response, oldAttributes);
 
-  return stableAttributes;
+  if (statusCode) {
+    oldAttributes[ATTR_HTTP_STATUS_CODE] = statusCode;
+    oldAttributes[AttributeNames.HTTP_STATUS_TEXT] = (
+      statusMessage || ''
+    ).toUpperCase();
+  }
+
+  setAttributesFromHttpKind(httpVersion, oldAttributes);
+
+  switch (semconvStability) {
+    case SemconvStability.STABLE:
+      return stableAttributes;
+    case SemconvStability.OLD:
+      return oldAttributes;
+  }
+
+  return Object.assign(oldAttributes, stableAttributes);
 };
 
 /**
@@ -444,19 +609,30 @@ export const getOutgoingRequestAttributesOnResponse = (
 export const getOutgoingRequestMetricAttributesOnResponse = (
   spanAttributes: Attributes
 ): Attributes => {
-  const stableAttributes: Attributes = {};
+  const metricAttributes: Attributes = {};
+  metricAttributes[ATTR_NET_PEER_PORT] = spanAttributes[ATTR_NET_PEER_PORT];
+  metricAttributes[ATTR_HTTP_STATUS_CODE] =
+    spanAttributes[ATTR_HTTP_STATUS_CODE];
+  metricAttributes[ATTR_HTTP_FLAVOR] = spanAttributes[ATTR_HTTP_FLAVOR];
+
+  return metricAttributes;
+};
+
+export const getOutgoingStableRequestMetricAttributesOnResponse = (
+  spanAttributes: Attributes
+): Attributes => {
+  const metricAttributes: Attributes = {};
 
   if (spanAttributes[ATTR_NETWORK_PROTOCOL_VERSION]) {
-    stableAttributes[ATTR_NETWORK_PROTOCOL_VERSION] =
+    metricAttributes[ATTR_NETWORK_PROTOCOL_VERSION] =
       spanAttributes[ATTR_NETWORK_PROTOCOL_VERSION];
   }
 
   if (spanAttributes[ATTR_HTTP_RESPONSE_STATUS_CODE]) {
-    stableAttributes[ATTR_HTTP_RESPONSE_STATUS_CODE] =
+    metricAttributes[ATTR_HTTP_RESPONSE_STATUS_CODE] =
       spanAttributes[ATTR_HTTP_RESPONSE_STATUS_CODE];
   }
-
-  return stableAttributes;
+  return metricAttributes;
 };
 
 function parseHostHeader(
@@ -648,23 +824,32 @@ function getInfoFromIncomingMessage(
 /**
  * Returns incoming request attributes scoped to the request data
  * @param {IncomingMessage} request the request object
- * @param {{ component: string, hookAttributes?: Attributes }} options used to pass data needed to create attributes
+ * @param {{ component: string, serverName?: string, hookAttributes?: Attributes }} options used to pass data needed to create attributes
+ * @param {SemconvStability} semconvStability determines which semconv version to use
  */
 export const getIncomingRequestAttributes = (
   request: IncomingMessage,
   options: {
     component: 'http' | 'https';
+    serverName?: string;
     hookAttributes?: Attributes;
+    semconvStability: SemconvStability;
     enableSyntheticSourceDetection: boolean;
   },
   logger: DiagLogger
 ): Attributes => {
   const headers = request.headers;
   const userAgent = headers['user-agent'];
+  const ips = headers['x-forwarded-for'];
+  const httpVersion = request.httpVersion;
+  const host = headers.host;
+  const hostname = host?.replace(/^(.*)(:[0-9]{1,5})/, '$1') || 'localhost';
+
   const method = request.method;
   const normalizedMethod = normalizeMethod(method);
 
   const serverAddress = getServerAddress(request, options.component);
+  const serverName = options.serverName;
   const remoteClientAddress = getRemoteClientAddress(request);
 
   const newAttributes: Attributes = {
@@ -708,7 +893,58 @@ export const getIncomingRequestAttributes = (
   if (options.enableSyntheticSourceDetection && userAgent) {
     newAttributes[ATTR_USER_AGENT_SYNTHETIC_TYPE] = getSyntheticType(userAgent);
   }
-  return Object.assign(newAttributes, options.hookAttributes);
+  const oldAttributes: Attributes = {
+    [ATTR_HTTP_URL]: parsedUrl.toString(),
+    [ATTR_HTTP_HOST]: host,
+    [ATTR_NET_HOST_NAME]: hostname,
+    [ATTR_HTTP_METHOD]: method,
+    [ATTR_HTTP_SCHEME]: options.component,
+  };
+
+  if (typeof ips === 'string') {
+    oldAttributes[ATTR_HTTP_CLIENT_IP] = ips.split(',')[0];
+  }
+
+  if (typeof serverName === 'string') {
+    oldAttributes[ATTR_HTTP_SERVER_NAME] = serverName;
+  }
+
+  if (parsedUrl?.pathname) {
+    oldAttributes[ATTR_HTTP_TARGET] =
+      parsedUrl?.pathname + parsedUrl?.search || '/';
+  }
+
+  if (userAgent !== undefined) {
+    oldAttributes[ATTR_HTTP_USER_AGENT] = userAgent;
+  }
+  setRequestContentLengthAttribute(request, oldAttributes);
+  setAttributesFromHttpKind(httpVersion, oldAttributes);
+
+  switch (options.semconvStability) {
+    case SemconvStability.STABLE:
+      return Object.assign(newAttributes, options.hookAttributes);
+    case SemconvStability.OLD:
+      return Object.assign(oldAttributes, options.hookAttributes);
+  }
+
+  return Object.assign(oldAttributes, newAttributes, options.hookAttributes);
+};
+
+/**
+ * Returns incoming request Metric attributes scoped to the request data
+ * @param {Attributes} spanAttributes the span attributes
+ * @param {{ component: string }} options used to pass data needed to create attributes
+ */
+export const getIncomingRequestMetricAttributes = (
+  spanAttributes: Attributes
+): Attributes => {
+  const metricAttributes: Attributes = {};
+  metricAttributes[ATTR_HTTP_SCHEME] = spanAttributes[ATTR_HTTP_SCHEME];
+  metricAttributes[ATTR_HTTP_METHOD] = spanAttributes[ATTR_HTTP_METHOD];
+  metricAttributes[ATTR_NET_HOST_NAME] = spanAttributes[ATTR_NET_HOST_NAME];
+  metricAttributes[ATTR_HTTP_FLAVOR] = spanAttributes[ATTR_HTTP_FLAVOR];
+  //TODO: http.target attribute, it should substitute any parameters to avoid high cardinality.
+  return metricAttributes;
 };
 
 /**
@@ -717,27 +953,68 @@ export const getIncomingRequestAttributes = (
  */
 export const getIncomingRequestAttributesOnResponse = (
   request: IncomingMessage,
-  response: ServerResponse
+  response: ServerResponse,
+  semconvStability: SemconvStability
 ): Attributes => {
-  const { statusCode } = response;
+  // take socket from the request,
+  // since it may be detached from the response object in keep-alive mode
+  const { socket } = request;
+  const { statusCode, statusMessage } = response;
 
   const newAttributes: Attributes = {
     [ATTR_HTTP_RESPONSE_STATUS_CODE]: statusCode,
   };
 
   const rpcMetadata = getRPCMetadata(context.active());
+  const oldAttributes: Attributes = {};
+  if (socket) {
+    const { localAddress, localPort, remoteAddress, remotePort } = socket;
+    oldAttributes[ATTR_NET_HOST_IP] = localAddress;
+    oldAttributes[ATTR_NET_HOST_PORT] = localPort;
+    oldAttributes[ATTR_NET_PEER_IP] = remoteAddress;
+    oldAttributes[ATTR_NET_PEER_PORT] = remotePort;
+  }
+  oldAttributes[ATTR_HTTP_STATUS_CODE] = statusCode;
+  oldAttributes[AttributeNames.HTTP_STATUS_TEXT] = (
+    statusMessage || ''
+  ).toUpperCase();
 
   if (rpcMetadata?.type === RPCType.HTTP && rpcMetadata.route !== undefined) {
+    oldAttributes[ATTR_HTTP_ROUTE] = rpcMetadata.route;
     newAttributes[ATTR_HTTP_ROUTE] = rpcMetadata.route;
   }
 
-  return newAttributes;
+  switch (semconvStability) {
+    case SemconvStability.STABLE:
+      return newAttributes;
+    case SemconvStability.OLD:
+      return oldAttributes;
+  }
+
+  return Object.assign(oldAttributes, newAttributes);
 };
 
 /**
  * Returns incoming request Metric attributes scoped to the request data
  * @param {Attributes} spanAttributes the span attributes
  */
+export const getIncomingRequestMetricAttributesOnResponse = (
+  spanAttributes: Attributes
+): Attributes => {
+  const metricAttributes: Attributes = {};
+  metricAttributes[ATTR_HTTP_STATUS_CODE] =
+    spanAttributes[ATTR_HTTP_STATUS_CODE];
+  metricAttributes[ATTR_NET_HOST_PORT] = spanAttributes[ATTR_NET_HOST_PORT];
+  if (spanAttributes[ATTR_HTTP_ROUTE] !== undefined) {
+    metricAttributes[ATTR_HTTP_ROUTE] = spanAttributes[ATTR_HTTP_ROUTE];
+  }
+  return metricAttributes;
+};
+
+/**
+ * Returns incoming stable request Metric attributes scoped to the request data
+ * @param {Attributes} spanAttributes the span attributes
+ */
 export const getIncomingStableRequestMetricAttributesOnResponse = (
   spanAttributes: Attributes
 ): Attributes => {
diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts
index ef5c5ee5526..32614f12cac 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-enable.test.ts
@@ -36,7 +36,6 @@ import {
   ATTR_HTTP_ROUTE,
   ATTR_NETWORK_PEER_ADDRESS,
   ATTR_NETWORK_PEER_PORT,
-  ATTR_NETWORK_PROTOCOL_NAME,
   ATTR_NETWORK_PROTOCOL_VERSION,
   ATTR_SERVER_ADDRESS,
   ATTR_SERVER_PORT,
@@ -45,6 +44,25 @@ import {
   ATTR_URL_SCHEME,
   HTTP_REQUEST_METHOD_VALUE_GET,
 } from '@opentelemetry/semantic-conventions';
+import {
+  ATTR_HTTP_CLIENT_IP,
+  ATTR_HTTP_FLAVOR,
+  ATTR_HTTP_HOST,
+  ATTR_HTTP_METHOD,
+  ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
+  ATTR_HTTP_SCHEME,
+  ATTR_HTTP_STATUS_CODE,
+  ATTR_HTTP_TARGET,
+  ATTR_HTTP_URL,
+  ATTR_NET_HOST_IP,
+  ATTR_NET_HOST_NAME,
+  ATTR_NET_HOST_PORT,
+  ATTR_NET_PEER_IP,
+  ATTR_NET_PEER_NAME,
+  ATTR_NET_PEER_PORT,
+  ATTR_NET_TRANSPORT,
+  NET_TRANSPORT_VALUE_IP_TCP,
+} from '../../src/semconv';
 import * as assert from 'assert';
 import * as nock from 'nock';
 import * as path from 'path';
@@ -61,7 +79,7 @@ import type {
   ServerResponse,
   RequestOptions,
 } from 'http';
-import { isWrapped } from '@opentelemetry/instrumentation';
+import { isWrapped, SemconvStability } from '@opentelemetry/instrumentation';
 import { getRPCMetadata, RPCType } from '@opentelemetry/core';
 
 const instrumentation = new HttpInstrumentation();
@@ -69,6 +87,7 @@ instrumentation.enable();
 instrumentation.disable();
 
 import * as http from 'http';
+import { AttributeNames } from '../../src/enums/AttributeNames';
 import { getRemoteClientAddress } from '../../src/utils';
 
 const applyCustomAttributesOnSpanErrorMessage =
@@ -79,6 +98,7 @@ const serverPort = 22346;
 const protocol = 'http';
 const hostname = 'localhost';
 const pathname = '/test';
+const serverName = 'my.server.name';
 const memoryExporter = new InMemorySpanExporter();
 const provider = new NodeTracerProvider({
   spanProcessors: [new SimpleSpanProcessor(memoryExporter)],
@@ -214,14 +234,33 @@ describe('HttpInstrumentation', () => {
         assertSpan(incomingSpan, SpanKind.SERVER, validations);
         assertSpan(outgoingSpan, SpanKind.CLIENT, validations);
         assert.strictEqual(
-          incomingSpan.attributes[ATTR_SERVER_PORT],
+          incomingSpan.attributes[ATTR_NET_HOST_PORT],
           serverPort
         );
         assert.strictEqual(
-          outgoingSpan.attributes[ATTR_SERVER_PORT],
+          outgoingSpan.attributes[ATTR_NET_PEER_PORT],
           serverPort
         );
       });
+
+      it('should remove auth from the `http.url` attribute (client side and server side)', async () => {
+        await httpRequest.get(
+          `${protocol}://user:pass@${hostname}:${serverPort}${pathname}`
+        );
+        const spans = memoryExporter.getFinishedSpans();
+        const [incomingSpan, outgoingSpan] = spans;
+        assert.strictEqual(spans.length, 2);
+        assert.strictEqual(incomingSpan.kind, SpanKind.SERVER);
+        assert.strictEqual(outgoingSpan.kind, SpanKind.CLIENT);
+        assert.strictEqual(
+          incomingSpan.attributes[ATTR_HTTP_URL],
+          `${protocol}://${hostname}:${serverPort}${pathname}`
+        );
+        assert.strictEqual(
+          outgoingSpan.attributes[ATTR_HTTP_URL],
+          `${protocol}://${hostname}:${serverPort}${pathname}`
+        );
+      });
     });
 
     describe('partially disable instrumentation', () => {
@@ -299,6 +338,7 @@ describe('HttpInstrumentation', () => {
           responseHook: responseHookFunction,
           startIncomingSpanHook: startIncomingSpanHookFunction,
           startOutgoingSpanHook: startOutgoingSpanHookFunction,
+          serverName,
         });
         instrumentation.enable();
         server = http.createServer((request, response) => {
@@ -374,28 +414,30 @@ describe('HttpInstrumentation', () => {
           resHeaders: result.resHeaders,
           reqHeaders: result.reqHeaders,
           component: 'http',
+          serverName,
         };
 
         assert.strictEqual(spans.length, 2);
         assert.strictEqual(
-          incomingSpan.attributes[ATTR_CLIENT_ADDRESS],
+          incomingSpan.attributes[ATTR_HTTP_CLIENT_IP],
           '<client>'
         );
         assert.strictEqual(
-          incomingSpan.attributes[ATTR_SERVER_PORT],
+          incomingSpan.attributes[ATTR_NET_HOST_PORT],
           serverPort
         );
         assert.strictEqual(
-          outgoingSpan.attributes[ATTR_SERVER_PORT],
+          outgoingSpan.attributes[ATTR_NET_PEER_PORT],
           serverPort
         );
         [
           { span: incomingSpan, kind: SpanKind.SERVER },
           { span: outgoingSpan, kind: SpanKind.CLIENT },
         ].forEach(({ span, kind }) => {
-          assert.ok(
-            !span.attributes[ATTR_NETWORK_PROTOCOL_NAME],
-            'should not be added for HTTP kind'
+          assert.strictEqual(span.attributes[ATTR_HTTP_FLAVOR], '1.1');
+          assert.strictEqual(
+            span.attributes[ATTR_NET_TRANSPORT],
+            NET_TRANSPORT_VALUE_IP_TCP
           );
           assertSpan(span, kind, validations);
         });
@@ -826,10 +868,7 @@ describe('HttpInstrumentation', () => {
             const [span] = spans;
             assert.strictEqual(spans.length, 1);
             assert.ok(Object.keys(span.attributes).length > 6);
-            assert.strictEqual(
-              span.attributes[ATTR_HTTP_RESPONSE_STATUS_CODE],
-              404
-            );
+            assert.strictEqual(span.attributes[ATTR_HTTP_STATUS_CODE], 404);
             assert.strictEqual(span.status.code, SpanStatusCode.ERROR);
             done();
           });
@@ -1025,13 +1064,14 @@ describe('HttpInstrumentation', () => {
       });
     });
 
-    describe('with semconv stability', () => {
+    describe('with semconv stability set to http', () => {
       beforeEach(() => {
         memoryExporter.reset();
       });
 
       before(async () => {
         instrumentation.setConfig({});
+        instrumentation['_semconvStability'] = SemconvStability.STABLE;
         instrumentation.enable();
         server = http.createServer((request, response) => {
           if (request.url?.includes('/premature-close')) {
@@ -1163,6 +1203,164 @@ describe('HttpInstrumentation', () => {
       });
     });
 
+    describe('with semconv stability set to http/dup', () => {
+      beforeEach(() => {
+        memoryExporter.reset();
+        instrumentation.setConfig({});
+      });
+
+      before(async () => {
+        instrumentation['_semconvStability'] = SemconvStability.DUPLICATE;
+        instrumentation.enable();
+        server = http.createServer((request, response) => {
+          if (request.url?.includes('/setroute')) {
+            const rpcData = getRPCMetadata(context.active());
+            assert.ok(rpcData != null);
+            assert.strictEqual(rpcData.type, RPCType.HTTP);
+            assert.strictEqual(rpcData.route, undefined);
+            rpcData.route = 'TheRoute';
+          }
+          response.setHeader('Content-Type', 'application/json');
+          response.end(
+            JSON.stringify({ address: getRemoteClientAddress(request) })
+          );
+        });
+
+        await new Promise<void>(resolve => server.listen(serverPort, resolve));
+      });
+
+      after(() => {
+        server.close();
+        instrumentation.disable();
+      });
+
+      it('should create client spans with semconv 1.27 and old 1.7', async () => {
+        const response = await httpRequest.get(
+          `${protocol}://${hostname}:${serverPort}${pathname}`
+        );
+        const spans = memoryExporter.getFinishedSpans();
+        assert.strictEqual(spans.length, 2);
+        const outgoingSpan = spans[1];
+
+        // should have only required and recommended attributes for semconv 1.27
+        assert.deepStrictEqual(outgoingSpan.attributes, {
+          // 1.27 attributes
+          [ATTR_HTTP_REQUEST_METHOD]: HTTP_REQUEST_METHOD_VALUE_GET,
+          [ATTR_SERVER_ADDRESS]: hostname,
+          [ATTR_SERVER_PORT]: serverPort,
+          [ATTR_URL_FULL]: `http://${hostname}:${serverPort}${pathname}`,
+          [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
+          [ATTR_NETWORK_PEER_ADDRESS]: response.address,
+          [ATTR_NETWORK_PEER_PORT]: serverPort,
+          [ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
+
+          // 1.7 attributes
+          [ATTR_HTTP_FLAVOR]: '1.1',
+          [ATTR_HTTP_HOST]: `${hostname}:${serverPort}`,
+          [ATTR_HTTP_METHOD]: 'GET',
+          [ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED]:
+            response.data.length,
+          [ATTR_HTTP_STATUS_CODE]: 200,
+          [ATTR_HTTP_TARGET]: '/test',
+          [ATTR_HTTP_URL]: `http://${hostname}:${serverPort}${pathname}`,
+          [ATTR_NET_PEER_IP]: response.address,
+          [ATTR_NET_PEER_NAME]: hostname,
+          [ATTR_NET_PEER_PORT]: serverPort,
+          [ATTR_NET_TRANSPORT]: 'ip_tcp',
+
+          // unspecified old names
+          [AttributeNames.HTTP_STATUS_TEXT]: 'OK',
+        });
+      });
+
+      it('should create server spans with semconv 1.27 and old 1.7', async () => {
+        const response = await httpRequest.get(
+          `${protocol}://${hostname}:${serverPort}${pathname}`
+        );
+        const spans = memoryExporter.getFinishedSpans();
+        assert.strictEqual(spans.length, 2);
+        const incomingSpan = spans[0];
+        const body = JSON.parse(response.data);
+
+        // should have only required and recommended attributes for semconv 1.27
+        assert.deepStrictEqual(incomingSpan.attributes, {
+          // 1.27 attributes
+          [ATTR_CLIENT_ADDRESS]: body.address,
+          [ATTR_HTTP_REQUEST_METHOD]: HTTP_REQUEST_METHOD_VALUE_GET,
+          [ATTR_SERVER_ADDRESS]: hostname,
+          [ATTR_SERVER_PORT]: serverPort,
+          [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
+          [ATTR_NETWORK_PEER_ADDRESS]: body.address,
+          [ATTR_NETWORK_PEER_PORT]: response.clientRemotePort,
+          [ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
+          [ATTR_URL_PATH]: pathname,
+          [ATTR_URL_SCHEME]: protocol,
+
+          // 1.7 attributes
+          [ATTR_HTTP_FLAVOR]: '1.1',
+          [ATTR_HTTP_HOST]: `${hostname}:${serverPort}`,
+          [ATTR_HTTP_METHOD]: 'GET',
+          [ATTR_HTTP_SCHEME]: protocol,
+          [ATTR_HTTP_STATUS_CODE]: 200,
+          [ATTR_HTTP_TARGET]: '/test',
+          [ATTR_HTTP_URL]: `http://${hostname}:${serverPort}${pathname}`,
+          [ATTR_NET_TRANSPORT]: 'ip_tcp',
+          [ATTR_NET_HOST_IP]: body.address,
+          [ATTR_NET_HOST_NAME]: hostname,
+          [ATTR_NET_HOST_PORT]: serverPort,
+          [ATTR_NET_PEER_IP]: body.address,
+          [ATTR_NET_PEER_PORT]: response.clientRemotePort,
+
+          // unspecified old names
+          [AttributeNames.HTTP_STATUS_TEXT]: 'OK',
+        });
+      });
+
+      it('should create server spans with semconv 1.27 and old 1.7 including http.route if RPC metadata is available', async () => {
+        const response = await httpRequest.get(
+          `${protocol}://${hostname}:${serverPort}${pathname}/setroute`
+        );
+        const spans = memoryExporter.getFinishedSpans();
+        assert.strictEqual(spans.length, 2);
+        const incomingSpan = spans[0];
+        const body = JSON.parse(response.data);
+
+        // should have only required and recommended attributes for semconv 1.27
+        assert.deepStrictEqual(incomingSpan.attributes, {
+          // 1.27 attributes
+          [ATTR_CLIENT_ADDRESS]: body.address,
+          [ATTR_HTTP_REQUEST_METHOD]: HTTP_REQUEST_METHOD_VALUE_GET,
+          [ATTR_SERVER_ADDRESS]: hostname,
+          [ATTR_SERVER_PORT]: serverPort,
+          [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
+          [ATTR_NETWORK_PEER_ADDRESS]: body.address,
+          [ATTR_NETWORK_PEER_PORT]: response.clientRemotePort,
+          [ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
+          [ATTR_URL_PATH]: `${pathname}/setroute`,
+          [ATTR_URL_SCHEME]: protocol,
+          [ATTR_HTTP_ROUTE]: 'TheRoute',
+
+          // 1.7 attributes
+          [ATTR_HTTP_FLAVOR]: '1.1',
+          [ATTR_HTTP_HOST]: `${hostname}:${serverPort}`,
+          [ATTR_HTTP_METHOD]: 'GET',
+          [ATTR_HTTP_SCHEME]: protocol,
+          [ATTR_HTTP_STATUS_CODE]: 200,
+          [ATTR_HTTP_TARGET]: `${pathname}/setroute`,
+          [ATTR_HTTP_URL]: `http://${hostname}:${serverPort}${pathname}/setroute`,
+          [ATTR_NET_TRANSPORT]: 'ip_tcp',
+          [ATTR_NET_HOST_IP]: body.address,
+          [ATTR_NET_HOST_NAME]: hostname,
+          [ATTR_NET_HOST_PORT]: serverPort,
+          [ATTR_NET_PEER_IP]: body.address,
+          [ATTR_NET_PEER_PORT]: response.clientRemotePort,
+
+          // unspecified old names
+          [AttributeNames.HTTP_STATUS_TEXT]: 'OK',
+        });
+      });
+    });
+
     describe('with require parent span', () => {
       beforeEach(done => {
         memoryExporter.reset();
diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-metrics.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-metrics.test.ts
index e076d791214..97d774f0b26 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-metrics.test.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/http-metrics.test.ts
@@ -29,11 +29,22 @@ import {
   ATTR_SERVER_PORT,
   ATTR_URL_SCHEME,
 } from '@opentelemetry/semantic-conventions';
+import {
+  ATTR_HTTP_FLAVOR,
+  ATTR_HTTP_METHOD,
+  ATTR_HTTP_SCHEME,
+  ATTR_HTTP_STATUS_CODE,
+  ATTR_NET_HOST_NAME,
+  ATTR_NET_HOST_PORT,
+  ATTR_NET_PEER_NAME,
+  ATTR_NET_PEER_PORT,
+} from '../../src/semconv';
 import * as assert from 'assert';
 import { HttpInstrumentation } from '../../src/http';
 import { httpRequest } from '../utils/httpRequest';
 import { TestMetricReader } from '../utils/TestMetricReader';
 import { context, ContextManager } from '@opentelemetry/api';
+import { SemconvStability } from '@opentelemetry/instrumentation';
 
 const instrumentation = new HttpInstrumentation();
 instrumentation.enable();
@@ -86,8 +97,97 @@ describe('metrics', () => {
     server.close();
     instrumentation.disable();
   });
+  describe('with no stability set', () => {
+    it('should add server/client duration metrics', async () => {
+      const requestCount = 3;
+      for (let i = 0; i < requestCount; i++) {
+        await httpRequest.get(
+          `${protocol}://${hostname}:${serverPort}${pathname}`
+        );
+      }
+      await metricReader.collectAndExport();
+      const resourceMetrics = metricsMemoryExporter.getMetrics();
+      const scopeMetrics = resourceMetrics[0].scopeMetrics;
+      assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
+      const metrics = scopeMetrics[0].metrics;
+      assert.strictEqual(metrics.length, 2, 'metrics count');
+      assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
+      assert.strictEqual(
+        metrics[0].descriptor.description,
+        'Measures the duration of inbound HTTP requests.'
+      );
+      assert.strictEqual(metrics[0].descriptor.name, 'http.server.duration');
+      assert.strictEqual(metrics[0].descriptor.unit, 'ms');
+      assert.strictEqual(metrics[0].dataPoints.length, 1);
+      assert.strictEqual(
+        (metrics[0].dataPoints[0].value as any).count,
+        requestCount
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_SCHEME],
+        'http'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_METHOD],
+        'GET'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_FLAVOR],
+        '1.1'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_NET_HOST_NAME],
+        'localhost'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_STATUS_CODE],
+        200
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_NET_HOST_PORT],
+        22346
+      );
+
+      assert.strictEqual(metrics[1].dataPointType, DataPointType.HISTOGRAM);
+      assert.strictEqual(
+        metrics[1].descriptor.description,
+        'Measures the duration of outbound HTTP requests.'
+      );
+      assert.strictEqual(metrics[1].descriptor.name, 'http.client.duration');
+      assert.strictEqual(metrics[1].descriptor.unit, 'ms');
+      assert.strictEqual(metrics[1].dataPoints.length, 1);
+      assert.strictEqual(
+        (metrics[1].dataPoints[0].value as any).count,
+        requestCount
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_HTTP_METHOD],
+        'GET'
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_NET_PEER_NAME],
+        'localhost'
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_NET_PEER_PORT],
+        22346
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_HTTP_STATUS_CODE],
+        200
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_HTTP_FLAVOR],
+        '1.1'
+      );
+    });
+  });
+
+  describe('with semconv stability set to stable', () => {
+    before(() => {
+      instrumentation['_semconvStability'] = SemconvStability.STABLE;
+    });
 
-  describe('using semconv stability attributes', () => {
     it('should add server/client duration metrics', async () => {
       const requestCount = 3;
       for (let i = 0; i < requestCount; i++) {
@@ -149,4 +249,144 @@ describe('metrics', () => {
       });
     });
   });
+
+  describe('with semconv stability set to duplicate', () => {
+    before(() => {
+      instrumentation['_semconvStability'] = SemconvStability.DUPLICATE;
+    });
+
+    it('should add server/client duration metrics', async () => {
+      const requestCount = 3;
+      for (let i = 0; i < requestCount; i++) {
+        await httpRequest.get(
+          `${protocol}://${hostname}:${serverPort}${pathname}`
+        );
+      }
+      await metricReader.collectAndExport();
+      const resourceMetrics = metricsMemoryExporter.getMetrics();
+      const scopeMetrics = resourceMetrics[0].scopeMetrics;
+      assert.strictEqual(scopeMetrics.length, 1, 'scopeMetrics count');
+      const metrics = scopeMetrics[0].metrics;
+      assert.strictEqual(metrics.length, 4, 'metrics count');
+
+      // old metrics
+      assert.strictEqual(metrics[0].dataPointType, DataPointType.HISTOGRAM);
+      assert.strictEqual(
+        metrics[0].descriptor.description,
+        'Measures the duration of inbound HTTP requests.'
+      );
+      assert.strictEqual(metrics[0].descriptor.name, 'http.server.duration');
+      assert.strictEqual(metrics[0].descriptor.unit, 'ms');
+      assert.strictEqual(metrics[0].dataPoints.length, 1);
+      assert.strictEqual(
+        (metrics[0].dataPoints[0].value as any).count,
+        requestCount
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_SCHEME],
+        'http'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_METHOD],
+        'GET'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_FLAVOR],
+        '1.1'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_NET_HOST_NAME],
+        'localhost'
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_HTTP_STATUS_CODE],
+        200
+      );
+      assert.strictEqual(
+        metrics[0].dataPoints[0].attributes[ATTR_NET_HOST_PORT],
+        22346
+      );
+
+      assert.strictEqual(metrics[1].dataPointType, DataPointType.HISTOGRAM);
+      assert.strictEqual(
+        metrics[1].descriptor.description,
+        'Measures the duration of outbound HTTP requests.'
+      );
+      assert.strictEqual(metrics[1].descriptor.name, 'http.client.duration');
+      assert.strictEqual(metrics[1].descriptor.unit, 'ms');
+      assert.strictEqual(metrics[1].dataPoints.length, 1);
+      assert.strictEqual(
+        (metrics[1].dataPoints[0].value as any).count,
+        requestCount
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_HTTP_METHOD],
+        'GET'
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_NET_PEER_NAME],
+        'localhost'
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_NET_PEER_PORT],
+        22346
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_HTTP_STATUS_CODE],
+        200
+      );
+      assert.strictEqual(
+        metrics[1].dataPoints[0].attributes[ATTR_HTTP_FLAVOR],
+        '1.1'
+      );
+
+      // Stable metrics
+      assert.strictEqual(metrics[2].dataPointType, DataPointType.HISTOGRAM);
+      assert.strictEqual(
+        metrics[2].descriptor.description,
+        'Duration of HTTP server requests.'
+      );
+      assert.strictEqual(
+        metrics[2].descriptor.name,
+        'http.server.request.duration'
+      );
+      assert.strictEqual(metrics[2].descriptor.unit, 's');
+      assert.strictEqual(metrics[2].dataPoints.length, 1);
+      assert.strictEqual(
+        (metrics[2].dataPoints[0].value as any).count,
+        requestCount
+      );
+      assert.deepStrictEqual(metrics[2].dataPoints[0].attributes, {
+        [ATTR_HTTP_REQUEST_METHOD]: 'GET',
+        [ATTR_URL_SCHEME]: 'http',
+        [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
+        [ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
+        [ATTR_HTTP_ROUTE]: 'TheRoute',
+      });
+
+      assert.strictEqual(metrics[3].dataPointType, DataPointType.HISTOGRAM);
+      assert.strictEqual(
+        metrics[3].descriptor.description,
+        'Duration of HTTP client requests.'
+      );
+      assert.strictEqual(
+        metrics[3].descriptor.name,
+        'http.client.request.duration'
+      );
+      assert.strictEqual(metrics[3].descriptor.unit, 's');
+      assert.strictEqual(metrics[3].dataPoints.length, 1);
+      assert.strictEqual(
+        (metrics[3].dataPoints[0].value as any).count,
+        requestCount
+      );
+
+      assert.deepStrictEqual(metrics[3].dataPoints[0].attributes, {
+        [ATTR_HTTP_REQUEST_METHOD]: 'GET',
+        [ATTR_SERVER_ADDRESS]: 'localhost',
+        [ATTR_SERVER_PORT]: 22346,
+        [ATTR_NETWORK_PROTOCOL_VERSION]: '1.1',
+        [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
+      });
+    });
+  });
 });
diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts
index 1f5e5c72e15..7079253efa3 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/https-enable.test.ts
@@ -30,11 +30,14 @@ import {
   SimpleSpanProcessor,
 } from '@opentelemetry/sdk-trace-base';
 import {
-  ATTR_CLIENT_ADDRESS,
-  ATTR_HTTP_RESPONSE_STATUS_CODE,
-  ATTR_SERVER_PORT,
-  ATTR_NETWORK_PROTOCOL_NAME,
-} from '@opentelemetry/semantic-conventions';
+  ATTR_HTTP_CLIENT_IP,
+  ATTR_HTTP_FLAVOR,
+  ATTR_HTTP_STATUS_CODE,
+  ATTR_NET_HOST_PORT,
+  ATTR_NET_PEER_PORT,
+  ATTR_NET_TRANSPORT,
+  NET_TRANSPORT_VALUE_IP_TCP,
+} from '../../src/semconv';
 import * as assert from 'assert';
 import * as fs from 'fs';
 import * as nock from 'nock';
@@ -59,6 +62,7 @@ let server: https.Server;
 const serverPort = 32345;
 const protocol = 'https';
 const hostname = 'localhost';
+const serverName = 'my.server.name';
 const pathname = '/test';
 const memoryExporter = new InMemorySpanExporter();
 const provider = new BasicTracerProvider({
@@ -162,11 +166,11 @@ describe('HttpsInstrumentation', () => {
         assertSpan(incomingSpan, SpanKind.SERVER, validations);
         assertSpan(outgoingSpan, SpanKind.CLIENT, validations);
         assert.strictEqual(
-          incomingSpan.attributes[ATTR_SERVER_PORT],
+          incomingSpan.attributes[ATTR_NET_HOST_PORT],
           serverPort
         );
         assert.strictEqual(
-          outgoingSpan.attributes[ATTR_SERVER_PORT],
+          outgoingSpan.attributes[ATTR_NET_PEER_PORT],
           serverPort
         );
       });
@@ -194,6 +198,7 @@ describe('HttpsInstrumentation', () => {
             return false;
           },
           applyCustomAttributesOnSpan: customAttributeFunction,
+          serverName,
         });
         instrumentation.enable();
         server = https.createServer(
@@ -241,19 +246,20 @@ describe('HttpsInstrumentation', () => {
           resHeaders: result.resHeaders,
           reqHeaders: result.reqHeaders,
           component: 'https',
+          serverName,
         };
 
         assert.strictEqual(spans.length, 2);
         assert.strictEqual(
-          incomingSpan.attributes[ATTR_CLIENT_ADDRESS],
+          incomingSpan.attributes[ATTR_HTTP_CLIENT_IP],
           '<client>'
         );
         assert.strictEqual(
-          incomingSpan.attributes[ATTR_SERVER_PORT],
+          incomingSpan.attributes[ATTR_NET_HOST_PORT],
           serverPort
         );
         assert.strictEqual(
-          outgoingSpan.attributes[ATTR_SERVER_PORT],
+          outgoingSpan.attributes[ATTR_NET_PEER_PORT],
           serverPort
         );
 
@@ -261,9 +267,10 @@ describe('HttpsInstrumentation', () => {
           { span: incomingSpan, kind: SpanKind.SERVER },
           { span: outgoingSpan, kind: SpanKind.CLIENT },
         ].forEach(({ span, kind }) => {
-          assert.ok(
-            !span.attributes[ATTR_NETWORK_PROTOCOL_NAME],
-            'should not be added for HTTP kind'
+          assert.strictEqual(span.attributes[ATTR_HTTP_FLAVOR], '1.1');
+          assert.strictEqual(
+            span.attributes[ATTR_NET_TRANSPORT],
+            NET_TRANSPORT_VALUE_IP_TCP
           );
           assertSpan(span, kind, validations);
         });
@@ -606,7 +613,7 @@ describe('HttpsInstrumentation', () => {
         const [span] = spans;
         assert.strictEqual(spans.length, 1);
         assert.strictEqual(span.status.code, SpanStatusCode.ERROR);
-        assert.ok(Object.keys(span.attributes).length >= 5);
+        assert.ok(Object.keys(span.attributes).length >= 6);
       });
 
       it('should have 1 ended span when request is aborted after receiving response', async () => {
@@ -659,10 +666,7 @@ describe('HttpsInstrumentation', () => {
             const [span] = spans;
             assert.strictEqual(spans.length, 1);
             assert.ok(Object.keys(span.attributes).length > 6);
-            assert.strictEqual(
-              span.attributes[ATTR_HTTP_RESPONSE_STATUS_CODE],
-              404
-            );
+            assert.strictEqual(span.attributes[ATTR_HTTP_STATUS_CODE], 404);
             assert.strictEqual(span.status.code, SpanStatusCode.ERROR);
             done();
           });
diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts
index 2e653aafb64..39658adbda9 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts
@@ -22,7 +22,6 @@ import {
   diag,
 } from '@opentelemetry/api';
 import {
-  ATTR_ERROR_TYPE,
   ATTR_HTTP_ROUTE,
   ATTR_USER_AGENT_ORIGINAL,
 } from '@opentelemetry/semantic-conventions';
@@ -32,6 +31,11 @@ import { Socket } from 'net';
 import * as sinon from 'sinon';
 import * as url from 'url';
 import {
+  ATTR_HTTP_REQUEST_CONTENT_LENGTH,
+  ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
+  ATTR_HTTP_RESPONSE_CONTENT_LENGTH,
+  ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
+  ATTR_HTTP_TARGET,
   ATTR_USER_AGENT_SYNTHETIC_TYPE,
   USER_AGENT_SYNTHETIC_TYPE_VALUE_BOT,
 } from '../../src/semconv';
@@ -39,7 +43,9 @@ import { IgnoreMatcher, ParsedRequestOptions } from '../../src/internal-types';
 import * as utils from '../../src/utils';
 import { RPCType, setRPCMetadata } from '@opentelemetry/core';
 import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
+import { SemconvStability } from '@opentelemetry/instrumentation';
 import { extractHostnameAndPort } from '../../src/utils';
+import { AttributeNames } from '../../src/enums/AttributeNames';
 
 describe('Utility', () => {
   describe('parseResponseStatus()', () => {
@@ -180,15 +186,19 @@ describe('Utility', () => {
         recordException: () => undefined,
       } as unknown as Span;
       const mock = sinon.mock(span);
-
-      mock.expects('setAttribute').calledWithExactly(ATTR_ERROR_TYPE, 'Error');
+      mock
+        .expects('setAttribute')
+        .calledWithExactly(AttributeNames.HTTP_ERROR_NAME, 'error');
+      mock
+        .expects('setAttribute')
+        .calledWithExactly(AttributeNames.HTTP_ERROR_MESSAGE, errorMessage);
       mock.expects('setStatus').calledWithExactly({
         code: SpanStatusCode.ERROR,
         message: errorMessage,
       });
       mock.expects('recordException').calledWithExactly(error);
 
-      utils.setSpanWithError(span, error);
+      utils.setSpanWithError(span, error, SemconvStability.OLD);
       mock.verify();
     });
   });
@@ -225,7 +235,8 @@ describe('Utility', () => {
         () => {
           const attributes = utils.getIncomingRequestAttributesOnResponse(
             request,
-            {} as ServerResponse
+            {} as ServerResponse,
+            SemconvStability.OLD
           );
           assert.deepStrictEqual(attributes[ATTR_HTTP_ROUTE], '/user/:id');
           context.disable();
@@ -238,22 +249,24 @@ describe('Utility', () => {
       const request = {
         socket: {},
       } as IncomingMessage;
-      const attributes = utils.getIncomingRequestAttributesOnResponse(request, {
-        socket: {},
-      } as ServerResponse & { socket: Socket });
+      const attributes = utils.getIncomingRequestAttributesOnResponse(
+        request,
+        {
+          socket: {},
+        } as ServerResponse & { socket: Socket },
+        SemconvStability.OLD
+      );
       assert.deepEqual(attributes[ATTR_HTTP_ROUTE], undefined);
     });
   });
 
-  describe('getIncomingStableRequestMetricAttributesOnResponse()', () => {
+  describe('getIncomingRequestMetricAttributesOnResponse()', () => {
     it('should correctly add http_route if span has it', () => {
       const spanAttributes: Attributes = {
         [ATTR_HTTP_ROUTE]: '/user/:id',
       };
       const metricAttributes =
-        utils.getIncomingStableRequestMetricAttributesOnResponse(
-          spanAttributes
-        );
+        utils.getIncomingRequestMetricAttributesOnResponse(spanAttributes);
 
       assert.deepStrictEqual(metricAttributes[ATTR_HTTP_ROUTE], '/user/:id');
     });
@@ -261,13 +274,153 @@ describe('Utility', () => {
     it('should skip http_route if span does not have it', () => {
       const spanAttributes: Attributes = {};
       const metricAttributes =
-        utils.getIncomingStableRequestMetricAttributesOnResponse(
-          spanAttributes
-        );
+        utils.getIncomingRequestMetricAttributesOnResponse(spanAttributes);
       assert.deepEqual(metricAttributes[ATTR_HTTP_ROUTE], undefined);
     });
   });
 
+  // Verify the key in the given attributes is set to the given value,
+  // and that no other HTTP Content Length attributes are set.
+  function verifyValueInAttributes(
+    attributes: Attributes,
+    key: string | undefined,
+    value: number
+  ) {
+    const SemanticAttributess = [
+      ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
+      ATTR_HTTP_RESPONSE_CONTENT_LENGTH,
+      ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
+      ATTR_HTTP_REQUEST_CONTENT_LENGTH,
+    ];
+
+    for (const attr of SemanticAttributess) {
+      if (attr === key) {
+        assert.strictEqual(attributes[attr], value);
+      } else {
+        assert.strictEqual(attributes[attr], undefined);
+      }
+    }
+  }
+
+  describe('setRequestContentLengthAttributes()', () => {
+    it('should set request content-length uncompressed attribute with no content-encoding header', () => {
+      const attributes: Attributes = {};
+      const request = {} as IncomingMessage;
+
+      request.headers = {
+        'content-length': '1200',
+      };
+      utils.setRequestContentLengthAttribute(request, attributes);
+
+      verifyValueInAttributes(
+        attributes,
+        ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
+        1200
+      );
+    });
+
+    it('should set request content-length uncompressed attribute with "identity" content-encoding header', () => {
+      const attributes: Attributes = {};
+      const request = {} as IncomingMessage;
+      request.headers = {
+        'content-length': '1200',
+        'content-encoding': 'identity',
+      };
+      utils.setRequestContentLengthAttribute(request, attributes);
+
+      verifyValueInAttributes(
+        attributes,
+        ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
+        1200
+      );
+    });
+
+    it('should set request content-length compressed attribute with "gzip" content-encoding header', () => {
+      const attributes: Attributes = {};
+      const request = {} as IncomingMessage;
+      request.headers = {
+        'content-length': '1200',
+        'content-encoding': 'gzip',
+      };
+      utils.setRequestContentLengthAttribute(request, attributes);
+
+      verifyValueInAttributes(
+        attributes,
+        ATTR_HTTP_REQUEST_CONTENT_LENGTH,
+        1200
+      );
+    });
+  });
+
+  describe('setResponseContentLengthAttributes()', () => {
+    it('should set response content-length uncompressed attribute with no content-encoding header', () => {
+      const attributes: Attributes = {};
+
+      const response = {} as IncomingMessage;
+
+      response.headers = {
+        'content-length': '1200',
+      };
+      utils.setResponseContentLengthAttribute(response, attributes);
+
+      verifyValueInAttributes(
+        attributes,
+        ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
+        1200
+      );
+    });
+
+    it('should set response content-length uncompressed attribute with "identity" content-encoding header', () => {
+      const attributes: Attributes = {};
+
+      const response = {} as IncomingMessage;
+
+      response.headers = {
+        'content-length': '1200',
+        'content-encoding': 'identity',
+      };
+
+      utils.setResponseContentLengthAttribute(response, attributes);
+
+      verifyValueInAttributes(
+        attributes,
+        ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
+        1200
+      );
+    });
+
+    it('should set response content-length compressed attribute with "gzip" content-encoding header', () => {
+      const attributes: Attributes = {};
+
+      const response = {} as IncomingMessage;
+
+      response.headers = {
+        'content-length': '1200',
+        'content-encoding': 'gzip',
+      };
+
+      utils.setResponseContentLengthAttribute(response, attributes);
+
+      verifyValueInAttributes(
+        attributes,
+        ATTR_HTTP_RESPONSE_CONTENT_LENGTH,
+        1200
+      );
+    });
+
+    it('should set no attributes with no content-length header', () => {
+      const attributes: Attributes = {};
+      const message = {} as IncomingMessage;
+
+      message.headers = {
+        'content-encoding': 'gzip',
+      };
+      utils.setResponseContentLengthAttribute(message, attributes);
+
+      verifyValueInAttributes(attributes, undefined, 1200);
+    });
+  });
+
   describe('getIncomingRequestAttributes()', () => {
     it('should not set http.route in http span attributes', () => {
       const request = {
@@ -283,6 +436,7 @@ describe('Utility', () => {
         request,
         {
           component: 'http',
+          semconvStability: SemconvStability.OLD,
           enableSyntheticSourceDetection: false,
         },
         diag
@@ -303,10 +457,12 @@ describe('Utility', () => {
         request,
         {
           component: 'http',
+          semconvStability: SemconvStability.OLD,
           enableSyntheticSourceDetection: false,
         },
         diag
       );
+      assert.strictEqual(attributes[ATTR_HTTP_TARGET], '/user/?q=val');
       assert.strictEqual(attributes[ATTR_USER_AGENT_SYNTHETIC_TYPE], undefined);
     });
 
@@ -323,6 +479,7 @@ describe('Utility', () => {
         request,
         {
           component: 'http',
+          semconvStability: SemconvStability.STABLE,
           enableSyntheticSourceDetection: true,
         },
         diag
diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/integrations/http-enable.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/integrations/http-enable.test.ts
index c322f07ed29..aa126d9186d 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/test/integrations/http-enable.test.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/test/integrations/http-enable.test.ts
@@ -15,7 +15,13 @@
  */
 
 import { SpanKind, Span, context, propagation } from '@opentelemetry/api';
-import { ATTR_NETWORK_PROTOCOL_NAME } from '@opentelemetry/semantic-conventions';
+import {
+  ATTR_HTTP_FLAVOR,
+  ATTR_HTTP_HOST,
+  ATTR_NET_TRANSPORT,
+  HTTP_FLAVOR_VALUE_HTTP_1_1,
+  NET_TRANSPORT_VALUE_IP_TCP,
+} from '../../src/semconv';
 import * as assert from 'assert';
 import * as url from 'url';
 import { HttpInstrumentation } from '../../src/http';
@@ -224,9 +230,13 @@ describe('HttpInstrumentation Integration tests', () => {
       assert.strictEqual(spans.length, 2);
       assert.strictEqual(span.name, 'GET');
       assert.strictEqual(result.reqHeaders['x-foo'], 'foo');
-      assert.ok(
-        !span.attributes[ATTR_NETWORK_PROTOCOL_NAME],
-        'should not be added for HTTP kind'
+      assert.strictEqual(
+        span.attributes[ATTR_HTTP_FLAVOR],
+        HTTP_FLAVOR_VALUE_HTTP_1_1
+      );
+      assert.strictEqual(
+        span.attributes[ATTR_NET_TRANSPORT],
+        NET_TRANSPORT_VALUE_IP_TCP
       );
       assertSpan(span, SpanKind.CLIENT, validations);
     });
@@ -395,6 +405,10 @@ describe('HttpInstrumentation Integration tests', () => {
       const span = spans.find(s => s.kind === SpanKind.CLIENT);
       assert.ok(span);
       assert.strictEqual(span.name, 'GET');
+      assert.strictEqual(
+        span.attributes[ATTR_HTTP_HOST],
+        `localhost:${mockServerPort}`
+      );
     });
   });
 });
diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/integrations/https-enable.test.ts b/experimental/packages/opentelemetry-instrumentation-http/test/integrations/https-enable.test.ts
index eec9e8563ea..c4e5f2f8020 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/test/integrations/https-enable.test.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/test/integrations/https-enable.test.ts
@@ -15,7 +15,12 @@
  */
 
 import { SpanKind, Span, context, propagation } from '@opentelemetry/api';
-import { ATTR_NETWORK_PROTOCOL_NAME } from '@opentelemetry/semantic-conventions';
+import {
+  HTTP_FLAVOR_VALUE_HTTP_1_1,
+  NET_TRANSPORT_VALUE_IP_TCP,
+  ATTR_HTTP_FLAVOR,
+  ATTR_NET_TRANSPORT,
+} from '../../src/semconv';
 import * as assert from 'assert';
 import * as http from 'http';
 import * as fs from 'fs';
@@ -160,6 +165,8 @@ describe('HttpsInstrumentation Integration tests', () => {
         hostname: 'localhost',
         httpStatusCode: result.statusCode!,
         httpMethod: 'GET',
+        pathname: '/',
+        path: '/?query=test',
         resHeaders: result.resHeaders,
         reqHeaders: result.reqHeaders,
         component: 'https',
@@ -185,6 +192,8 @@ describe('HttpsInstrumentation Integration tests', () => {
         hostname: 'localhost',
         httpStatusCode: result.statusCode!,
         httpMethod: 'GET',
+        pathname: '/',
+        path: '/?query=test',
         resHeaders: result.resHeaders,
         reqHeaders: result.reqHeaders,
         component: 'https',
@@ -213,6 +222,8 @@ describe('HttpsInstrumentation Integration tests', () => {
         hostname: 'localhost',
         httpStatusCode: result.statusCode!,
         httpMethod: 'GET',
+        pathname: '/',
+        path: '/?query=test',
         resHeaders: result.resHeaders,
         reqHeaders: result.reqHeaders,
         component: 'https',
@@ -221,9 +232,13 @@ describe('HttpsInstrumentation Integration tests', () => {
       assert.strictEqual(spans.length, 2);
       assert.strictEqual(span.name, 'GET');
       assert.strictEqual(result.reqHeaders['x-foo'], 'foo');
-      assert.ok(
-        !span.attributes[ATTR_NETWORK_PROTOCOL_NAME],
-        'should not be added for HTTP kind'
+      assert.strictEqual(
+        span.attributes[ATTR_HTTP_FLAVOR],
+        HTTP_FLAVOR_VALUE_HTTP_1_1
+      );
+      assert.strictEqual(
+        span.attributes[ATTR_NET_TRANSPORT],
+        NET_TRANSPORT_VALUE_IP_TCP
       );
       assertSpan(span, SpanKind.CLIENT, validations);
     });
@@ -239,6 +254,7 @@ describe('HttpsInstrumentation Integration tests', () => {
         hostname: 'localhost',
         httpStatusCode: result.statusCode!,
         httpMethod: 'GET',
+        pathname: '/',
         resHeaders: result.resHeaders,
         reqHeaders: result.reqHeaders,
         component: 'https',
@@ -266,6 +282,7 @@ describe('HttpsInstrumentation Integration tests', () => {
         hostname: 'localhost',
         httpStatusCode: 200,
         httpMethod: 'GET',
+        pathname: '/',
         resHeaders: result.resHeaders,
         reqHeaders: result.reqHeaders,
         component: 'https',
diff --git a/experimental/packages/opentelemetry-instrumentation-http/test/utils/assertSpan.ts b/experimental/packages/opentelemetry-instrumentation-http/test/utils/assertSpan.ts
index 0f27f0bc9db..330ec36a135 100644
--- a/experimental/packages/opentelemetry-instrumentation-http/test/utils/assertSpan.ts
+++ b/experimental/packages/opentelemetry-instrumentation-http/test/utils/assertSpan.ts
@@ -22,20 +22,28 @@ import {
 import { hrTimeToNanoseconds } from '@opentelemetry/core';
 import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
 import {
-  ATTR_HTTP_REQUEST_METHOD,
-  ATTR_HTTP_RESPONSE_STATUS_CODE,
-  ATTR_NETWORK_PEER_ADDRESS,
-  ATTR_SERVER_ADDRESS,
-  ATTR_SERVER_PORT,
-  ATTR_URL_FULL,
-  ATTR_URL_SCHEME,
-  ATTR_USER_AGENT_ORIGINAL,
-  ATTR_URL_PATH,
-} from '@opentelemetry/semantic-conventions';
+  ATTR_HTTP_METHOD,
+  ATTR_HTTP_REQUEST_CONTENT_LENGTH,
+  ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED,
+  ATTR_HTTP_RESPONSE_CONTENT_LENGTH,
+  ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED,
+  ATTR_HTTP_SCHEME,
+  ATTR_HTTP_SERVER_NAME,
+  ATTR_HTTP_STATUS_CODE,
+  ATTR_HTTP_TARGET,
+  ATTR_HTTP_URL,
+  ATTR_HTTP_USER_AGENT,
+  ATTR_NET_HOST_IP,
+  ATTR_NET_HOST_PORT,
+  ATTR_NET_PEER_IP,
+  ATTR_NET_PEER_NAME,
+  ATTR_NET_PEER_PORT,
+} from '../../src/semconv';
 import * as assert from 'assert';
 import * as http from 'http';
 import * as utils from '../../src/utils';
 import { DummyPropagation } from './DummyPropagation';
+import { AttributeNames } from '../../src/enums/AttributeNames';
 
 export const assertSpan = (
   span: ReadableSpan,
@@ -45,10 +53,11 @@ export const assertSpan = (
     httpMethod: string;
     resHeaders: http.IncomingHttpHeaders;
     hostname: string;
-    pathname?: string;
+    pathname: string;
     reqHeaders?: http.OutgoingHttpHeaders;
     path?: string | null;
     forceStatus?: SpanStatus;
+    serverName?: string;
     component: string;
     noNetPeer?: boolean; // we don't expect net peer info when request throw before being sent
     error?: Exception;
@@ -60,12 +69,17 @@ export const assertSpan = (
   assert.strictEqual(span.name, validations.httpMethod);
 
   assert.strictEqual(
-    span.attributes[ATTR_HTTP_REQUEST_METHOD],
-    validations.httpMethod
+    span.attributes[AttributeNames.HTTP_ERROR_MESSAGE],
+    span.status.message
   );
+  assert.strictEqual(span.attributes[ATTR_HTTP_METHOD], validations.httpMethod);
 
   assert.strictEqual(
-    span.attributes[ATTR_HTTP_RESPONSE_STATUS_CODE],
+    span.attributes[ATTR_HTTP_TARGET],
+    validations.path || validations.pathname
+  );
+  assert.strictEqual(
+    span.attributes[ATTR_HTTP_STATUS_CODE],
     validations.httpStatusCode
   );
 
@@ -96,49 +110,83 @@ export const assertSpan = (
   assert.ok(span.endTime, 'must be finished');
   assert.ok(hrTimeToNanoseconds(span.duration), 'must have positive duration');
 
+  if (validations.reqHeaders) {
+    const userAgent = validations.reqHeaders['user-agent'];
+    if (userAgent) {
+      assert.strictEqual(span.attributes[ATTR_HTTP_USER_AGENT], userAgent);
+    }
+  }
+
   if (span.kind === SpanKind.CLIENT) {
+    if (validations.resHeaders['content-length']) {
+      const contentLength = Number(validations.resHeaders['content-length']);
+
+      if (
+        validations.resHeaders['content-encoding'] &&
+        validations.resHeaders['content-encoding'] !== 'identity'
+      ) {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH],
+          contentLength
+        );
+      } else {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED],
+          contentLength
+        );
+      }
+    }
     assert.strictEqual(
-      span.attributes[ATTR_SERVER_ADDRESS],
+      span.attributes[ATTR_NET_PEER_NAME],
       validations.hostname,
       'must be consistent (PEER_NAME and hostname)'
     );
     if (!validations.noNetPeer) {
-      assert.ok(
-        span.attributes[ATTR_NETWORK_PEER_ADDRESS],
-        'must have PEER_IP'
-      );
-      assert.ok(span.attributes[ATTR_SERVER_PORT], 'must have PEER_PORT');
+      assert.ok(span.attributes[ATTR_NET_PEER_IP], 'must have PEER_IP');
+      assert.ok(span.attributes[ATTR_NET_PEER_PORT], 'must have PEER_PORT');
     }
     assert.ok(
-      (span.attributes[ATTR_URL_FULL] as string).indexOf(
-        span.attributes[ATTR_SERVER_ADDRESS] as string
+      (span.attributes[ATTR_HTTP_URL] as string).indexOf(
+        span.attributes[ATTR_NET_PEER_NAME] as string
       ) > -1,
       'must be consistent'
     );
   }
   if (span.kind === SpanKind.SERVER) {
+    if (validations.reqHeaders && validations.reqHeaders['content-length']) {
+      const contentLength = validations.reqHeaders['content-length'];
+
+      if (
+        validations.reqHeaders['content-encoding'] &&
+        validations.reqHeaders['content-encoding'] !== 'identity'
+      ) {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH],
+          contentLength
+        );
+      } else {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED],
+          contentLength
+        );
+      }
+    }
+    if (validations.serverName) {
+      assert.strictEqual(
+        span.attributes[ATTR_HTTP_SERVER_NAME],
+        validations.serverName,
+        ' must have serverName attribute'
+      );
+      assert.ok(span.attributes[ATTR_NET_HOST_PORT], 'must have HOST_PORT');
+      assert.ok(span.attributes[ATTR_NET_HOST_IP], 'must have HOST_IP');
+    }
     assert.strictEqual(
-      span.attributes[ATTR_URL_SCHEME],
+      span.attributes[ATTR_HTTP_SCHEME],
       validations.component,
       ' must have http.scheme attribute'
     );
     assert.ok(typeof span.parentSpanContext?.spanId === 'string');
     assert.ok(isValidSpanId(span.parentSpanContext.spanId));
-
-    assert.strictEqual(
-      span.attributes[ATTR_URL_PATH],
-      validations.path || validations.pathname
-    );
-
-    if (validations.reqHeaders) {
-      const userAgent = validations.reqHeaders['user-agent'];
-      if (userAgent) {
-        assert.strictEqual(
-          span.attributes[ATTR_USER_AGENT_ORIGINAL],
-          userAgent
-        );
-      }
-    }
   } else if (validations.reqHeaders) {
     assert.ok(validations.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY]);
     assert.ok(validations.reqHeaders[DummyPropagation.SPAN_CONTEXT_KEY]);


Reviews:
user: login: JamieDanielson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T15:13:44Z
user: login: trentm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T16:10:30Z
user: login: trentm
body: Nice! A few nits is all. I think this is good.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T00:13:26Z
user: login: JamieDanielson
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T14:39:59Z
user: login: trentm
body: LGTM with a suggestion/correction for the content-length-related attrs.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T16:45:19Z
user: login: trentm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T17:08:26Z

Review comments:
diff_hunk: @@ -51,34 +51,83 @@ See [examples/http](https://github.com/open-telemetry/opentelemetry-js/tree/main
 
 ### Http instrumentation Options
 
-Http instrumentation has few options available to choose from. You can set the following:
-
-| Options | Type | Description |
-| ------- | ---- | ----------- |
-| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
-| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#93) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
-| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L95) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
-| [`startIncomingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L97) | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest |
-| [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
-| `ignoreIncomingRequestHook` | `IgnoreIncomingRequestFunction` | Http instrumentation will not trace all incoming requests that matched with custom function |
-| `ignoreOutgoingRequestHook` | `IgnoreOutgoingRequestFunction` | Http instrumentation will not trace all outgoing requests that matched with custom function |
-| `disableOutgoingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests. |
-| `disableIncomingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests. |
-| [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
-| [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
-| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L107) | `object` | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
+Http instrumentation has a few [configuration options](https://github.com/open-telemetry/opentelemetry-js/blob/e1ec4026edae53a2dea3a9a604d6d21bb5e8d99f/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L60-L93) available to choose from.
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: JamieDanielson
body: Note for reviewer: I removed the links from the table and put a general link up here. It makes the table easier to work with, but more importantly, the links were no longer linking to the right place and they are fragile anyway.
created_at: 2025-05-09T15:13:44Z
author_association: MEMBER
diff_hunk: @@ -51,34 +51,83 @@ See [examples/http](https://github.com/open-telemetry/opentelemetry-js/tree/main
 
 ### Http instrumentation Options
 
-Http instrumentation has few options available to choose from. You can set the following:
-
-| Options | Type | Description |
-| ------- | ---- | ----------- |
-| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
-| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#93) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
-| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L95) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
-| [`startIncomingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L97) | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest |
-| [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
-| `ignoreIncomingRequestHook` | `IgnoreIncomingRequestFunction` | Http instrumentation will not trace all incoming requests that matched with custom function |
-| `ignoreOutgoingRequestHook` | `IgnoreOutgoingRequestFunction` | Http instrumentation will not trace all outgoing requests that matched with custom function |
-| `disableOutgoingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests. |
-| `disableIncomingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests. |
-| [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
-| [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
-| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L107) | `object` | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
+Http instrumentation has a few [configuration options](https://github.com/open-telemetry/opentelemetry-js/blob/e1ec4026edae53a2dea3a9a604d6d21bb5e8d99f/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L60-L93) available to choose from.
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: trentm
body: +1 from me. I did the same in other instrumentation READMEs. Those links are a maintenance pain.
created_at: 2025-05-09T16:10:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,34 +51,83 @@ See [examples/http](https://github.com/open-telemetry/opentelemetry-js/tree/main
 
 ### Http instrumentation Options
 
-Http instrumentation has few options available to choose from. You can set the following:
-
-| Options | Type | Description |
-| ------- | ---- | ----------- |
-| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
-| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#93) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
-| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L95) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
-| [`startIncomingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L97) | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest |
-| [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
-| `ignoreIncomingRequestHook` | `IgnoreIncomingRequestFunction` | Http instrumentation will not trace all incoming requests that matched with custom function |
-| `ignoreOutgoingRequestHook` | `IgnoreOutgoingRequestFunction` | Http instrumentation will not trace all outgoing requests that matched with custom function |
-| `disableOutgoingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests. |
-| `disableIncomingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests. |
-| [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
-| [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
-| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L107) | `object` | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
+Http instrumentation has a few [configuration options](https://github.com/open-telemetry/opentelemetry-js/blob/e1ec4026edae53a2dea3a9a604d6d21bb5e8d99f/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L60-L93) available to choose from.
+You can set the following:
+
+| Options                                 | Type                                       | Description                                                                                                                                                                                                                                                                                                                  |
+| --------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `applyCustomAttributesOnSpan`           | `HttpCustomAttributeFunction`              | Function for adding custom attributes                                                                                                                                                                                                                                                                                        |
+| `requestHook`                           | `HttpRequestCustomAttributeFunction`       | Function for adding custom attributes before request is handled                                                                                                                                                                                                                                                              |
+| `responseHook`                          | `HttpResponseCustomAttributeFunction`      | Function for adding custom attributes before response is handled                                                                                                                                                                                                                                                             |
+| `startIncomingSpanHook`                 | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest                                                                                                                                                                                                                                            |
+| `startOutgoingSpanHook`                 | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest                                                                                                                                                                                                                                            |
+| `ignoreIncomingRequestHook`             | `IgnoreIncomingRequestFunction`            | Http instrumentation will not trace all incoming requests that matched with custom function                                                                                                                                                                                                                                  |
+| `ignoreOutgoingRequestHook`             | `IgnoreOutgoingRequestFunction`            | Http instrumentation will not trace all outgoing requests that matched with custom function                                                                                                                                                                                                                                  |
+| `disableOutgoingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests.                                                                                                                                                                                     |
+| `disableIncomingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests.                                                                                                                                                                                     |
+| `serverName`                            | `string`                                   | The primary server name of the matched virtual host.                                                                                                                                                                                                                                                                         |
+| `requireParentforOutgoingSpans`         | Boolean                                    | Require that is a parent span to create new span for outgoing requests.                                                                                                                                                                                                                                                      |
+| `requireParentforIncomingSpans`         | Boolean                                    | Require that is a parent span to create new span for incoming requests.                                                                                                                                                                                                                                                      |
+| `headersToSpanAttributes`               | `object`                                   | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
 
 ## Semantic Conventions
 
-This package emits Semantic Convention [Version 1.27.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md).
-
-Follow all requirements and recommendations of HTTP Client and Server Semantic Conventions Version 1.27.0 for [spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md) and [metrics](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md), including all required and recommended attributes.
+Prior to version `0.54.0`, this instrumentation created spans targeting an experimental semantic convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
+
+HTTP semantic conventions (semconv) were stabilized in v1.23.0, and a [migration process](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md#http-semantic-convention-stability-migration) was defined.
+`instrumentation-http` versions 0.54.0 and later include support for migrating to stable HTTP semantic conventions, as described below.
+The intent is to provide an approximate 6 month time window for users of this instrumentation to migrate to the new HTTP semconv, after which a new minor version will use the *new* semconv by default and drop support for the old semconv.
+See the [HTTP semconv migration plan for OpenTelemetry JS instrumentations](https://github.com/open-telemetry/opentelemetry-js/issues/5646).
+
+To select which semconv version(s) is emitted from this instrumentation, use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable.
+
+- `http`: emit the new (stable) v1.23.0+ semantics
+- `http/dep`: emit **both** the old v1.7.0 and the new (stable) v1.23.0+ semantics
+- By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.7.0 semconv is used.
+
+### Attributes collected
+
+| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              | tmp - old                                          |
+| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------- |
+| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             | SEMATTRS_HTTP_CLIENT_IP                            |
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: trentm
body: What was your intent with the `tmp - old` column?
created_at: 2025-05-09T23:13:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,34 +51,83 @@ See [examples/http](https://github.com/open-telemetry/opentelemetry-js/tree/main
 
 ### Http instrumentation Options
 
-Http instrumentation has few options available to choose from. You can set the following:
-
-| Options | Type | Description |
-| ------- | ---- | ----------- |
-| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
-| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#93) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
-| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L95) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
-| [`startIncomingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L97) | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest |
-| [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
-| `ignoreIncomingRequestHook` | `IgnoreIncomingRequestFunction` | Http instrumentation will not trace all incoming requests that matched with custom function |
-| `ignoreOutgoingRequestHook` | `IgnoreOutgoingRequestFunction` | Http instrumentation will not trace all outgoing requests that matched with custom function |
-| `disableOutgoingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests. |
-| `disableIncomingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests. |
-| [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
-| [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
-| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L107) | `object` | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
+Http instrumentation has a few [configuration options](https://github.com/open-telemetry/opentelemetry-js/blob/e1ec4026edae53a2dea3a9a604d6d21bb5e8d99f/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L60-L93) available to choose from.
+You can set the following:
+
+| Options                                 | Type                                       | Description                                                                                                                                                                                                                                                                                                                  |
+| --------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `applyCustomAttributesOnSpan`           | `HttpCustomAttributeFunction`              | Function for adding custom attributes                                                                                                                                                                                                                                                                                        |
+| `requestHook`                           | `HttpRequestCustomAttributeFunction`       | Function for adding custom attributes before request is handled                                                                                                                                                                                                                                                              |
+| `responseHook`                          | `HttpResponseCustomAttributeFunction`      | Function for adding custom attributes before response is handled                                                                                                                                                                                                                                                             |
+| `startIncomingSpanHook`                 | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest                                                                                                                                                                                                                                            |
+| `startOutgoingSpanHook`                 | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest                                                                                                                                                                                                                                            |
+| `ignoreIncomingRequestHook`             | `IgnoreIncomingRequestFunction`            | Http instrumentation will not trace all incoming requests that matched with custom function                                                                                                                                                                                                                                  |
+| `ignoreOutgoingRequestHook`             | `IgnoreOutgoingRequestFunction`            | Http instrumentation will not trace all outgoing requests that matched with custom function                                                                                                                                                                                                                                  |
+| `disableOutgoingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests.                                                                                                                                                                                     |
+| `disableIncomingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests.                                                                                                                                                                                     |
+| `serverName`                            | `string`                                   | The primary server name of the matched virtual host.                                                                                                                                                                                                                                                                         |
+| `requireParentforOutgoingSpans`         | Boolean                                    | Require that is a parent span to create new span for outgoing requests.                                                                                                                                                                                                                                                      |
+| `requireParentforIncomingSpans`         | Boolean                                    | Require that is a parent span to create new span for incoming requests.                                                                                                                                                                                                                                                      |
+| `headersToSpanAttributes`               | `object`                                   | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
 
 ## Semantic Conventions
 
-This package emits Semantic Convention [Version 1.27.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md).
-
-Follow all requirements and recommendations of HTTP Client and Server Semantic Conventions Version 1.27.0 for [spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md) and [metrics](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md), including all required and recommended attributes.
+Prior to version `0.54.0`, this instrumentation created spans targeting an experimental semantic convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
+
+HTTP semantic conventions (semconv) were stabilized in v1.23.0, and a [migration process](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md#http-semantic-convention-stability-migration) was defined.
+`instrumentation-http` versions 0.54.0 and later include support for migrating to stable HTTP semantic conventions, as described below.
+The intent is to provide an approximate 6 month time window for users of this instrumentation to migrate to the new HTTP semconv, after which a new minor version will use the *new* semconv by default and drop support for the old semconv.
+See the [HTTP semconv migration plan for OpenTelemetry JS instrumentations](https://github.com/open-telemetry/opentelemetry-js/issues/5646).
+
+To select which semconv version(s) is emitted from this instrumentation, use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable.
+
+- `http`: emit the new (stable) v1.23.0+ semantics
+- `http/dep`: emit **both** the old v1.7.0 and the new (stable) v1.23.0+ semantics
+- By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.7.0 semconv is used.
+
+### Attributes collected
+
+| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              | tmp - old                                          |
+| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------- |
+| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             | SEMATTRS_HTTP_CLIENT_IP                            |
+| `http.flavor`                               | `network.protocol.version`  | Kind of HTTP protocol used                                                     | SEMATTRS_HTTP_FLAVOR                               |
+| `http.host`                                 | `server.address`            | The value of the HTTP host header                                              | SEMATTRS_HTTP_HOST                                 |
+| `http.method`                               | `http.request.method`       | HTTP request method                                                            | SEMATTRS_HTTP_METHOD                               |
+| `http.request_content_length`               | no change, not yet stable   | The size of the request payload body in bytes                                  | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH               |
+| `http.request_content_length_uncompressed`  | `http.request.body.size`    | The size of the uncompressed request payload body after transport decoding     | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED  |
+| `http.response_content_length`              | no change, not yet stable   | The size of the response payload body in bytes                                 | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH              |
+| `http.response_content_length_uncompressed` | `http.request.body.size`    | The size of the uncompressed response payload body after transport decoding    | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED |
+| `http.route`                                | no change                   | The matched route (path template).                                             | SEMATTRS_HTTP_ROUTE                                |
+| `http.scheme`                               | `url.scheme`                | The URI scheme identifying the used protocol                                   | SEMATTRS_HTTP_SCHEME                               |
+| `http.server_name`                          | `server.address`            | The primary server name of the matched virtual host                            | SEMATTRS_HTTP_SERVER_NAME                          |
+| `http.status_code`                          | `http.response.status_code` | HTTP response status code                                                      | SEMATTRS_HTTP_STATUS_CODE                          |
+| `http.target`                               | `url.path` and `url.query`  | The URI path and query component                                               | SEMATTRS_HTTP_TARGET                               |
+| `http.url`                                  | `url.full`                  | Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]` | SEMATTRS_HTTP_URL                                  |
+| `http.user_agent`                           | `user_agent.original`       | Value of the HTTP User-Agent header sent by the client                         | SEMATTRS_HTTP_USER_AGENT                           |
+| `net.host.ip`                               | network.local.address       | Like net.peer.ip but for the host IP. Useful in case of a multi-IP host        | NET_HOST_IP                                        |
+| `net.host.name`                             | `server.address`            | Local hostname or similar                                                      | NET_HOST_NAME                                      |
+| `net.host.port`                             | `server.port`               | Like net.peer.port but for the host port                                       | NET_HOST_PORT                                      |
+| `net.peer.ip.`                              | `network.peer.address`      | Remote address of the peer (dotted decimal for IPv4 or RFC5952 for IPv6)       | NET_PEER_IP                                        |
+| `net.peer.name`                             | `server.address`            | Server domain name if available without reverse DNS lookup                     | NET_PEER_NAME                                      |
+| `net.peer.port`                             | `server.port`               | Server port number                                                             | NET_PEER_PORT                                      |
+| `net.transport`                             | `network.transport`         | Transport protocol used                                                        | NET_TRANSPORT                                      |
 
 Metrics Exported:
 
 - [`http.server.request.duration`](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md#metric-httpserverrequestduration)
 - [`http.client.request.duration`](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md#metric-httpclientrequestduration)
 
+### Upgrading Semantic Conventions
+
+When upgrading to the new semantic conventions, it is recommended to do so in the following order:
+
+1. Upgrade `@opentelemetry/instrumentation-grpc` to the latest version
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: trentm
body: ```suggestion
1. Upgrade `@opentelemetry/instrumentation-http` to the latest version
```
created_at: 2025-05-09T23:14:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,22 +154,52 @@ export class HttpInstrumentation extends InstrumentationBase<HttpInstrumentation
 
   private _recordServerDuration(
     durationMs: number,
+    oldAttributes: Attributes,
     stableAttributes: Attributes
   ) {
-    this._stableHttpServerDurationHistogram.record(
-      durationMs / 1000,
-      stableAttributes
-    );
+    if (
+      (this._semconvStability & SemconvStability.OLD) ===
+      SemconvStability.OLD
path: experimental/packages/opentelemetry-instrumentation-http/src/http.ts
user: login: trentm
body: nit: I know you are just restoring code as is was, so feel free to ignore.
This pattern could be changed to the more compact:

```suggestion
      this._semconvStability & SemconvStability.OLD
```
created_at: 2025-05-09T23:18:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -365,6 +459,15 @@ export const getOutgoingRequestAttributes = (
     headers,
     `${options.component}:`
   );
+
+  const oldAttributes: Attributes = {
+    [ATTR_HTTP_URL]: urlFull,
+    [ATTR_HTTP_METHOD]: method,
+    [ATTR_HTTP_TARGET]: requestOptions.path || '/',
+    [ATTR_NET_PEER_NAME]: hostname,
+    [ATTR_HTTP_HOST]: headers.host ?? `${hostname}:${port}`,
+  };
+
path: experimental/packages/opentelemetry-instrumentation-http/src/utils.ts
user: login: trentm
body: nit unrelated to *this* change: I notice that https://github.com/open-telemetry/opentelemetry-js/pull/5488 added `ATTR_USER_AGENT_ORIGINAL` to `newAttributes`.

I think it probably should not have. Notice that `newAttributes` has a comment "Opt-in attributes left off for now" and per https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-client-span `user_agent.original` is Opt-in.

Possibly if `enableSyntheticSourceDetection` is configured, this could be considered as an Opt-in to also capture the raw `user_agent.original`.

Anyway, not for this PR.
created_at: 2025-05-09T23:38:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,49 +114,99 @@ export const assertSpan = (
   assert.ok(span.endTime, 'must be finished');
   assert.ok(hrTimeToNanoseconds(span.duration), 'must have positive duration');
 
+  if (validations.reqHeaders) {
+    const userAgent = validations.reqHeaders['user-agent'];
+    if (userAgent) {
+      assert.strictEqual(span.attributes[ATTR_HTTP_USER_AGENT], userAgent);
+    }
+  }
+
   if (span.kind === SpanKind.CLIENT) {
+    if (validations.resHeaders['content-length']) {
+      const contentLength = Number(validations.resHeaders['content-length']);
+
+      if (
+        validations.resHeaders['content-encoding'] &&
+        validations.resHeaders['content-encoding'] !== 'identity'
+      ) {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH],
+          contentLength
+        );
+      } else {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED],
+          contentLength
+        );
+      }
+    }
     assert.strictEqual(
-      span.attributes[ATTR_SERVER_ADDRESS],
+      span.attributes[ATTR_NET_PEER_NAME],
       validations.hostname,
       'must be consistent (PEER_NAME and hostname)'
     );
     if (!validations.noNetPeer) {
-      assert.ok(
-        span.attributes[ATTR_NETWORK_PEER_ADDRESS],
-        'must have PEER_IP'
-      );
-      assert.ok(span.attributes[ATTR_SERVER_PORT], 'must have PEER_PORT');
+      assert.ok(span.attributes[ATTR_NET_PEER_IP], 'must have PEER_IP');
+      assert.ok(span.attributes[ATTR_NET_PEER_PORT], 'must have PEER_PORT');
     }
     assert.ok(
-      (span.attributes[ATTR_URL_FULL] as string).indexOf(
-        span.attributes[ATTR_SERVER_ADDRESS] as string
+      (span.attributes[ATTR_HTTP_URL] as string).indexOf(
+        span.attributes[ATTR_NET_PEER_NAME] as string
       ) > -1,
       'must be consistent'
     );
   }
   if (span.kind === SpanKind.SERVER) {
+    if (validations.reqHeaders && validations.reqHeaders['content-length']) {
+      const contentLength = validations.reqHeaders['content-length'];
+
+      if (
+        validations.reqHeaders['content-encoding'] &&
+        validations.reqHeaders['content-encoding'] !== 'identity'
+      ) {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH],
+          contentLength
+        );
+      } else {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED],
+          contentLength
+        );
+      }
+    }
+    if (validations.serverName) {
+      assert.strictEqual(
+        span.attributes[ATTR_HTTP_SERVER_NAME],
+        validations.serverName,
+        ' must have serverName attribute'
+      );
+      assert.ok(span.attributes[ATTR_NET_HOST_PORT], 'must have HOST_PORT');
+      assert.ok(span.attributes[ATTR_NET_HOST_IP], 'must have HOST_IP');
+    }
     assert.strictEqual(
-      span.attributes[ATTR_URL_SCHEME],
+      span.attributes[ATTR_HTTP_SCHEME],
       validations.component,
       ' must have http.scheme attribute'
     );
     assert.ok(typeof span.parentSpanContext?.spanId === 'string');
     assert.ok(isValidSpanId(span.parentSpanContext.spanId));
 
-    assert.strictEqual(
-      span.attributes[ATTR_URL_PATH],
-      validations.path || validations.pathname
-    );
+    // TODO: stable only
+    // assert.strictEqual(
+    //   span.attributes[ATTR_URL_PATH],
+    //   validations.path || validations.pathname
+    // );
path: experimental/packages/opentelemetry-instrumentation-http/test/utils/assertSpan.ts
user: login: trentm
body: ATTR_URL_PATH presence is being tested by `describe('with semconv stability set to http'` in http-enable.test.ts, so I'm fine removing this block.
created_at: 2025-05-10T00:11:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,49 +114,99 @@ export const assertSpan = (
   assert.ok(span.endTime, 'must be finished');
   assert.ok(hrTimeToNanoseconds(span.duration), 'must have positive duration');
 
+  if (validations.reqHeaders) {
+    const userAgent = validations.reqHeaders['user-agent'];
+    if (userAgent) {
+      assert.strictEqual(span.attributes[ATTR_HTTP_USER_AGENT], userAgent);
+    }
+  }
+
   if (span.kind === SpanKind.CLIENT) {
+    if (validations.resHeaders['content-length']) {
+      const contentLength = Number(validations.resHeaders['content-length']);
+
+      if (
+        validations.resHeaders['content-encoding'] &&
+        validations.resHeaders['content-encoding'] !== 'identity'
+      ) {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH],
+          contentLength
+        );
+      } else {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED],
+          contentLength
+        );
+      }
+    }
     assert.strictEqual(
-      span.attributes[ATTR_SERVER_ADDRESS],
+      span.attributes[ATTR_NET_PEER_NAME],
       validations.hostname,
       'must be consistent (PEER_NAME and hostname)'
     );
     if (!validations.noNetPeer) {
-      assert.ok(
-        span.attributes[ATTR_NETWORK_PEER_ADDRESS],
-        'must have PEER_IP'
-      );
-      assert.ok(span.attributes[ATTR_SERVER_PORT], 'must have PEER_PORT');
+      assert.ok(span.attributes[ATTR_NET_PEER_IP], 'must have PEER_IP');
+      assert.ok(span.attributes[ATTR_NET_PEER_PORT], 'must have PEER_PORT');
     }
     assert.ok(
-      (span.attributes[ATTR_URL_FULL] as string).indexOf(
-        span.attributes[ATTR_SERVER_ADDRESS] as string
+      (span.attributes[ATTR_HTTP_URL] as string).indexOf(
+        span.attributes[ATTR_NET_PEER_NAME] as string
       ) > -1,
       'must be consistent'
     );
   }
   if (span.kind === SpanKind.SERVER) {
+    if (validations.reqHeaders && validations.reqHeaders['content-length']) {
+      const contentLength = validations.reqHeaders['content-length'];
+
+      if (
+        validations.reqHeaders['content-encoding'] &&
+        validations.reqHeaders['content-encoding'] !== 'identity'
+      ) {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH],
+          contentLength
+        );
+      } else {
+        assert.strictEqual(
+          span.attributes[ATTR_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED],
+          contentLength
+        );
+      }
+    }
+    if (validations.serverName) {
+      assert.strictEqual(
+        span.attributes[ATTR_HTTP_SERVER_NAME],
+        validations.serverName,
+        ' must have serverName attribute'
+      );
+      assert.ok(span.attributes[ATTR_NET_HOST_PORT], 'must have HOST_PORT');
+      assert.ok(span.attributes[ATTR_NET_HOST_IP], 'must have HOST_IP');
+    }
     assert.strictEqual(
-      span.attributes[ATTR_URL_SCHEME],
+      span.attributes[ATTR_HTTP_SCHEME],
       validations.component,
       ' must have http.scheme attribute'
     );
     assert.ok(typeof span.parentSpanContext?.spanId === 'string');
     assert.ok(isValidSpanId(span.parentSpanContext.spanId));
 
-    assert.strictEqual(
-      span.attributes[ATTR_URL_PATH],
-      validations.path || validations.pathname
-    );
+    // TODO: stable only
+    // assert.strictEqual(
+    //   span.attributes[ATTR_URL_PATH],
+    //   validations.path || validations.pathname
+    // );
 
-    if (validations.reqHeaders) {
-      const userAgent = validations.reqHeaders['user-agent'];
-      if (userAgent) {
-        assert.strictEqual(
-          span.attributes[ATTR_USER_AGENT_ORIGINAL],
-          userAgent
-        );
-      }
-    }
+    // if (validations.reqHeaders) {
+    //   const userAgent = validations.reqHeaders['user-agent'];
+    //   if (userAgent) {
+    //     assert.strictEqual(
+    //       span.attributes[ATTR_USER_AGENT_ORIGINAL],
+    //       userAgent
+    //     );
+    //   }
+    // }
path: experimental/packages/opentelemetry-instrumentation-http/test/utils/assertSpan.ts
user: login: trentm
body: There is a test for this in "test/functionals/utils.test.ts", so I'm fine removing this block.
That means the commented out "import ..." at the top of this file can also be removed.
created_at: 2025-05-10T00:13:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,34 +51,83 @@ See [examples/http](https://github.com/open-telemetry/opentelemetry-js/tree/main
 
 ### Http instrumentation Options
 
-Http instrumentation has few options available to choose from. You can set the following:
-
-| Options | Type | Description |
-| ------- | ---- | ----------- |
-| [`applyCustomAttributesOnSpan`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L91) | `HttpCustomAttributeFunction` | Function for adding custom attributes |
-| [`requestHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#93) | `HttpRequestCustomAttributeFunction` | Function for adding custom attributes before request is handled |
-| [`responseHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L95) | `HttpResponseCustomAttributeFunction` | Function for adding custom attributes before response is handled |
-| [`startIncomingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L97) | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest |
-| [`startOutgoingSpanHook`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L99) | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest |
-| `ignoreIncomingRequestHook` | `IgnoreIncomingRequestFunction` | Http instrumentation will not trace all incoming requests that matched with custom function |
-| `ignoreOutgoingRequestHook` | `IgnoreOutgoingRequestFunction` | Http instrumentation will not trace all outgoing requests that matched with custom function |
-| `disableOutgoingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests. |
-| `disableIncomingRequestInstrumentation` | `boolean` | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests. |
-| [`requireParentforOutgoingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L103) | Boolean | Require that is a parent span to create new span for outgoing requests. |
-| [`requireParentforIncomingSpans`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L105) | Boolean | Require that is a parent span to create new span for incoming requests. |
-| [`headersToSpanAttributes`](https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L107) | `object` | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
+Http instrumentation has a few [configuration options](https://github.com/open-telemetry/opentelemetry-js/blob/e1ec4026edae53a2dea3a9a604d6d21bb5e8d99f/experimental/packages/opentelemetry-instrumentation-http/src/types.ts#L60-L93) available to choose from.
+You can set the following:
+
+| Options                                 | Type                                       | Description                                                                                                                                                                                                                                                                                                                  |
+| --------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `applyCustomAttributesOnSpan`           | `HttpCustomAttributeFunction`              | Function for adding custom attributes                                                                                                                                                                                                                                                                                        |
+| `requestHook`                           | `HttpRequestCustomAttributeFunction`       | Function for adding custom attributes before request is handled                                                                                                                                                                                                                                                              |
+| `responseHook`                          | `HttpResponseCustomAttributeFunction`      | Function for adding custom attributes before response is handled                                                                                                                                                                                                                                                             |
+| `startIncomingSpanHook`                 | `StartIncomingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in incomingRequest                                                                                                                                                                                                                                            |
+| `startOutgoingSpanHook`                 | `StartOutgoingSpanCustomAttributeFunction` | Function for adding custom attributes before a span is started in outgoingRequest                                                                                                                                                                                                                                            |
+| `ignoreIncomingRequestHook`             | `IgnoreIncomingRequestFunction`            | Http instrumentation will not trace all incoming requests that matched with custom function                                                                                                                                                                                                                                  |
+| `ignoreOutgoingRequestHook`             | `IgnoreOutgoingRequestFunction`            | Http instrumentation will not trace all outgoing requests that matched with custom function                                                                                                                                                                                                                                  |
+| `disableOutgoingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting outgoing requests at all. This can be helpful when another instrumentation handles outgoing requests.                                                                                                                                                                                     |
+| `disableIncomingRequestInstrumentation` | `boolean`                                  | Set to true to avoid instrumenting incoming requests at all. This can be helpful when another instrumentation handles incoming requests.                                                                                                                                                                                     |
+| `serverName`                            | `string`                                   | The primary server name of the matched virtual host.                                                                                                                                                                                                                                                                         |
+| `requireParentforOutgoingSpans`         | Boolean                                    | Require that is a parent span to create new span for outgoing requests.                                                                                                                                                                                                                                                      |
+| `requireParentforIncomingSpans`         | Boolean                                    | Require that is a parent span to create new span for incoming requests.                                                                                                                                                                                                                                                      |
+| `headersToSpanAttributes`               | `object`                                   | List of case insensitive HTTP headers to convert to span attributes. Client (outgoing requests, incoming responses) and server (incoming requests, outgoing responses) headers will be converted to span attributes in the form of `http.{request\|response}.header.header_name`, e.g. `http.response.header.content_length` |
 
 ## Semantic Conventions
 
-This package emits Semantic Convention [Version 1.27.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md).
-
-Follow all requirements and recommendations of HTTP Client and Server Semantic Conventions Version 1.27.0 for [spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-spans.md) and [metrics](https://github.com/open-telemetry/semantic-conventions/blob/v1.27.0/docs/http/http-metrics.md), including all required and recommended attributes.
+Prior to version `0.54.0`, this instrumentation created spans targeting an experimental semantic convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md).
+
+HTTP semantic conventions (semconv) were stabilized in v1.23.0, and a [migration process](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md#http-semantic-convention-stability-migration) was defined.
+`instrumentation-http` versions 0.54.0 and later include support for migrating to stable HTTP semantic conventions, as described below.
+The intent is to provide an approximate 6 month time window for users of this instrumentation to migrate to the new HTTP semconv, after which a new minor version will use the *new* semconv by default and drop support for the old semconv.
+See the [HTTP semconv migration plan for OpenTelemetry JS instrumentations](https://github.com/open-telemetry/opentelemetry-js/issues/5646).
+
+To select which semconv version(s) is emitted from this instrumentation, use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable.
+
+- `http`: emit the new (stable) v1.23.0+ semantics
+- `http/dep`: emit **both** the old v1.7.0 and the new (stable) v1.23.0+ semantics
+- By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.7.0 semconv is used.
+
+### Attributes collected
+
+| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              | tmp - old                                          |
+| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------- |
+| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             | SEMATTRS_HTTP_CLIENT_IP                            |
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: JamieDanielson
body: Ha my intent was to remove that 😅  Will remove in a commit shortly
created_at: 2025-05-12T14:39:59Z
author_association: MEMBER
diff_hunk: @@ -87,30 +87,30 @@ To select which semconv version(s) is emitted from this instrumentation, use the
 
 ### Attributes collected
 
-| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              | tmp - old                                          |
-| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------- |
-| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             | SEMATTRS_HTTP_CLIENT_IP                            |
-| `http.flavor`                               | `network.protocol.version`  | Kind of HTTP protocol used                                                     | SEMATTRS_HTTP_FLAVOR                               |
-| `http.host`                                 | `server.address`            | The value of the HTTP host header                                              | SEMATTRS_HTTP_HOST                                 |
-| `http.method`                               | `http.request.method`       | HTTP request method                                                            | SEMATTRS_HTTP_METHOD                               |
-| `http.request_content_length`               | no change, not yet stable   | The size of the request payload body in bytes                                  | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH               |
-| `http.request_content_length_uncompressed`  | `http.request.body.size`    | The size of the uncompressed request payload body after transport decoding     | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED  |
-| `http.response_content_length`              | no change, not yet stable   | The size of the response payload body in bytes                                 | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH              |
-| `http.response_content_length_uncompressed` | `http.request.body.size`    | The size of the uncompressed response payload body after transport decoding    | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED |
-| `http.route`                                | no change                   | The matched route (path template).                                             | SEMATTRS_HTTP_ROUTE                                |
-| `http.scheme`                               | `url.scheme`                | The URI scheme identifying the used protocol                                   | SEMATTRS_HTTP_SCHEME                               |
-| `http.server_name`                          | `server.address`            | The primary server name of the matched virtual host                            | SEMATTRS_HTTP_SERVER_NAME                          |
-| `http.status_code`                          | `http.response.status_code` | HTTP response status code                                                      | SEMATTRS_HTTP_STATUS_CODE                          |
-| `http.target`                               | `url.path` and `url.query`  | The URI path and query component                                               | SEMATTRS_HTTP_TARGET                               |
-| `http.url`                                  | `url.full`                  | Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]` | SEMATTRS_HTTP_URL                                  |
-| `http.user_agent`                           | `user_agent.original`       | Value of the HTTP User-Agent header sent by the client                         | SEMATTRS_HTTP_USER_AGENT                           |
-| `net.host.ip`                               | network.local.address       | Like net.peer.ip but for the host IP. Useful in case of a multi-IP host        | NET_HOST_IP                                        |
-| `net.host.name`                             | `server.address`            | Local hostname or similar                                                      | NET_HOST_NAME                                      |
-| `net.host.port`                             | `server.port`               | Like net.peer.port but for the host port                                       | NET_HOST_PORT                                      |
-| `net.peer.ip.`                              | `network.peer.address`      | Remote address of the peer (dotted decimal for IPv4 or RFC5952 for IPv6)       | NET_PEER_IP                                        |
-| `net.peer.name`                             | `server.address`            | Server domain name if available without reverse DNS lookup                     | NET_PEER_NAME                                      |
-| `net.peer.port`                             | `server.port`               | Server port number                                                             | NET_PEER_PORT                                      |
-| `net.transport`                             | `network.transport`         | Transport protocol used                                                        | NET_TRANSPORT                                      |
+| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              |
+| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ |
+| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             |
+| `http.flavor`                               | `network.protocol.version`  | Kind of HTTP protocol used                                                     |
+| `http.host`                                 | `server.address`            | The value of the HTTP host header                                              |
+| `http.method`                               | `http.request.method`       | HTTP request method                                                            |
+| `http.request_content_length`               | no change, not yet stable   | The size of the request payload body in bytes                                  |
+| `http.request_content_length_uncompressed`  | `http.request.body.size`    | The size of the uncompressed request payload body after transport decoding     |
+| `http.response_content_length`              | no change, not yet stable   | The size of the response payload body in bytes                                 |
+| `http.response_content_length_uncompressed` | `http.request.body.size`    | The size of the uncompressed response payload body after transport decoding    |
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: trentm
body: ```suggestion
| `http.request_content_length`               | (opt-in, `headersToSpanAttributes`) | The size of the request payload body in bytes. For newer semconv, use the `headersToSpanAttributes: ` option to capture this as `http.request.header.content_length`. |
| `http.request_content_length_uncompressed`  | (not included)              | The size of the uncompressed request payload body after transport decoding. (In semconv v1.23.0 this is defined by `http.request.body.size`, which is experimental and opt-in.)    |
| `http.response_content_length`              | (opt-in, `headersToSpanAttributes`) | The size of the response payload body in bytes. For newer semconv, use the `headersToSpanAttributes: ` option to capture this as `http.response.header.content_length`. |
| `http.response_content_length_uncompressed` | (not included)              | The size of the uncompressed response payload body after transport decoding. (In semconv v1.23.0 this is defined by `http.response.body.size`, which is experimental and opt-in.)    |
```

- This corrects `http.request.body.size` to `http.response.body.size` on line 99.
- Neither of `http.{request,response}.body.size` are produced by the current instr-http, unless I totally missed it. I think we could add these to isntr-http, but I think that should be in a separate PR.




created_at: 2025-05-12T16:23:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,30 +87,30 @@ To select which semconv version(s) is emitted from this instrumentation, use the
 
 ### Attributes collected
 
-| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              | tmp - old                                          |
-| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------- |
-| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             | SEMATTRS_HTTP_CLIENT_IP                            |
-| `http.flavor`                               | `network.protocol.version`  | Kind of HTTP protocol used                                                     | SEMATTRS_HTTP_FLAVOR                               |
-| `http.host`                                 | `server.address`            | The value of the HTTP host header                                              | SEMATTRS_HTTP_HOST                                 |
-| `http.method`                               | `http.request.method`       | HTTP request method                                                            | SEMATTRS_HTTP_METHOD                               |
-| `http.request_content_length`               | no change, not yet stable   | The size of the request payload body in bytes                                  | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH               |
-| `http.request_content_length_uncompressed`  | `http.request.body.size`    | The size of the uncompressed request payload body after transport decoding     | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED  |
-| `http.response_content_length`              | no change, not yet stable   | The size of the response payload body in bytes                                 | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH              |
-| `http.response_content_length_uncompressed` | `http.request.body.size`    | The size of the uncompressed response payload body after transport decoding    | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED |
-| `http.route`                                | no change                   | The matched route (path template).                                             | SEMATTRS_HTTP_ROUTE                                |
-| `http.scheme`                               | `url.scheme`                | The URI scheme identifying the used protocol                                   | SEMATTRS_HTTP_SCHEME                               |
-| `http.server_name`                          | `server.address`            | The primary server name of the matched virtual host                            | SEMATTRS_HTTP_SERVER_NAME                          |
-| `http.status_code`                          | `http.response.status_code` | HTTP response status code                                                      | SEMATTRS_HTTP_STATUS_CODE                          |
-| `http.target`                               | `url.path` and `url.query`  | The URI path and query component                                               | SEMATTRS_HTTP_TARGET                               |
-| `http.url`                                  | `url.full`                  | Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]` | SEMATTRS_HTTP_URL                                  |
-| `http.user_agent`                           | `user_agent.original`       | Value of the HTTP User-Agent header sent by the client                         | SEMATTRS_HTTP_USER_AGENT                           |
-| `net.host.ip`                               | network.local.address       | Like net.peer.ip but for the host IP. Useful in case of a multi-IP host        | NET_HOST_IP                                        |
-| `net.host.name`                             | `server.address`            | Local hostname or similar                                                      | NET_HOST_NAME                                      |
-| `net.host.port`                             | `server.port`               | Like net.peer.port but for the host port                                       | NET_HOST_PORT                                      |
-| `net.peer.ip.`                              | `network.peer.address`      | Remote address of the peer (dotted decimal for IPv4 or RFC5952 for IPv6)       | NET_PEER_IP                                        |
-| `net.peer.name`                             | `server.address`            | Server domain name if available without reverse DNS lookup                     | NET_PEER_NAME                                      |
-| `net.peer.port`                             | `server.port`               | Server port number                                                             | NET_PEER_PORT                                      |
-| `net.transport`                             | `network.transport`         | Transport protocol used                                                        | NET_TRANSPORT                                      |
+| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              |
+| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ |
+| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             |
+| `http.flavor`                               | `network.protocol.version`  | Kind of HTTP protocol used                                                     |
+| `http.host`                                 | `server.address`            | The value of the HTTP host header                                              |
+| `http.method`                               | `http.request.method`       | HTTP request method                                                            |
+| `http.request_content_length`               | no change, not yet stable   | The size of the request payload body in bytes                                  |
+| `http.request_content_length_uncompressed`  | `http.request.body.size`    | The size of the uncompressed request payload body after transport decoding     |
+| `http.response_content_length`              | no change, not yet stable   | The size of the response payload body in bytes                                 |
+| `http.response_content_length_uncompressed` | `http.request.body.size`    | The size of the uncompressed response payload body after transport decoding    |
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: trentm
body: Side note: I think the current `headersToSpanAttributes` is getting things wrong.

Using this example instrumentation and HTTP-using script: https://gist.github.com/trentm/ce3568c01d1328d0769f136c3d24d6bb

And running that via `OTEL_SEMCONV_STABILITY_OPT_IN=http node play-http.js`, I get these spans (trimmed for clarity):

```js
// client span
{
  traceId: '231454febbc08ef404100d393fd9ad00',
  parentSpanContext: {
    traceId: '231454febbc08ef404100d393fd9ad00',
    spanId: 'bee01929e2b1182e',
    traceFlags: 1,
    isRemote: true
  },
  name: 'POST',
  id: '7d5f568d5fb5de3a',
  kind: 1,
  attributes: {
    'http.request.method': 'POST',
    'url.scheme': 'http',
    'server.address': 'localhost',
    'network.peer.address': '::1',
    'network.peer.port': 64804,
    'network.protocol.version': '1.1',
    'url.path': '/',
    'client.address': '::1',
    'server.port': 3000,
    'http.request.header.content_length': [ '13' ],
    'http.response.status_code': 200
  },
  status: { code: 0 },
  events: [],
  links: []
}

// server span
{
  traceId: '231454febbc08ef404100d393fd9ad00',
  parentSpanContext: undefined,
  traceState: undefined,
  name: 'POST',
  id: 'bee01929e2b1182e',
  kind: 2,
  timestamp: 1747067420549000,
  duration: 15742.708,
  attributes: {
    'http.request.method': 'POST',
    'server.address': 'localhost',
    'server.port': 3000,
    'url.full': 'http://localhost:3000/',
    'http.response.status_code': 200,
    'network.peer.address': '::1',
    'network.peer.port': 3000,
    'network.protocol.version': '1.1',
    'http.request.header.content_length': [ 13 ],
    'http.response.header.content_length': [ '4' ]
  },
  status: { code: 0 },
  events: [],
  links: []
}
```

1. Note in the server span: ` 'http.request.header.content_length': [ 13 ],`. That value is a *number* rather than a string. That's inconsistent with other "content length" headers.
2. The normalization if the `key` (e.g. `content_length`): I don't think hyphens should be normalized to underscores. https://opentelemetry.io/docs/specs/semconv/http/http-spans/   The spec prose is a little ambiguous on normalization here, but the examples include usage of hyphens in the keys.


ANYway. This is all for a separate PR. I'll make a separate issue.
created_at: 2025-05-12T16:42:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,30 +87,30 @@ To select which semconv version(s) is emitted from this instrumentation, use the
 
 ### Attributes collected
 
-| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              | tmp - old                                          |
-| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------- |
-| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             | SEMATTRS_HTTP_CLIENT_IP                            |
-| `http.flavor`                               | `network.protocol.version`  | Kind of HTTP protocol used                                                     | SEMATTRS_HTTP_FLAVOR                               |
-| `http.host`                                 | `server.address`            | The value of the HTTP host header                                              | SEMATTRS_HTTP_HOST                                 |
-| `http.method`                               | `http.request.method`       | HTTP request method                                                            | SEMATTRS_HTTP_METHOD                               |
-| `http.request_content_length`               | no change, not yet stable   | The size of the request payload body in bytes                                  | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH               |
-| `http.request_content_length_uncompressed`  | `http.request.body.size`    | The size of the uncompressed request payload body after transport decoding     | SEMATTRS_HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED  |
-| `http.response_content_length`              | no change, not yet stable   | The size of the response payload body in bytes                                 | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH              |
-| `http.response_content_length_uncompressed` | `http.request.body.size`    | The size of the uncompressed response payload body after transport decoding    | SEMATTRS_HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED |
-| `http.route`                                | no change                   | The matched route (path template).                                             | SEMATTRS_HTTP_ROUTE                                |
-| `http.scheme`                               | `url.scheme`                | The URI scheme identifying the used protocol                                   | SEMATTRS_HTTP_SCHEME                               |
-| `http.server_name`                          | `server.address`            | The primary server name of the matched virtual host                            | SEMATTRS_HTTP_SERVER_NAME                          |
-| `http.status_code`                          | `http.response.status_code` | HTTP response status code                                                      | SEMATTRS_HTTP_STATUS_CODE                          |
-| `http.target`                               | `url.path` and `url.query`  | The URI path and query component                                               | SEMATTRS_HTTP_TARGET                               |
-| `http.url`                                  | `url.full`                  | Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]` | SEMATTRS_HTTP_URL                                  |
-| `http.user_agent`                           | `user_agent.original`       | Value of the HTTP User-Agent header sent by the client                         | SEMATTRS_HTTP_USER_AGENT                           |
-| `net.host.ip`                               | network.local.address       | Like net.peer.ip but for the host IP. Useful in case of a multi-IP host        | NET_HOST_IP                                        |
-| `net.host.name`                             | `server.address`            | Local hostname or similar                                                      | NET_HOST_NAME                                      |
-| `net.host.port`                             | `server.port`               | Like net.peer.port but for the host port                                       | NET_HOST_PORT                                      |
-| `net.peer.ip.`                              | `network.peer.address`      | Remote address of the peer (dotted decimal for IPv4 or RFC5952 for IPv6)       | NET_PEER_IP                                        |
-| `net.peer.name`                             | `server.address`            | Server domain name if available without reverse DNS lookup                     | NET_PEER_NAME                                      |
-| `net.peer.port`                             | `server.port`               | Server port number                                                             | NET_PEER_PORT                                      |
-| `net.transport`                             | `network.transport`         | Transport protocol used                                                        | NET_TRANSPORT                                      |
+| v1.7.0 semconv                              | v1.23.0 semconv             | Short Description                                                              |
+| ------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ |
+| `http.client_ip`                            | `client.address`            | The IP address of the original client behind all proxies, if known             |
+| `http.flavor`                               | `network.protocol.version`  | Kind of HTTP protocol used                                                     |
+| `http.host`                                 | `server.address`            | The value of the HTTP host header                                              |
+| `http.method`                               | `http.request.method`       | HTTP request method                                                            |
+| `http.request_content_length`               | no change, not yet stable   | The size of the request payload body in bytes                                  |
+| `http.request_content_length_uncompressed`  | `http.request.body.size`    | The size of the uncompressed request payload body after transport decoding     |
+| `http.response_content_length`              | no change, not yet stable   | The size of the response payload body in bytes                                 |
+| `http.response_content_length_uncompressed` | `http.request.body.size`    | The size of the uncompressed response payload body after transport decoding    |
path: experimental/packages/opentelemetry-instrumentation-http/README.md
user: login: trentm
body: > ANYway. This is all for a separate PR. I'll make a separate issue.

https://github.com/open-telemetry/opentelemetry-js/issues/5678
created_at: 2025-05-12T17:08:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-09T21:22:53Z
body: ## [Codecov](https://app.codecov.io/gh/open-telemetry/opentelemetry-js/pull/5665?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.03%. Comparing base [(`0665c85`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-js/commit/0665c854c8565ed92172396f55773df868c5de9c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry) to head [(`73049a9`)](https://app.codecov.io/gh/open-telemetry/opentelemetry-js/commit/73049a9537858854ddd8204b2226d7f61a524cc1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry).
> Report is 3 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #5665   +/-   ##
=======================================
  Coverage   95.03%   95.03%           
=======================================
  Files         310      310           
  Lines        7988     7992    +4     
  Branches     1607     1614    +7     
=======================================
+ Hits         7591     7595    +4     
  Misses        397      397           
```

[see 1 file with indirect coverage changes](https://app.codecov.io/gh/open-telemetry/opentelemetry-js/pull/5665/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-telemetry)

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: JamieDanielson
created_at: 2025-05-09T21:32:40Z
body: I'm not quite sure how to handle the changelog. My first thought is to Skip Changelog, because it should just be reimplementing behavior that was already in the previous release. But there is the other PR in Breaking Changes, so we have to somehow offset the two.
author_association: MEMBER
user: login: trentm
created_at: 2025-05-10T00:15:01Z
body: > I'm not quite sure how to handle the changelog.

The end result for *release* will be that there was churn in instr-http that *should* just amount to a refactor. So I'd move the earlier breaking-change one to a `refactor(instrumentation-http): ...` one that discusses the change/sorta-revert and links to both. It would be helpful context if someone finds some issue and comes looking for possible culprits in the changelog.
author_association: CONTRIBUTOR
user: login: JamieDanielson
created_at: 2025-05-12T14:59:04Z
body: Thanks for the review and notes @trentm ! I've made the changes suggested (holding off on the user agent change since it went in separately as mentioned). Let me know if you see anything else.
author_association: MEMBER