Skip to content

feat(tracing): emit OTel HTTP semantics for client spans - #20009

Open
link04 wants to merge 50 commits into
hanane/otel-semantics-test-harnessfrom
maximo/otel-http-client-semantics
Open

link04 wants to merge 50 commits into
hanane/otel-semantics-test-harnessfrom
maximo/otel-http-client-semantics

Conversation

@link04

@link04 link04 commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds OpenTelemetry HTTP semantic conventions for outbound HTTP client spans when DD_TRACE_OTEL_SEMANTICS_ENABLED=true.

This PR wires the shared HTTP semantics introduced in #20008 into the HTTP client lifecycle and set_http_meta. The snapshot coverage and configurable client error-status ranges are added separately in #20689.

This is the client layer of the implementation replacing #19501.

Emitted attributes

When OTel semantics are enabled, client spans use the following attributes:

Input OTel attribute
Request method http.request.method
Non-canonical or unknown method http.request.method_original
Request URL url.full
Destination host server.address
Destination port server.port
Response status http.response.status_code
User-Agent header user_agent.original
HTTP error status error.type

Numeric values such as server.port and http.response.status_code are stored as numeric attributes so they retain the correct type when exported over OTLP.

For example, a request using:

method = POST
url = https://user:pass@api.example.com:8443/v1/items
status = 201
user-agent = client/1.0

emits:

http.request.method = "POST"
url.full = "https://REDACTED:REDACTED@api.example.com:8443/v1/items"
server.address = "api.example.com"
server.port = 8443
http.response.status_code = 201
user_agent.original = "client/1.0"

The equivalent Datadog HTTP attributes are not emitted on the OTel path.

Method normalization

Known methods are normalized to their canonical uppercase spelling:

get   → http.request.method="GET"
Patch → http.request.method="PATCH"

When normalization changes the original value, the input is preserved in http.request.method_original.

Unknown methods use _OTHER:

PROPFIND →
    http.request.method="_OTHER"
    http.request.method_original="PROPFIND"

Client semantics are applied only when the span explicitly has span.kind=client. An HTTP span_type alone is not enough because some server integrations also use it.

URL handling

Client URLs are emitted as url.full.

The implementation:

  • redacts credentials, including passwords containing @;
  • applies the existing query-string capture and obfuscation settings;
  • extracts server.address and server.port from the URL;
  • derives default ports from the scheme when the URL does not specify one;
  • falls back to separately supplied server or target-host metadata when the URL does not provide an address;
  • avoids logging the URL when parsing fails because it may contain credentials or sensitive query parameters.

Error handling

Client responses with a status code of 400 or greater are marked as errors.

The status code is stored as the string value of error.type when no more specific error type already exists. Exception metadata always takes precedence and is never replaced by a status-derived error type.

Configurable client error-status ranges are intentionally deferred to #20689.

Resource naming

OTel client spans use a low-cardinality, method-only resource:

GET      → GET
get      → GET
PROPFIND → HTTP

Client routes and raw URL paths are not included in the resource.

For event-based HTTP clients, the resource is assigned during the span-start lifecycle, before propagation and sampling. This ensures that resource-based sampling rules see the final OTel resource rather than the integration’s previous METHOD path value.

The implementation also preserves user ownership:

  • Resources generated by integrations may be replaced with the OTel resource.
  • Resources replaced by span-start processors or user code are preserved.
  • Later calls to set_http_meta do not overwrite a user-owned resource.

httplib performs the same early resource assignment before distributed-tracing injection, where its sampling decision occurs.

Compatibility

When DD_TRACE_OTEL_SEMANTICS_ENABLED is disabled, the existing Datadog behavior is unchanged:

The feature flag is evaluated at each set_http_meta call.

Validation includes:

  • scripts/lint checks
  • focused tracer tests
  • HTTPX2 integration tests
  • httplib integration tests
  • Test_OtelSemantics_Spans_Http_Client system tests against libdatadog#2323

Risks

The change affects the shared set_http_meta path used by HTTP integrations. The behavior is guarded by an opt-in feature flag, requires an explicit client span kind, and has coverage for both the OTel and existing Datadog paths.

PR stack

  1. feat(tracing): add OTel HTTP semantics foundation #20008 — shared OTel HTTP semantics foundation
  2. test(tracing): snapshot OTLP traces through the test agent #20688 — OTLP snapshot harness
  3. feat(tracing): emit OTel HTTP semantics for client spans #20009 — HTTP client semantics
  4. chore(tracing): add HTTP client error statuses and OTel client snapshots #20689 — client error-status configuration and OTLP snapshots
  5. feat(tracing): emit OTel HTTP semantics for server spans #20010 — HTTP server/framework and AppSec semantics
  6. chore(appsec): keep Datadog block-path tags when OTel semantics are off #20690 — AppSec block-path attributes and server OTLP snapshots

This PR is based on #20688. The original #19501 remains available as the complete historical implementation and system-test reference.

@link04 link04 added the changelog/no-changelog A changelog entry is not required for this PR. label Sep 1, 2026
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against hanane/otel-semantics-test-harness using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

.claude/skills/apm-integrations/references/implementation-guide.md      @DataDog/python-guild
ddtrace/_trace/otel/http/tags.py                                        @DataDog/apm-sdk-capabilities-python
ddtrace/_trace/subscribers/http_client.py                               @DataDog/apm-sdk-capabilities-python @DataDog/python-guild @DataDog/apm-idm-python
ddtrace/contrib/internal/httplib/patch.py                               @DataDog/python-guild @DataDog/apm-idm-python
ddtrace/contrib/internal/trace_utils.py                                 @DataDog/python-guild @DataDog/apm-idm-python
releasenotes/notes/otel-http-client-semantics-11ee892be11a1ba3.yaml     @DataDog/apm-python
tests/contrib/aiohttp/test_aiohttp_client.py                            @DataDog/python-guild @DataDog/apm-idm-python
tests/contrib/httplib/test_httplib.py                                   @DataDog/python-guild @DataDog/apm-idm-python
tests/contrib/httplib/test_httplib_distributed.py                       @DataDog/python-guild @DataDog/apm-idm-python
tests/contrib/httpx2/test_httpx2.py                                     @DataDog/python-guild @DataDog/apm-idm-python
tests/contrib/requests/test_requests.py                                 @DataDog/python-guild @DataDog/apm-idm-python
tests/contrib/urllib3/test_urllib3.py                                   @DataDog/python-guild @DataDog/apm-idm-python
tests/opentelemetry/test_otel_http_naming.py                            @DataDog/apm-sdk-capabilities-python
tests/tracer/test_otel_http_dispatch.py                                 @DataDog/apm-sdk-capabilities-python
tests/tracer/test_otel_span_naming.py                                   @DataDog/apm-sdk-capabilities-python
tests/tracer/test_trace_utils.py                                        @DataDog/apm-sdk-capabilities-python

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 1 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

There are 201 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 201 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=132)
ddtrace.internal.ci_visibility.api._base -×-> ddtrace.trace  (product:ci_visibility -> product:tracing, score=130)
ddtrace.llmobs._integrations.langchain -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=130)
ddtrace.llmobs._integrations.llama_index -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=130)
ddtrace.internal.opentelemetry.context -×-> ddtrace.trace  (product:opentelemetry -> product:tracing, score=130)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Pipelines  Tests

❌ Errors

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 7 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | core/integration_agent 1/12 — ❌ 1 test failed

View more details · View in GitLab

❌ test_otlp_trace_snapshot[py3.9] from test_otlp_trace_snapshot.py
404 page not found

DataDog/apm-reliability/dd-trace-py | core/integration_agent 3/12 — ❌ 1 test failed

View more details · View in GitLab

❌ test_otlp_trace_snapshot[py3.11] from test_otlp_trace_snapshot.py
404 page not found

DataDog/apm-reliability/dd-trace-py | core/integration_agent 4/12 — ❌ 1 test failed

View more details · View in GitLab

❌ test_otlp_trace_snapshot[py3.12] from test_otlp_trace_snapshot.py
404 page not found

View all 7 failed jobs.

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🔄 Datadog retried 7 tests - 1 passed on retry View in Datadog

🚧 2 tests that failed were ignored due to quarantine View in Datadog

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 2353c5d | Docs | View more details | Give us feedback!

@pr-commenter

pr-commenter Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-10-08 16:23:16

Comparing candidate commit 2353c5d in PR branch maximo/otel-http-client-semantics with baseline commit 51f0acb in branch hanane/otel-semantics-test-harness.

📊 Benchmarking dashboard

Found 0 performance improvements and 3 performance regressions! Performance is the same for 357 metrics, 9 unstable metrics, 3 known flaky benchmarks, 5 flaky benchmarks without significant changes.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:httppropagationextract-empty_headers

  • 🟥 execution_time [+87.916ns; +112.522ns] or [+11.953%; +15.298%]

scenario:msgpackencoderscenario-simple_one_span

  • 🟥 execution_time [+501.502ns; +567.954ns] or [+12.189%; +13.804%]

scenario:span-start-finish-telemetry

  • 🟥 execution_time [+3.761ms; +3.950ms] or [+9.062%; +9.519%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:coreapiscenario-context_with_data_listeners

  • unstable execution_time [-730.501ns; +743.070ns] or [-6.969%; +7.089%]

scenario:coreapiscenario-core_dispatch_1_listener

  • unstable execution_time [-41.676ns; +36.302ns] or [-6.247%; +5.442%]

scenario:coreapiscenario-core_dispatch_50_listeners

  • unstable execution_time [-1830.553ns; +1979.869ns] or [-9.238%; +9.991%]

scenario:coreapiscenario-core_dispatch_exception_listeners

  • unstable execution_time [-1872.075ns; +2169.783ns] or [-9.643%; +11.176%]

scenario:coreapiscenario-core_dispatch_listeners

  • unstable execution_time [-354.548ns; +402.535ns] or [-8.360%; +9.491%]

scenario:coreapiscenario-core_dispatch_no_args_listeners

  • unstable execution_time [-208.420ns; +244.580ns] or [-7.834%; +9.193%]

scenario:coreapiscenario-core_dispatch_with_results_1_listener

  • unstable execution_time [-122.238ns; +58.624ns] or [-8.855%; +4.247%]

scenario:coreapiscenario-core_dispatch_with_results_50_listeners

  • unstable execution_time [-4263.456ns; +4901.544ns] or [-8.947%; +10.286%]

scenario:coreapiscenario-core_dispatch_with_results_listeners

  • unstable execution_time [-898.699ns; +978.279ns] or [-8.909%; +9.698%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

scenario:httppropagationinject-ids_only

  • 🟥 execution_time [+2.984µs; +3.079µs] or [+21.084%; +21.756%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+269.047ns; +312.729ns] or [+13.913%; +16.172%]

scenario:tracer-small

  • 🟥 execution_time [+38.430µs; +39.697µs] or [+15.324%; +15.829%]

Known flaky benchmarks without significant changes:

  • scenario:errortrackingflasksqli-baseline
  • scenario:flasksimple-iast-get
  • scenario:sethttpmeta-all-enabled
  • scenario:span-start
  • scenario:telemetryaddmetric-record-100-metrics

@link04 link04 added OpenTelemetry AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos ai-generated PR created with AI assistance and removed changelog/no-changelog A changelog entry is not required for this PR. labels Sep 1, 2026
gh-worker-dd-mergequeue-cf854d Bot pushed a commit to DataDog/libdatadog that referenced this pull request Sep 2, 2026
…nsions (#2323)

### What does this PR do?

Preserves OpenTelemetry HTTP semantics in libdatadog's in-process consumers and OTLP output.

#### Trace stats and OTLP trace metrics

- reads canonical Datadog HTTP method/status keys first, then falls back to `http.request.method` and `http.response.status_code`
- accepts valid numeric or string status values without allowing empty, malformed, non-finite, or non-integral values to shadow a valid fallback
- emits OTel trace-metric dimensions for method, status, route, span name, and kind
- leaves successful `status.code` unset and prevents additional tags from overriding the computed status

### HTTP tag handling

libdatadog does not rename tracer span tags in this PR. It accepts the Datadog and OpenTelemetry names below as equivalent inputs to trace-stat aggregation and emits the canonical OpenTelemetry name in OTLP trace metrics.

| Concept | Datadog span input | OpenTelemetry span input | OTLP trace-metric output |
| --- | --- | --- | --- |
| HTTP method | `http.method` | `http.request.method` | `http.request.method` |
| HTTP response status | `http.status_code` | `http.response.status_code` | `http.response.status_code` as an integer |
| Span error status | Span error flag | Span error flag | `status.code=STATUS_CODE_ERROR` for errors; omitted for successful spans |

When both naming variants are present, the Datadog name wins. Empty or malformed values fall through to the alternate name instead of hiding a valid value.

#### OTLP sampled flags

Sampling priority is a trace-level decision, but v0.4/v0.5 inputs may attach `_sampling_priority_v1` to only one span. The mapper now uses the same first-priority policy as `drop_chunks`, treats a missing priority as sampled, and applies one decision to every span in the chunk.

This prevents sampled traces from exporting child spans with `flags=0` and keeps OTLP flags consistent with the existing chunk-retention policy.

No public signature, wire-format, setting, or environment-variable change is introduced.

### Motivation

Under `DD_TRACE_OTEL_SEMANTICS_ENABLED`, spans use OTel HTTP names before client-side stats are computed. Stats and OTLP metrics must preserve those dimensions, and all spans in an exported trace must carry the same sampled decision.

Companion changes:

- Historical full Python PR: [DataDog/dd-trace-py#19501](DataDog/dd-trace-py#19501)
- Native Python stack: [foundation #20008](DataDog/dd-trace-py#20008), [client #20009](DataDog/dd-trace-py#20009), [server #20010](DataDog/dd-trace-py#20010)
- [DataDog/dd-trace-js#9688](DataDog/dd-trace-js#9688)
- [DataDog/system-tests#7455](DataDog/system-tests#7455)

### Testing

- `cargo fmt --check -p libdd-trace-utils`
- mapper unit tests: **31 passed**
- OTLP trace-exporter integration tests: **3 passed**
- trace-stats tests: **43 passed**

End-to-end re-run from libdatadog head `0ed7fd9bd` with the final Python server layer and companion system tests:

| Scenario | Result |
| --- | ---: |
| `OTEL_SEMANTICS_OTLP_TRACE_METRICS` | **4 passed** |
| `OTEL_SEMANTICS_OTLP_SAMPLING_RULES` | **2 passed** |

Co-authored-by: maximo.bautista <maximo.bautista@datadoghq.com>
@link04 link04 closed this Sep 3, 2026
@link04 link04 reopened this Sep 3, 2026
@link04
link04 marked this pull request as ready for review September 3, 2026 18:03
@link04
link04 requested review from a team as code owners September 3, 2026 18:03
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T15:50:24.948967Z 2353c5d New commits
🔒 Security Review ✅ Completed 2026-10-08T15:52:09.837904Z 2353c5d New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3802bd6045

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ddtrace/contrib/internal/trace_utils.py
Comment thread ddtrace/contrib/internal/trace_utils.py
Comment thread ddtrace/contrib/internal/trace_utils.py
@link04
link04 requested a review from a team as a code owner September 3, 2026 20:13
@link04
link04 force-pushed the maximo/otel-http-client-semantics branch from 3802bd6 to 6f6a6ab Compare September 3, 2026 20:14
@Hanane-Laassilia
Hanane-Laassilia force-pushed the maximo/otel-http-client-semantics branch from 3f165df to 9df533c Compare October 8, 2026 07:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9df533c273

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if not config._otel_trace_semantics_enabled:
span._set_attribute(http.METHOD, method)
return
normalized_method, original_method = normalize_http_method(method)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor configured OTel HTTP methods

When OTel semantics is enabled and a user configures an extension method such as OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS=PROPFIND, this call still uses the hard-coded default method set; a repository-wide search finds no reader for that variable or another known-method override. The client span is consequently emitted with http.request.method=_OTHER and resource HTTP rather than PROPFIND, preventing sampling rules and dashboards from matching the configured method. The OTel HTTP convention requires an override whenever valid methods can be converted to _OTHER; make normalization honor the configured full method set.

Useful? React with 👍 / 👎.

Hanane-Laassilia and others added 10 commits October 8, 2026 16:34
Apply OTel HTTP attributes, error handling, and sampling-visible resource names to outbound spans while delegating span mutations to the tracing layer.

Co-authored-by: Cursor <cursoragent@cursor.com>
Name Datadog semantics explicitly in client tests and keep the resource ownership comment concise.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Hanane-Laassilia
Hanane-Laassilia force-pushed the maximo/otel-http-client-semantics branch from 9df533c to 2353c5d Compare October 8, 2026 15:46
gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Oct 9, 2026
## Description

Adds the shared foundation for emitting OpenTelemetry HTTP semantic conventions when `DD_TRACE_OTEL_SEMANTICS_ENABLED=true`.

This PR does **not** change HTTP integrations yet. The client and server integrations start using this foundation in #20009 and #20010.

This is the first part of the implementation for #19501.

### Configuration

`DD_TRACE_OTEL_SEMANTICS_ENABLED` remains disabled by default. When enabled, it enforces the settings required by the OTel HTTP semantics path:

- OTLP trace export
- Span attribute schema `v0`
- Peer-service defaults disabled
- Native span events enabled for OTLP export

Conflicting settings are overridden and reported through logs and telemetry.

### Shared HTTP semantics

The implementation lives under `ddtrace/_trace/otel/http/`:

- `tags.py` handles HTTP method normalization, URL attributes, server address and port extraction, query-string handling, status codes, and client/server error classification.
- `resource.py` generates low-cardinality HTTP resource names and prevents later instrumentation updates from overwriting resources set by user code.

`OTelHTTPSpanAttributes` is shared by the upcoming client and server layers. Those layers will instantiate it from `set_http_meta()` and provide metadata as it becomes available during the request lifecycle.

For example:

```python
attributes = OTelHTTPSpanAttributes(span, integration_config)
attributes.set_method("get")
attributes.set_url("https://example.com/users/42")
attributes.set_status_code(200)
attributes.set_resource("/users/{id}")
```

This produces:

```text
http.request.method = "GET"
http.request.method_original = "get"
url.scheme = "https"
url.path = "/users/42"
http.response.status_code = 200
resource = "GET /users/{id}"
```

Recreating the helper during later metadata phases is supported: method, resource, and status-error ownership are stored on the span.

### OTel requirements covered

The behavior follows the [OpenTelemetry HTTP span conventions](https://opentelemetry.io/docs/specs/semconv/http/http-spans/).

| Scenario | OTel behavior | Covered behavior |
| --- | --- | --- |
| Method `get` | Normalize the method and preserve its original spelling | Emits `http.request.method="GET"` and `http.request.method_original="get"` |
| Unknown method `PROPFIND` | Emit `_OTHER` and retain the original method | Emits `_OTHER`, preserves `PROPFIND`, and uses `HTTP` for resource naming |
| Server route `/users/{id}` | Use the low-cardinality route template instead of the raw request path | Refines the resource to `GET /users/{id}` |
| Client URL containing credentials | Redact user information from `url.full` | Emits `https://REDACTED:REDACTED@example.com/search` |
| Client 4xx response | Mark the span as an error | Sets `span.error=1` and a string status-derived `error.type` |
| Server 4xx response | Do not mark the span as an error by default | Leaves `span.error=0` |
| Server 5xx response | Mark the span as an error | Sets `span.error=1` |

Existing Datadog query capture, query obfuscation, and custom server error-range settings remain supported. Exception information takes precedence over status-derived error metadata.

### Resource ownership

Resource naming distinguishes instrumentation-generated values from user replacements:

1. Instrumentation may progressively refine a resource as more metadata becomes available.
2. If user code replaces that resource, subsequent instrumentation updates preserve the user value.
3. Raw request paths are never used as OTel server resources; route templates are used when available.

WebSocket resources are left unchanged to avoid collapsing them to the unknown-method resource name.

## Testing

Unit coverage includes:

- Method normalization and stale `method_original` cleanup
- Client and server URL attributes
- Credential redaction and query-string obfuscation
- Malformed URLs
- Client/server status-code error classification
- Repeated metadata updates
- Exception preservation
- User-owned resource preservation
- Schema and peer-service overrides
- OTLP writer selection and native span events

End-to-end OTLP snapshots are added later in the stack because they depend on the harness introduced in #20688:

- #20689: HTTP client snapshots
- #20690: HTTP server and AppSec snapshots

## Risks

The feature remains opt-in. This PR configures the enabled mode and adds the shared implementation, but it does not change emitted HTTP integration spans until the client and server follow-up PRs are merged.

## PR stack

1. #20008 — shared configuration and HTTP semantics foundation
2. #20688 — OTLP snapshot harness
3. #20009 — HTTP client integration
4. #20689 — client configuration and snapshots
5. #20010 — HTTP server/framework and AppSec integration
6. #20690 — AppSec block-path attributes and server snapshots

Co-authored-by: Hanane-Laassilia <hanane.laassilia@datadoghq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos ai-generated PR created with AI assistance OpenTelemetry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants