Skip to content

feat(tt): tag server spans with _dd.tt.extraction_sources from RC patterns#11376

Draft
labbati wants to merge 4 commits into
masterfrom
labbati/transaction-tracking-made-easy
Draft

feat(tt): tag server spans with _dd.tt.extraction_sources from RC patterns#11376
labbati wants to merge 4 commits into
masterfrom
labbati/transaction-tracking-made-easy

Conversation

@labbati
Copy link
Copy Markdown
Member

@labbati labbati commented May 15, 2026

What

Tag inbound HTTP server spans with _dd.tt.extraction_sources listing the names of request headers and query-string parameters that match a remote-config-driven pattern list. Intended as the agent-side groundwork for the "transaction tracking made easy" workflow on the DSM side.

While the pattern list is empty (default), this feature is inert: a single volatile read + isEmpty() check on the request hot path, no allocations, no tag.

How

  • Remote config carrier. New optional field tt_extraction_patterns (List<String>) on the existing APM_TRACING dynamic-config product. Wired through TracingConfigPoller.LibConfigDynamicConfigTraceConfig. No new RC product.
  • Pattern matcher. New internal-api class TransactionTrackingPatterns. Hand-rolled *-glob (no java.util.regex): segment walker on *-split chunks, lowercased once at compile time, snapshot held in a volatile List<...>. Multiple * per pattern allowed; case-insensitive on the candidate name.
  • Hook. HttpServerDecorator.onRequest(...) now calls a new forEachRequestHeaderName(REQUEST, Consumer<String>) extension point (default no-op) when the pattern snapshot is non-empty. Matching header names and matching query-string parameter names are collected and emitted as a CSV under _dd.tt.extraction_sources:
    • Format: header:<lowercased-name> / qs:<lowercased-name>, deduplicated per source bucket, sorted alphabetically within each bucket, headers first then qs (deterministic for log-based aggregation and test assertions).
  • Free coverage. Servlet decorators (javax-servlet-3.0 and javax-servlet-2.2) override forEachRequestHeaderName via HttpServletRequest#getHeaderNames(), so Spring WebMVC (3.1 / 5.3 / 6.0) and other servlet-based stacks (Tomcat, Jetty, Undertow when used via servlet) get this transparently. Non-servlet stacks (Netty, Vert.x, WebFlux, …) fall back to the no-op default until per-decorator overrides are added — to be tracked separately.

Pattern syntax is intentionally minimal: * is the only metacharacter (zero-or-more chars). No ?, no character classes.

Why APM_TRACING

Per the design doc, this is the simplest route: zero new wiring on the agent side and zero new product binding on the backend side (the field is additive JSON). Migration to a dedicated APM_TT_* product is a code-only change for the agent and a small overlap window on the backend, deferrable until customer demand for independent targeting materialises.

Tests

  • TransactionTrackingPatternsTest (JUnit5, internal-api): literal / *-prefix / *-suffix / *-middle / multi-* / case-insensitivity / no-match / empty.
  • TracingConfigPollerTest (JUnit5, dd-trace-core): RC update with the field propagates and clears; absent field leaves config untouched.
  • HttpServerDecoratorTtExtractionTest (Spock, agent-bootstrap): mock decorator subclass with canned header names + URL with query string asserts tag presence, exact CSV ordering, source-prefixing, and absence when patterns are empty (fast-path correctness).
  • TtExtractionSpringBootTest (Spock, spring-webmvc-5.3): full Spring Boot test app, real request through Tomcat, asserts _dd.tt.extraction_sources on the resulting server span and asserts absence when patterns are empty.

Targeted Gradle commands (all green locally — see commits for the exact list):

:internal-api:test --tests TransactionTrackingPatternsTest
:dd-trace-core:test --tests TracingConfigPollerTest
:dd-java-agent:agent-bootstrap:test
:dd-java-agent:instrumentation:servlet:javax-servlet:javax-servlet-3.0:test
:dd-java-agent:instrumentation:servlet:javax-servlet:javax-servlet-2.2:test
:dd-java-agent:instrumentation:spring:spring-webmvc:spring-webmvc-5.3:test --tests test.tt.TtExtractionSpringBootTest

Rollout / kill switch

No agent-side feature flag. The feature is inert until the backend pushes a non-empty tt_extraction_patterns. Backend can target by org / service / env / canary; pushing an empty list at any time disables the feature on the next snapshot.

Plan deviations from the design doc

(Surfaced for reviewer awareness; documented in detail in the implementation report.)

  1. The APM_TRACING DTO uses Moshi, not Jackson; I followed the existing pattern (@Json(name = "tt_extraction_patterns")).
  2. There is no jakarta-servlet-5.0 server decorator in this repo — jakarta server flavours live in per-container modules (Tomcat-10, Jetty-11+, Liberty-23, …) and each uses container-specific request types. Out of scope for this PR; falls back to the no-op default. Tracked as a follow-up.
  3. InstrumentationTags actually lives in internal-api, not agent-bootstrap; the new constant lives in the real file.
  4. Skipped a ConfigDefaults constant — there is no env-var / system-property fallback in v1 (deferred to v2), so the constant would be dead code.
  5. Skipped the JMH micro-benchmark — internal-api has no jmh source set. Empty-pattern fast-path correctness is exercised by the negative tests at every layer.
  6. Skipped the defensive 32-name cap / catch-all WARN from the design doc's Risks section — those were "future hardening" suggestions, not part of v1.

Span tag spec

Key Value Example
_dd.tt.extraction_sources CSV of matches; header:<lower> and qs:<lower> entries; sorted alphabetically within source; headers first header:authorization,header:x-customer-id,qs:order_id

Set only when at least one match is found. Absent otherwise.

labbati added 4 commits May 15, 2026 15:18
Adds the TransactionTrackingPatterns util (hand-rolled glob matcher with a
single volatile snapshot, zero allocations on the empty fast path), wires
the new optional 'tt_extraction_patterns' field through the APM_TRACING
remote-config DTO and DynamicConfig snapshot, exposes a Config static
fallback (always empty for now) and adds the InstrumentationTags
TT_EXTRACTION_SOURCES constant.

No tagging behaviour yet \u2014 only the configuration plumbing and the
compiled-pattern snapshot publication.

tag: ai generated
Adds the HttpServerDecorator.forEachRequestHeaderName extension point
(no-op default) and a guarded tagging block in onRequest. When the
TransactionTrackingPatterns snapshot is non-empty the server span gets
a single _dd.tt.extraction_sources tag whose value is a deterministic
CSV of matching header / query-string parameter names, sorted within
each source bucket (headers first, then qs) and lowercased.

Fast path on the empty snapshot is a single volatile read + isEmpty()
check, no allocation. A negative unit test asserts no tag is set when
the pattern list is empty.

tag: ai generated
Overrides forEachRequestHeaderName in the javax-servlet 2.2 and 3.0
decorators using HttpServletRequest.getHeaderNames(), so Spring WebMVC
running on top of any javax-servlet container (Tomcat, Jetty, etc.)
emits the _dd.tt.extraction_sources tag transparently. Adds a Spring
WebMVC 5.3 integration test (MockMvc) covering mixed header/qs matches
and asserting absence of the tag when the pattern list is empty.

The InstrumentationSpecification MOCK_DSM_TRACE_CONFIG now implements
the new TraceConfig.getTransactionTrackingExtractionPatterns() with an
empty list so the test infrastructure still compiles.

tag: ai generated
@dd-octo-sts
Copy link
Copy Markdown
Contributor

dd-octo-sts Bot commented May 15, 2026

❌ New Groovy Files Detected

Please avoid introducing new .groovy files to this repository.

  • dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecoratorTtExtractionTest.groovy
  • dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-5.3/src/test/groovy/test/tt/TtExtractionSpringBootTest.groovy

Instead, rewrite the new file(s) in Java / JUnit. See the How to Test With JUnit Guide for more details.

If this PR needs an exception, add the tag: override-groovy-enforcement label to bypass this workflow.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant