Conversation
…ty working Selecting a standalone sampler only when ASM/IAST/SCA is enabled left every other DD_APM_TRACING_ENABLED=false configuration on the default sampler, which keeps traces, so APM traces kept flowing. Drop them instead, as dd-trace-js and dd-trace-py do; LLM Observability spans are unaffected because they ship to the LLM Observability intake, which is independent of APM sampling priority. DD_TRACE_ENABLED=false installed the real LLM Observability SDK with no tracer to back it, and the no-op tracer's buildSpan() returns null, so every LLMObs.start*Span() call threw out of application code. Keep the SDK no-op. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This comment has been minimized.
This comment has been minimized.
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
Co-Authored-By: Claude Opus 5 <[email protected]>
The two new Groovy smoke tests would have needed the `tag: override groovy enforcement` label. What they proved end-to-end — a dropped APM trace is still handed to the writers, which is how LLM Observability spans still reach their intake — is now asserted in SamplerTest instead. Co-Authored-By: Claude Opus 5 <[email protected]>
There was a problem hiding this comment.
A sampled parent can lock a keep priority before the new sampler runs. The sampler then fails to drop that normal APM trace when APM tracing is off.
🤖 Datadog Autotest · Commit 5923093 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 592309351a
ℹ️ 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".
| if (isAsmEnabled(config)) { | ||
| log.debug( | ||
| "APM tracing is disabled, but ASM is enabled. Only 1 APM trace per minute will be sent."); | ||
| return new AsmStandaloneSampler(Clock.systemUTC()); |
There was a problem hiding this comment.
Preserve standalone ASM sampling after remote activation
When DD_APM_TRACING_ENABLED=false and AppSec starts in its default ENABLED_INACTIVE state, isAsmEnabled(config) is false, so this permanently selects the drop-all sampler. Remote Configuration can later activate AppSec, but it changes AppSecSystem state rather than the immutable Config activation used here, and the sampler is not rebuilt; consequently non-event traces remain dropped instead of providing the one-trace-per-minute service-catalog and billing signal required by standalone ASM. The sampler selection needs to accommodate runtime AppSec activation rather than treating ENABLED_INACTIVE as permanently off.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I replaced that branch with the new ApmTracingDisabledSampler, which constructs both samplers up front and picks between them per sampling decision based on ActiveSubsystems.APPSEC_ACTIVE.
mhlidd
left a comment
There was a problem hiding this comment.
One comment about expected behavior
…d no product marked the trace Co-Authored-By: Claude Opus 5 <[email protected]>
Co-Authored-By: Claude Opus 5 <[email protected]>
7860cdc to
80e580e
Compare
mhlidd
left a comment
There was a problem hiding this comment.
LGTM following addressing Codex and Copilot comments 👍
Co-Authored-By: Claude Opus 5 <[email protected]>
There was a problem hiding this comment.
The new drop sampler cannot replace manual keep decisions. APM traces can still leave the process when APM tracing is off. Remote AppSec activation and shutdown after single-span sampling also have trace-loss paths.
🤖 Datadog Autotest · Commit fe764e7 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
| // catalog, so with it off we can drop every APM trace. Traces a product does want | ||
| // (asm.keep, ai_guard.keep) are force-kept on the span, which no sampler can override. | ||
| log.debug("APM tracing is disabled. APM traces will be dropped."); | ||
| return new ForcePrioritySampler(PrioritySampling.SAMPLER_DROP, SamplingMechanism.DEFAULT); |
There was a problem hiding this comment.
The drop sampler does not override manual keep decisions
Applications can send APM trace data after users turn APM tracing off.
Assertion details
- Input: Set DD_APM_TRACING_ENABLED=false without ASM. Then set manual.keep, use the sampling-priority API, or enable the latency trace interceptor.
- Expected:
APM tracing off must drop ordinary APM traces. Only product-owned ASM and AI Guard decisions must keep traces. - Actual:
The DEFAULT drop vote cannot replace a locked USER_KEEP decision. The writer then exports the APM trace.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest · Open Bits AI session
| // and the trace is never dispatched. That is harmless for a sampled-out APM trace, but | ||
| // with APM tracing disabled the dropped trace also carries the LLM Observability spans, | ||
| // which have to reach their intake regardless of the APM sampling decision. | ||
| consumeFromSecondaryQueue(); |
There was a problem hiding this comment.
The flush barrier omits pending span-sampling work
Shutdown can lose pending LLM Observability spans when single-span sampling rules are active.
Assertion details
- Input: Enable LLM Observability and single-span sampling rules with APM tracing off. Finish a trace and close the writer before the span-sampling worker forwards it.
- Expected:
Flush must process all earlier span-sampling work before it reports completion. - Actual:
A dropped trace can remain in the span-sampling queue when the primary flush marker runs. The secondary-queue sweep sees no trace. Close then stops the sampling worker and loses the LLM Observability event.
Was this helpful? React 👍 or 👎
🤖 Datadog Autotest · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest · Open Bits AI session
Co-Authored-By: Claude Opus 5 <[email protected]>
What Does This Do
Two fixes for running LLM Observability without APM tracing (solves #10051):
DD_APM_TRACING_ENABLED=falsenow drops APM traces. The standalone sampler was only selected when ASM/IAST/SCA was enabled; every other configuration — including LLM Observability — fell through to the default sampler, which keeps traces, so APM traces kept flowing. On distributed traces the drop was defeated a second way: an extracted sampling priority is locked onto the span context at construction, so a request arriving withx-datadog-sampling-priority: 1silenced the drop before it was ever decided locally.DD_TRACE_ENABLED=falsewithDD_LLMOBS_ENABLED=trueno longer throws.LLMObsSystem.start()installed the real SDK implementation even with no tracer installed, andAgentTracer.get().buildSpan()returnsnullon the no-op tracer, so everyLLMObs.start*Span()call threw aNullPointerExceptionout of application code.Motivation
DD_APM_TRACING_ENABLED=falseis documented as working in all SDKs. LLM Observability users who don't want APM tracing had no working way to turn it off: the APM flag was a no-op, andDD_TRACE_ENABLED=falsebroke the LLMObs SDK.Additional Notes
Approach. This is a reduced port of #10989, which had gone stale. That PR introduced a
ProductTraceSource.LLMOBSbit plus aStandaloneProductenum and a unifiedStandaloneSamplerreplacingAsmStandaloneSampler. Two things changed since:0x20is now taken byAI_GUARD, and no other tracer allocates an LLMObs_dd.p.tsbit — in dd-trace-js the products are exactlyAPM/ASM/DSM/DJM/DBM/AI_GUARD, and in dd-trace-py exactly the same set. LLM Observability doesn't need one: its spans ship to the LLM Observability intake (/evp_proxy/v2/api/v2/llmobs), which is independent of APM sampling priority, so the correct behavior is to drop the APM traces and let the LLMObs events through.That leaves the fix as the selection logic that other tracers already have — compare
getProductRateLimiterin dd-trace-js (1/minutefor appsec/iast,RateLimiter(0), i.e. drop-all, otherwise) andLLMObsProcessor.process_tracein dd-trace-py (returnsNonefor the whole APM trace when APM tracing is disabled):AsmStandaloneSampler, 1 APM trace per minute for service catalog / billing.ForcePrioritySampler(SAMPLER_DROP, DEFAULT).Because a
DEFAULT-mechanism priority cannot overwrite a priority that is already set, products that force-keep their own traces are unaffected — AI Guard (ai_guard.keep) and ASM (asm.keep) keep working exactly as before, whichAIGuardSamplingTestcovers.Inherited sampling priorities. That same lock is what defeated the drop on distributed traces:
DDSpanContextlocks an extracted priority at construction, andDEFAULTis not one of the mechanismsSamplingMechanism.canAvoidSamplingPriorityLockexempts, so the drop-all vote was silently refused on exactly the traces the flag exists to suppress.ContextInterpreter.samplingPriorityOrDefaultnow leaves the extracted priorityUNSETwhen APM tracing is disabled and no product claimed the trace (no_dd.p.ts), letting the local sampler decide. This is the same shape as dd-trace-js' standaloneonSpanExtracthook (packages/dd-trace/src/standalone/index.js), which resets the upstream priority under the same condition.It sits in
ContextInterpreterbecause that is the one point every propagation style funnels through, so Datadog, W3C, B3, Haystack and XRay headers are all covered — including Haystack, whosedefaultSamplingPriority()override manufactures aSAMPLER_KEEPout of headers that carry no priority at all.Gating on
_dd.p.tsis what keeps this from discarding a decision another product asked for: ASM- and AI-Guard-marked traces are already exempt from sampler override inTraceCollector.setSamplingPriorityIfNecessary, and they keep their upstream priority here too.ASM standalone behavior is unchanged.
AsmStandaloneSamplervotes withSamplingMechanism.APPSEC, whichcanAvoidSamplingPriorityLockalready exempts when APM tracing is disabled — and on that pathDDSpanContextassigns rather than compare-and-swaps — so it already overrode an inherited priority before this change and reaches the same decision after it.Testing.
SamplerTest: APM traces are dropped when APM tracing is disabled, with and without LLM Observability enabled — and the dropped trace is still handed to the writers, which is what keeps LLM Observability spans flowing to their own intake.LLMObsSystemTest: the SDK stays no-op when tracing is disabled.DatadogHttpExtractorTest.ApmTracingDisabled: an upstreamSAMPLER_KEEPis dropped when no product marked the trace, and preserved when_dd.p.tsis set.AIGuardSamplingTest,ApmTracingDisabledChunkMarkerTestand theapm-tracing-disabledsmoke tests pass unchanged.Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueClaude session:
ba8ff7f0-b73e-41d6-a806-3603ab1c7329Resume:
claude --resume ba8ff7f0-b73e-41d6-a806-3603ab1c7329🤖 Generated with Claude Code