test(otel): OTel HTTP semantics coverage against the cross-tracer RFC - #7455
test(otel): OTel HTTP semantics coverage against the cross-tracer RFC#7455link04 wants to merge 30 commits into
Conversation
…tests Add an end-to-end scenario and cross-language tests asserting that HTTP server and client spans honor the OpenTelemetry HTTP semantic conventions when DD_TRACE_OTEL_SEMANTICS_ENABLED=true. - OTEL_SEMANTICS EndToEndScenario sets DD_TRACE_OTEL_SEMANTICS_ENABLED=true - tests/test_otel_http_semantics.py validates the OTel attribute names are emitted and the legacy Datadog names are absent (the flag is mutually exclusive), for both server (url.path/url.scheme) and client (url.full, no url.path/url.query) spans - manifests: dotnet and java left active; other languages marked missing_feature until their tracers ship the flag Validates DataDog/dd-trace-dotnet#8791 and DataDog/dd-trace-java#11652. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ked vs tracer PRs Verified assertions against the actual implementations in dd-trace-dotnet#8791 (SpanMetadataOTelRules.cs, HttpOtelHelper.cs) and dd-trace-java#11652 (OtelHttpSemantics.java): - server url.query test (was http.query.string) — present when a query is sent - server error.type test on 5xx — set to the status-code string. java emits this; dd-trace-dotnet does not map error status to error.type, so it is gated missing_feature for dotnet (per-language manifest) - server client.address / network.peer.address validated only when present (Recommended-level), with a clarifying note on the requirement-level approach - left client server.port as an if-present check: dotnet omits default ports and java falls back to 80/443, so always-present would be wrong Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round out the OTel HTTP semantic-convention coverage:
- server http.route: asserts the low-cardinality route template (not the raw
URL path), using the established /sample_rate_route/{i} endpoint. http.route
is unchanged by the feature but the spec requires it be a template.
- client error.type on a 5xx distant call (java emits; gated missing_feature
for dd-trace-dotnet, which does not map error status to error.type).
- network.peer.address / network.peer.port validated when present (Recommended)
on both server and client spans.
15 tests total (9 server, 6 client). ruff + manifest validator + meta-tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s-meta ports
Verified the resource/span-name behavior across all three tracer PRs
(dd-trace-dotnet#8791, dd-trace-java#11652, dd-trace-js#8933):
- test_resource_name: the server entry-span resource is the low-cardinality
"{method} {http.route}", never the raw URL path. java rewrites the resource to
this form; dotnet and nodejs leave the already-compliant DD resource unchanged.
Expected value derived from the same span's http.route so per-tracer template
syntax ({i} / {i:int} / :i) cancels out. Gated on the root web span so the java
spring.handler child (also type==web) is excluded.
- nodejs activated: dd-trace-js#8933 implements the full feature including
error.type, so its missing_feature gates are removed (now dotnet+java+nodejs).
- numeric attributes (server.port, network.peer.port) read from meta OR metrics:
dd-trace-js routes numerics into metrics, java/dotnet keep them in meta strings.
16 tests total. ruff + manifest validator + meta-tests green; collect-only passes
for both dotnet and nodejs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation)
Principle: the system-tests are the authoritative harness — assert exactly what
the OTel HTTP spec requires and record tracer divergences as manifest gates
rather than weakening the assertions. Behavior verified against all three PRs
(dd-trace-dotnet#8791, dd-trace-java#11652, dd-trace-js#8933).
- client server.port: now asserted present (Required per spec; the distant-call
URL uses a non-default port so every compliant tracer emits it).
- test_no_route_resource: resource is the bare "{method}", never the URL path.
java + js compliant; dd-trace-dotnet keeps the path -> bug gate.
- test_request_method_normalization: unknown method -> http.request.method=_OTHER
+ http.request.method_original. Only java implements it; dotnet + js -> missing_feature.
- test_other_method_span_name: per spec the _OTHER resource must be "HTTP". No
tracer complies yet (java keeps raw method -> bug; dotnet/js -> missing_feature) —
kept as a fully-gated spec anchor that activates as tracers comply.
19 tests total. ruff + manifest validator + meta-tests green; collect-only passes
for dotnet, java, and nodejs.
Note: the unknown-method tests are the first in system-tests to send a non-standard
verb; verified by static analysis, pending a live OTEL_SEMANTICS run to confirm each
weblog produces a span for an unknown method (else gate that language incomplete_test_app).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…query-drop) Running OTEL_SEMANTICS against dd-trace-js#8933 locally revealed that the express weblog's /make_distant_call drops the target URL's query string, so a distant call to /status?code=500 actually hit /status (no code) and returned 400. The PR was correct (it set error.type="400"); the test was hardcoded to "500". Fix: distant-call an unmatched route (/no_such_route_xyz -> 404, no query needed) and assert error.type equals the span's own status code for any 4xx/5xx client response. Verified end-to-end vs dd-trace-js#8933 (nodejs@6.0.0-pre, express4): 17 passed, 2 xfailed (the _OTHER method-normalization tests, correctly gated missing_feature for nodejs), 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…crash
Two things:
1. OTEL_SEMANTICS_OTLP scenario — the OTel-semantics flag plus the APM_TRACING_OTLP
exporter env, so HTTP attributes are validated as *typed* OpenTelemetry values
(http.response.status_code / server.port as int; method / url.* / server.address /
user_agent.original as string) which the Datadog agent protocol cannot represent.
- tests/test_otel_http_semantics_otlp.py reads typed attributes via
interfaces.open_telemetry.get_otel_spans().
- utils/proxy/traces/otlp_v1.py: coerce OTLP intValue to a Python int so the type is
comparable regardless of JSON vs binary OTLP encoding.
- Verified e2e vs dd-trace-js#8933 (nodejs@6.0.0-pre): server+client names and types
pass; status_code-is-int xfails -> the PR emits http.response.status_code as a STRING
instead of an int (finding for the JS PR; the DD-agent path masks this).
- Gated the new classes missing_feature for languages where the feature is unimplemented
(python/golang/php/ruby/rust) or OTLP is unverified (dotnet/java); nodejs active.
2. Fix the "Test the test" CI crash: two manifest entries used `bug (<text>)` with a
non-JIRA reason. add_pytest_marker -> _ensure_jira_ticket_as_reason calls pytest.exit
during collection, which aborted the whole TEST_THE_TEST run (surfacing as an unrelated
test_library_conf remote-config INTERNALERROR). Changed them to `missing_feature`
(dotnet test_no_route_resource, java test_other_method_span_name). TEST_THE_TEST now
collects and deselects correctly (288 passed, 2311 deselected locally).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ate OTLP int tests The HTTP tracer PR now emits http.response.status_code as a numeric metric (int-typed, so OTLP serializes it as intValue) rather than a meta string — which is what the spec requires. Update the DD-agent server/client test_response_status_code and test_error_type to read the value via the existing _numeric_tag(meta-or-metrics) helper instead of meta only, and compare error.type against the stringified int. Now that the tracer emits an int, the OTLP test_status_code_is_int checks pass, so drop their nodejs missing_feature gates. Verified by replay against the HTTP+DB tracer build: OTEL_SEMANTICS 19 passed, 2 xfailed (method-normalization gates) OTEL_SEMANTICS_OTLP 8 passed Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elopment dd-trace-dotnet branches merging
…ef_6_0_0)
dd-trace-js#8933 (DD_TRACE_OTEL_SEMANTICS_ENABLED HTTP semantics) merged to
master (semver-minor) and ships in the upcoming v6 release; master is
6.0.0-pre and the released v5.x line does not have it. Leaving the nodejs
tests un-gated would fail them on prod/5.x runs.
Gate the four HTTP semantic-convention test classes to *ref_6_0_0
('>=6.0.0-pre'), the established convention for master-only features (cf.
Test_Startup_Logs::test_startup_logs_default). This means:
- dev pipeline (dd-trace-js#master == 6.0.0-pre): tests run and must pass
- prod/5.x: tests xfail cleanly (feature legitimately absent) -> CI green
- auto-activates once 6.0.0 is released
Other languages remain missing_feature (not yet implemented). The two
server method-normalization tests stay missing_feature -- that behavior
isn't implemented even on master (they xfail on 6.0.0-pre too).
Verified by replay against the 6.0.0-pre build:
OTEL_SEMANTICS 19 passed, 2 xfailed
OTEL_SEMANTICS_OTLP 8 passed
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # manifests/golang.yml # manifests/python.yml
The OpenTelemetry HTTP semantic-convention suite needs /sample_rate_route/<i>, /status, /make_distant_call, and a root route that accepts non-GET methods. The express4-otel and flask-poc-otel weblogs only had "/" on GET, so the suite could not be pointed at an upstream OpenTelemetry SDK at all. Paths and query parameter names are kept identical to the Datadog express and flask weblogs so the same test code drives both.
OTEL_SEMANTICS_STATS covers the RFC requirement that the span error flag and the client-side trace stats agree. It is a separate scenario rather than extra env on OTEL_SEMANTICS because turning on stats computation also turns on client-side P0 dropping, which would break the span assertions. OTEL_SEMANTICS_UPSTREAM_SDK runs the same behavior tests against an upstream OpenTelemetry SDK weblog instead of a Datadog tracer. It is a measurement, not a release gate: an assertion an upstream SDK fails is either a bug in the assertion or a place where the RFC deviates from upstream behavior. OTEL_SEMCONV_STABILITY_OPT_IN=http is required, otherwise the SDKs still emit the pre-1.0 attribute names and every assertion fails for the wrong reason. The test_the_test updates keep the legacy scenario matrix and the tracer_release group rule in agreement with the new scenario.
Four classes of defect, plus the tests the RFC requires and did not have. Span names are now derived from the attributes the span itself reports rather than hardcoded. The RFC's system-tests section excludes the server span's http.route value and name value from the cross-tracer contract as framework-specific, so a hardcoded "GET /users" contradicted the RFC it was meant to enforce. Deriving keeps the assertion framework-neutral and still catches the two real bugs: falling back to the URI path as a target, and substituting the whole name instead of only the method token for _OTHER. test_span_name_unknown_method asserted the name was exactly "HTTP". That is wrong for any framework that resolves a route, since _OTHER substitutes only the method token. It now derives, and separately asserts the raw verb never reaches the name. Int-typed attributes go through _assert_int_attribute, which asserts where the key lives and what type it has, and that it is never in both meta and metrics. A bare equality check against an int silently required the value to be a number in metrics on the agent path, encoding non-standard behavior as the contract. New tests for RFC requirements that had none: DD_TRACE_HTTP_*_ERROR_STATUSES taking precedence in both directions, http.request.method_original on a case-variant method, server.port as an intValue, http.endpoint retained under the flag including on OTLP, span kind, route invariance across parameter values, and span error versus trace stats consistency.
_client_span matched on "is a CLIENT span to weblog:7777", which is true of every distant call in the file. An OTLP payload is a batch, so on a real SDK it holds spans from unrelated requests and the lookup returned whichever distant call happened to be first in the batch. Four client tests were asserting against another test's span and reporting status 200 for a request that asked for 400, 500, or 302. Pin the lookup to the trace of the request-correlated server span.
/make_distant_call always issued a GET, so the client-side semantic convention tests could not drive a non-standard verb through the client instrumentation and test_span_name_unknown_method looked like a tracer bug when it was really a weblog gap. The nodejs express weblog already accepts a method query param; django, flask, fastapi and tornado now do the same. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
http.endpoint is the endpoint-aggregation fallback a tracer emits only when the framework resolved no route, and every tracer gates it on DD_TRACE_RESOURCE_RENAMING_ENABLED. The two "retained under the flag" tests asked for a routed path with the option unset, so the attribute could never be present and both tests failed on every tracer for a reason that had nothing to do with the OTel semantics flag. Set the option in the OTEL_SEMANTICS and OTEL_SEMANTICS_OTLP scenarios and request a path with no route. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
manifests/python.yml disabled the whole suite with a blanket "DD_TRACE_OTEL_SEMANTICS_ENABLED not implemented" declaration, which hid the fact that most of it already passes. Replace it with per-test entries for the seven cases that really fail, each with the measured reason. manifests/nodejs.yml gets the same weblog_declaration shape for Test_OtelSemantics_OTLP_Spans_Http_ErrorStatusConfiguration as its sibling classes, since the four tests xpass, and an incomplete_test_app entry for the lowercase-method case. Both languages declare test_method_original_on_case_variant incomplete_test_app: the HTTP server uppercases the method before any instrumentation hook runs, so no tracer can see the original token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The OTEL_SEMANTICS_UPSTREAM_SDK scenario existed but only java_otel, nodejs_otel and python_otel could run it, so the measurement had a hole where .NET should be. This adds the fourth. The weblog is a minimal-API ASP.NET Core 8 app running the real upstream distro, installed from otel-dotnet-auto-install.sh and activated by sourcing instrument.sh. Its endpoints mirror flask-poc-otel and express4-otel path for path and query parameter for query parameter, including honouring ?method= on /make_distant_call, because a weblog that answers a slightly different request turns a cross-language comparison into noise. Registering a library touches five places plus the CI orchestrator self-check, which carries its own hardcoded list of otel weblogs. Run: 30 passed, 7 failed of 37, the best of the three upstream SDKs, and the only client class that comes back clean. It also surfaces something new: .NET emits url.query with the leading "?" attached and every value replaced by "Redacted", which fails url_query_present_with_query_string and makes both obfuscation tests pass for a reason unrelated to what they assert.
… emits A tracer that has converted its HTTP client spans but not its server spans still reports the system-tests request id under http.useragent, not user_agent.original. _server_span only looked at user_agent.original, so for that tracer it fell through to an arbitrary SERVER span in the batch, and _client_span then searched the wrong trace and found nothing. Every client assertion failed for a reason that had nothing to do with the client.
Test_OtelSemantics_OTLP asserted on the server span and the client span inside the same test in four of its five tests. A tracer converts one span kind at a time, so that shape reports a tracer which has finished half the work as having finished none of it, and gives whoever is doing the other half no signal. dd-trace-dotnet is the live example: client-side work only, scored 1/5 on this class while every one of the four failures was the server span. Now Test_OtelSemantics_OTLP_Server (5) and Test_OtelSemantics_OTLP_Client (4). server.port and http.response.status_code stay the only two attributes typed as int, which is what the RFC's attribute tables specify for both span kinds.
The span name for an unaccepted method was asserted as the literal "HTTP", which is what the HTTP semconv prose asks for. The decision taken with the PM and the OTel team is to adopt the collector's set_semconv_span_name algorithm instead, since the Datadog backend discards OTLP Span.Name anyway and the question is what non-Datadog backends consume. That algorithm's httpSpanName reads http.request.method and one target key, http.route for SERVER and url.template for CLIENT, and returns method + " " + target or bare method. It substitutes nothing, so an unaccepted method names the span _OTHER /users, never HTTP /users. dd-trace-js currently emits the HTTP literal and passed the old assertion, so it now fails this test until it follows the agreed algorithm.
It asserted that a lowercase "get" on the wire is reported through http.request.method_original. The token never reaches the wire: the harness builds requests with requests.Request(...).prepare(), and PreparedRequest.prepare_method uppercases it. Every implementation measured failed it identically, all three Datadog branches and all three upstream OTel SDKs, which is the signature of a harness bug rather than six tracers being wrong. Nothing is lost. http.request.method_original is still asserted on both the server and the client span by test_span_name_unknown_method. What the deleted test uniquely covered was case sensitivity, and the RFC has no rule for it: lines 142 and 186 define the accepted set as the RFC 9110 verbs plus PATCH and QUERY without saying whether the comparison is case-sensitive, and the RFC's own test tables do not list this test. If we want the rule, it belongs in the RFC first, and the collector's naming algorithm never reads method_original anyway.
|
- silence shellcheck SC1091 on the dotnet_otel entrypoint - declare Test_OtelSemantics_Stats_Consistency in every manifest, not just python - gate the python activation on v4.15.0-dev - keep p0 traces in OTEL_SEMANTICS_STATS so the 200 span survives - wire the five new scenarios into run-end-to-end.yml - fail loudly instead of picking a stranger when no OTLP server span correlates - require url.full to exist before asserting credentials were redacted - normalize an attribute-less OTLP span to a dict, and dedupe spans across payloads - keep poc-otel out of OTEL_INTEGRATIONS and spring-boot-otel out of the semantics suite Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Upstream-SDK comparison is covered elsewhere, so drop the scenario, the dotnet_otel poc-otel weblog, and the plumbing that only existed to support them.
DD_TRACE_HTTP_CLIENT_ERROR_STATUSES is out of scope for the tracer PRs, so no tracer reads it. Under the flag the client error range is fixed at 400-599, which the client class already asserts. The server setting is pre-existing and its two tests stay. 46 tests to 44.
dd-trace-js has no DD_TRACE_HTTP_SERVER_ERROR_STATUSES, so the scenario hands it a range it cannot read and both assertions fail. Declare it rather than leave it red; adding the setting to the tracer is separate work. Also correct the class docstring: DD_TRACE_HTTP_CLIENT_ERROR_STATUSES does exist, in Java, .NET, Go, Ruby and PHP. It is Python and Node.js that lack it.
libdatadog only turns client-side stats on when the agent advertises client_drop_p0s, so with it False a tracer built on libdatadog computed no stats at all and the agent computed them instead. The scenario was reading the agent's numbers and reporting them as the tracer's, which is the one thing it exists to check. dd-trace-py sent no v0.6/stats payload at all under it. Safe to advertise because the scenario pins the sampling rate to 1, so every trace is p1 and none of the spans these tests read can be dropped, which was the original reason for turning it off. Also drops five python manifest entries that are no longer true: the naming ones said dd-trace-py deliberately does not remap the resource under the flag, which it now does, and the stats one blamed libdatadog's concentrator, which now reads the OTel attribute names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two Stats_Consistency tests pass against a native extension rebuilt by hand at the libdatadog patch rev. In CI the python weblog installs a wheel that compiles src/native against the pinned libdatadog v38.0.0, which predates the fix, so dropping the declaration would have turned both tests red on this PR. Reinstated with the dependency named rather than as a bare missing_feature, so whoever bumps the pin knows this is the thing to re-enable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Motivation
End-to-end coverage for the OTel HTTP semantic conventions the tracers are adding behind
DD_TRACE_OTEL_SEMANTICS_ENABLED, checked against the cross-tracer RFC rather than against whatever any one tracer currently emits.This supersedes #7139, which was closed unmerged. This branch is cut from its head, so its commits are included here.
Changes
31 tests to 44, across six classes: 20 server, 11 client, 5 OTLP-only server, 4 OTLP-only client, 2 error-status config, 2 stats.
One new scenario:
OTEL_SEMANTICS_STATS, separate rather than extra env onOTEL_SEMANTICS, becauseDD_TRACE_COMPUTE_STATSalso turns on client-side P0 dropping, which would break every span assertion in the existing scenario.Substance is in
tests/test_otel_http_semantics.py: derived span names, a helper that reads an int attribute from eithermetaormetricsdepending on export path, shared legacy-key tuples, and the server/client split of the OTLP-only class.What is in the suite, scenario by scenario
44 tests in
tests/test_otel_http_semantics.py, six classes, four scenarios. Two classes are shared betweenOTEL_SEMANTICSandOTEL_SEMANTICS_OTLP, so the same 44 tests produce 75 executions per weblog.OTEL_SEMANTICSDD_TRACE_CLIENT_IP_ENABLED,DD_TRACE_RESOURCE_RENAMING_ENABLED, obfuscation regexpOTEL_SEMANTICS_OTLPDD_TRACE_OTEL_ENABLEDand the OTLP exporter pointed at the proxyOTEL_SEMANTICS_OTLP_CUSTOM_ERROR_STATUSESDD_TRACE_HTTP_SERVER_ERROR_STATUSES=200OTEL_SEMANTICS_STATSDD_TRACE_COMPUTE_STATS,DD_TRACE_FEATURES=discovery,client_drop_p0s=FalseEvery class asserts against the same span twice when it runs in both scenarios: once as Datadog MsgPack
meta, once as OTLP attributes. That is deliberate, it is how the two-representation rule gets checked without duplicating the test bodies.Test_OtelSemantics_Spans_Http_Server(20):OTEL_SEMANTICS,OTEL_SEMANTICS_OTLPtest_otel_attributes_presentGET /http.request.method,url.path,url.scheme, status code all present with the right valuestest_legacy_attributes_absentGET /+X-Forwarded-Forhttp.method,http.url,http.status_code,http.useragent,http.client_ip,http.hostname,network.client.ip,http.query.stringsurvive, inmetaandmetricstest_span_name_with_routeGET /sample_rate_route/1{method} {http.route}, derived from the attribute rather than hardcodedtest_span_name_without_routeGET /no_such_route_xyz{method} {route}conditioned on whatever route the span itself reports, so a framework catch-all still passes, and the URI path never appears in the nametest_span_name_unknown_methodPROPFIND /_OTHER,http.request.method_originalkeepsPROPFIND, and the raw verb never appears in the span nametest_span_name_route_invariance/sample_rate_route/1vs/99999test_status_500_no_exception_is_status_code_errorGET /status?code=500error.typeset to the status as a stringtest_status_400_is_not_errorGET /status?code=400test_status_3xx_is_not_errorGET /status?code=302test_url_query_present_with_query_stringGET /?otel=visibleurl.querypresent, without the leading?test_url_query_absent_without_query_stringGET /url.queryabsent rather than empty-stringtest_url_query_obfuscationGET /?token=<sensitive>url.query, value replaced byredactedtest_user_agentGET /user_agent.originalcarries the request UAtest_client_addressGET /+X-Forwarded-Forclient.addressresolves from the forwarded header, requiresDD_TRACE_CLIENT_IP_ENABLEDtest_network_peer_addressGET /network.peer.addressis the socket peer, distinct fromclient.addresstest_server_addressGET /server.addressis the host the request was sent totest_server_portGET /server.portis7777, int on OTLP, string inmeta, required onceserver.addressis settest_http_endpoint_retainedGET /no_such_route_xyzhttp.endpointfallback is not stripped by the flagtest_span_kind_is_serverGET /span.kind=server/ OTLPSERVERtest_http_route_retainedGET /sample_rate_route/1http.routeis present and is a template, not the raw path with the parameter baked inTest_OtelSemantics_Spans_Http_Client(11):OTEL_SEMANTICS,OTEL_SEMANTICS_OTLPAll of these drive the weblog's outbound
/make_distant_callendpoint back at itself.test_otel_attributes_presentGEThttp.request.method,url.full,server.address,server.port, status codetest_legacy_attributes_absentGEThttp.method,http.url,http.status_code,out.host,peer.hostname,network.destination.nameall gone,metaandmetricstest_url_path_absent_on_clienturl.fullpresent andurl.path/url.scheme/url.queryabsent, the client table isurl.fullonlytest_span_name_is_methodGET{method}(no tracer emitsurl.template), and specifically notHTTP GET, which is what upstream otelhttp producestest_span_name_unknown_methodPROPFIND_OTHER+method_original, raw verb absent from the nametest_span_kind_is_clientGETspan.kind=client/ OTLPCLIENTtest_status_400_is_error/status?code=400test_status_500_is_error/status?code=500test_status_3xx_not_error/status?code=302test_url_full_credential_redactionuser:pass@url.full; if the HTTP API kept it, both halves readREDACTEDtest_url_full_query_obfuscation?token=<sensitive>url.full, not just tourl.queryTest_OtelSemantics_OTLP_Server(5):OTEL_SEMANTICS_OTLPonlySplit from the client class on purpose: a tracer converts one span kind at a time, and a combined test reports half-done work as not-started.
test_status_code_is_integer_typehttp.response.status_codeis an OTLPintValue, explicitly not adoubleValuetest_server_port_is_integer_typeserver.portis anintValuetest_http_endpoint_retained_otlphttp.endpointsurvives the OTLP export, an OTLP-side filter that strips Datadog-only keys must not take ittest_otel_attributes_present_otlptest_legacy_attributes_absent_otlpTest_OtelSemantics_OTLP_Client(4):OTEL_SEMANTICS_OTLPonlySame four checks as above, on the client span: two integer types, required keys present, legacy keys absent.
Test_OtelSemantics_OTLP_Spans_Http_ErrorStatusConfiguration(2):OTEL_SEMANTICS_OTLP_CUSTOM_ERROR_STATUSESServer spans only.
DD_TRACE_HTTP_CLIENT_ERROR_STATUSESexists in Java, .NET, Go, Ruby and PHP but not in Python or Node.js, so there is nothing to configure on the client side in the two tracers under test.test_server_error_statuses_config_overridesGET /(200)200, a 200 is an error anderror.typeis"200"test_server_error_statuses_config_excludes_500GET /status?code=500Test_OtelSemantics_Stats_Consistency(2):OTEL_SEMANTICS_STATSThree requests each, then waits for a client-side stats payload.
test_stats_agree_with_span_error/status?code=500: the span is an error, a stats bucket exists keyed on the span name withHTTPStatusCode=500, andHits == Errorstest_stats_agree_with_span_success/status?code=200: the span is not an error and the matching bucket records zero errorsWhy this scenario exists: an export-time rename keeps in-process consumers working but lets the span and the stats be computed from two different rules. It also catches the second failure mode, that
http.status_codeis the key the agent's aggregation reads and the flag suppresses it, soHTTPStatusCodesilently becomes 0 unless the tracer routes the numeric status to stats another way.Tag changes under test, for review
Every assertion below is gated on the flag. With it off, nothing in this suite runs.
Server spans
http.methodhttp.request.method,_OTHERfor an unaccepted verb withhttp.request.method_originalcarrying the raw tokenhttp.urlurl.path,url.query(obfuscated),url.schemehttp.status_codehttp.response.status_codehttp.useragentuser_agent.originalhttp.client_ipclient.addressnetwork.destination.ipnetwork.peer.addresshttp.hostserver.address,server.porthttp.routehttp.route, unchangedhttp.endpointhttp.endpoint, unchanged, no OTel equivalentClient spans
http.methodhttp.request.method, same_OTHERrulehttp.urlurl.full, nourl.pathon a client spanhttp.status_codehttp.response.status_codeout.host/out.portserver.address/server.portEach rename is asserted twice: the new name present, and the legacy name absent. The legacy tuples are shared constants so a reviewer can see the whole removal list in one place.
Typing. On OTLP exactly two attributes are ints,
server.portandhttp.response.status_code. That is asserted in the OTLP-only classes. On the Agent/MsgPack path every attribute is a string inmeta, so the same tests read frommetathere. This two-representation rule is why the int helper exists.Span naming.
{method} {http.route}for server,{method} {url.template}for client, bare{method}with no target. This is the OpenTelemetry Collector'sset_semconv_span_name(processor/transformprocessor/internal/traces), agreed with the PM and the OTel team. The collector does no substitution, so an unaccepted method names the span_OTHER /users, notHTTP /users. Worth flagging in review: the HTTP semconv prose saysHTTPthere, and every implementation that normalizes the attribute correctly (dd-trace-js among them) currently emitsHTTP. The collector is the divergent one, and it is what we agreed to follow.Notable fixes inside the branch
_server_spancorrelated the request onuser_agent.originalonly, so for a tracer that has converted its client side but not its server side (dotnet) the lookup missed, fell through to a "first SERVER span in the batch" fallback, and handed every client assertion an unrelated trace id. Reported as 0/11 for a tracer that scores 10/11 on the same span via the Agent path. Fixed by matching whichever user-agent key the tracer emits; correlation is plumbing, not the thing under test. Worth keeping in mind for any shared suite: correlate on something the flag cannot rename.http.endpointtests were asking for a routed path withDD_TRACE_RESOURCE_RENAMING_ENABLEDunset, so the attribute could never exist and all four combinations failed for a reason unrelated to the flag.test_method_original_on_case_variant. It sent a lowercase verb and expectedhttp.request.method_originalto preserve it, but no weblog can ever see a lowercase token:requestsuppercases inprepare_method, Node'shttpserver uppercasesreq.method, WSGI uppercasesREQUEST_METHOD. It failed identically on every weblog, and the RFC states no case-sensitivity rule. If a language whose server does preserve the token needs checking later, the test has to come back with a raw-socket client.