Skip to content

Map OpenTelemetry tag names via the registry (pass-through default) - #12230

Draft
dougqh wants to merge 1 commit into
dougqh/generator-v2from
dougqh/otel-names-v2
Draft

Map OpenTelemetry tag names via the registry (pass-through default)#12230
dougqh wants to merge 1 commit into
dougqh/generator-v2from
dougqh/otel-names-v2

Conversation

@dougqh

@dougqh dougqh commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What

Give every known tag an OpenTelemetry name derived from the tag registry, so a serializer can emit Datadog spans under OTel semantic-convention names without a separate mapping table. Draft direction-demo for the OTel team, based on #12047 so their follow-on PR shows only the OTel delta.

Namespace model

otel-name is optional and tri-state:

YAML Meaning OTel name
absent pass-through (RFC "retain") = dd-name
a value rename that value
none Datadog-only (reserved, 0 today) suppressed¹

¹ none has zero instances today and currently behaves as pass-through; true suppression is a follow-on.

dd-name is the canonical Datadog name and the tag identity. keyOf is many→one — any name (canonical or OTel) resolves to the one id; nameOf still returns the canonical name. openTelemetryNameOf(id) returns the rename or null; OtlpTraceProto falls back to dd-name on null, so pass-through needs no per-tag data on the wire path.

Notable decisions

  • No OTEL_APPLICABLE id bit — with pass-through as the default, every known tag today is emitted under OpenTelemetry, so the flag would be constant (dropped as a false generalization). It returns once a none (Datadog-only) tag exists.
  • Build-time guardrail — fails when one canonical name is declared with conflicting otel-names across span types/mixins. A span-kind-dependent mapping (server http.url) is a derivation, not a rename — deferred to the derivation layer.
  • YAML keys renamed tag:dd-name: and open-telemetry-nameotel-name.
  • Per the DDOT HTTP semconv RFC: http.useragentuser_agent.original; http.url stays url.full (client-correct), server split deferred.

Test

generate + spotless + verify clean; internal-api + dd-trace-core compile clean; KnownTagsTest and TagMapDenseFuzzForkedTest pass.

🤖 Generated with Claude Code

@dougqh

dougqh commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@dougqh dougqh added comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring labels Aug 18, 2026

@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: b12ee865ab

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +216 to +217
String otelName = tagEntry.openTelemetryName();
String key = otelName != null ? otelName : tagEntry.tag();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Activate the registry before resolving OTLP tag names

With the default trace.experimental.dense.tags.enabled=false, the only production call to KnownTags.init() remains guarded by DENSE_TAGS_ENABLED in CoreTracer, so KnownTagCodec is inactive here. Consequently tagEntry.openTelemetryName() returns null for every tag and normal OTLP exports continue emitting http.method, http.status_code, and the other Datadog names instead of the mappings introduced by this change; only users enabling the experimental dense-tag store see the new behavior. Registry name resolution needs to be initialized independently of whether dense storage is enabled.

Useful? React with 👍 / 👎.

Comment thread tag-conventions.yaml Outdated
- { tag: servlet.path, type: string, required: optional }
- { tag: servlet.context, type: string, required: optional }
- { dd-name: http.url, type: string, required: required, otel-name: url.full } # NOTE: single http.url identity (shared w/ http.client) => one otel-name. url.full is the client-correct rename; server's spec mapping (url.path + url.scheme + url.query) is a one-to-many split reserved for the derivation layer (needs span.kind). TODO(otel): server split.
- { dd-name: http.route, type: string, required: conditional, otel-name: http.route } # same name in both namespaces

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update the stale HTTP route namespace test

Declaring http.route as its own OpenTelemetry name makes the generated resolver return "http.route", but KnownTagsTest.tagsWithoutOtelNameReturnNull still asserts that openTelemetryNameOf(KnownTags.HTTP_ROUTE_ID) is null. The internal-api test suite therefore fails deterministically once this generated registry is tested; update that assertion and include the same-name mapping among the applicable-tag cases.

Useful? React with 👍 / 👎.

Comment on lines +215 to +216
require(!requireOtelName || m.containsKey("otel-name")) {
"tag '$ddName' must declare otel-name (a name, the same name, or 'none')"

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 Validate the otel-name value instead of only its presence

For a stored tag written as otel-name: with no value, SnakeYAML supplies a present key whose value is null, so this check passes even though the documented tri-state permits only a nonempty name or the literal none; the subsequent safe cast silently converts that invalid value into a Datadog-only tag. Non-string values are accepted the same way, allowing a typo in this registry to bypass the intended build-time guardrail and generate an incorrect applicability bit. Require a valid string value and explicitly recognize none, rather than checking only containsKey.

Useful? React with 👍 / 👎.

@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Aug 18, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 50.00%
Overall Coverage: 53.27% (-4.11%)

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

@dd-octo-sts

dd-octo-sts Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.94 s 13.90 s [-0.6%; +1.2%] (no difference)
startup:insecure-bank:tracing:Agent 12.91 s 13.01 s [-1.6%; +0.1%] (no difference)
startup:petclinic:appsec:Agent 16.78 s 16.60 s [+0.3%; +1.9%] (maybe worse)
startup:petclinic:iast:Agent 16.83 s 16.96 s [-1.8%; +0.2%] (no difference)
startup:petclinic:profiling:Agent 16.56 s 16.70 s [-1.7%; +0.0%] (no difference)
startup:petclinic:sca:Agent 16.89 s 16.81 s [-0.4%; +1.4%] (no difference)
startup:petclinic:tracing:Agent 16.05 s 16.23 s [-2.2%; -0.0%] (maybe better)

Commit: 28080337 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

@dougqh
dougqh force-pushed the dougqh/otel-names-v2 branch from b12ee86 to d8ce773 Compare August 18, 2026 18:51
@dougqh dougqh changed the title Add OpenTelemetry-namespace applicability to the tag registry Map OpenTelemetry tag names via the registry (pass-through default) Aug 18, 2026
Give every known tag an OpenTelemetry name derived from the registry,
so a serializer can emit DD spans under OTel semantic-convention names
without a separate mapping table.

- otel-name is OPTIONAL and tri-state: absent means the OTel name is
  implicitly the dd-name (pass-through / RFC "retain"); a value renames;
  the literal `none` reserves a Datadog-only tag (suppressed from OTel).
  `none` has zero instances today and currently behaves as pass-through.
- dd-name is the canonical Datadog name AND the tag identity. keyOf is
  many->one: any name (canonical or OTel) resolves to the one id; nameOf
  still returns the canonical name.
- openTelemetryNameOf(id) returns the RENAME or null; OtlpTraceProto
  falls back to the dd-name on null, so pass-through needs no per-tag
  data on the wire path.
- Rename the YAML keys tag: -> dd-name: and open-telemetry-name ->
  otel-name.
- Guard tag identity: fail the build when one canonical name is declared
  with conflicting otel-names across span types/mixins (a span-kind-
  dependent mapping is a derivation, not a rename).
- No OTEL_APPLICABLE id bit: with pass-through as the default every known
  tag today is emitted under OpenTelemetry, so the flag would be constant
  (dropped as a false generalization). It returns as a follow-on once a
  Datadog-only (`none`) tag exists.
- Per the DDOT HTTP semconv RFC: http.useragent -> user_agent.original;
  http.url stays url.full (client-correct) with server's split deferred
  to the derivation layer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dougqh
dougqh force-pushed the dougqh/otel-names-v2 branch from d8ce773 to 2808033 Compare August 18, 2026 20:14
@dougqh
dougqh force-pushed the dougqh/generator-v2 branch from 19222e1 to 33f32c4 Compare August 18, 2026 20:14
@dougqh

dougqh commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 2808033700

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tag-conventions.yaml
- { tag: http.status_code, type: int, required: conditional, open-telemetry-name: http.response.status_code }
- { tag: network.protocol.version, type: string, required: recommended }
- { dd-name: http.method, type: string, required: required, otel-name: http.request.method }
- { dd-name: http.status_code, type: int, required: conditional, otel-name: http.response.status_code }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Apply the rename to first-class HTTP status values

When an HTTP status is set through AgentSpan.setHttpStatusCode or the TagInterceptor, it is stored in Metadata.httpStatusCode, not metadata.getTags(). OtlpTraceProto.MetaWriter.accept emits that value through the writeSpanTag(buf, HTTP_STATUS, ...) overload, bypassing the new EntryReader.openTelemetryName() projection, so ordinary spans still export http.status_code rather than the mapping declared here. Fresh evidence beyond the earlier resolver-initialization comment is that the resolver is now initialized unconditionally, but this first-class metadata path never consults it.

Useful? React with 👍 / 👎.

Comment on lines +214 to +215
String otelName = tagEntry.openTelemetryName();
String key = otelName != null ? otelName : tagEntry.tag();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update OTLP tests to expect renamed attributes

With the registry activated by the static CoreTracer in OtlpTraceProtoTest, this lookup changes the http.method test tags at lines 363 and 378 into http.request.method. However, verifySpan at lines 996-1000 still requires every original extraTags key to be present, so the string-tag and mixed-tag parameterized cases fail deterministically instead of validating the new output namespace.

Useful? React with 👍 / 👎.

Comment on lines +214 to +215
String otelName = tagEntry.openTelemetryName();
String key = otelName != null ? otelName : tagEntry.tag();

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 Deduplicate aliases before emitting renamed attributes

With the default KnownTagCodec.DENSE_STORE=false, TagMap.set keeps a Datadog name and its OpenTelemetry alias as separate bucket entries. If a span contains both http.method and http.request.method—for example when Datadog and OpenTelemetry instrumentation both contribute attributes—this projection maps both entries to http.request.method, and MetaWriter emits two attributes with the same key and potentially conflicting, iteration-order-dependent values. These keys remained distinct before this change; the exporter needs to canonicalize or deduplicate projected names independently of the experimental dense store.

Useful? React with 👍 / 👎.

Comment on lines +185 to +186
default String openTelemetryName() {
return KnownTagCodec.openTelemetryNameOf(tagId());

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 Reuse the known tag ID during dense serialization

perf: When dense tags are enabled, MetaWriter.accept reaches this method through TagMap.forEach for every serialized tag, but that dense iteration path initializes EntryReadingHelper with only nameOf(knownIds[i]). Calling tagId() here therefore hashes and probes keyOf(name) again for every emitted dense tag, even though knownIds[i] is already available and the existing three-argument EntryReadingHelper.set overload can retain it. This is mechanism-determined repeated work on the per-span serialization path; pass the known ID through and verify the improvement with the serialization benchmark.

AGENTS.md reference: AGENTS.md:L79-L81

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant