From fec98ea077f74ee4a7b9d0909206c69d4bc863f4 Mon Sep 17 00:00:00 2001 From: Tyler Finethy Date: Thu, 13 Aug 2026 13:42:08 -0400 Subject: [PATCH 1/2] Fix error spam from filtered out log probes A log probe with a condition that rejects a hit no longer evaluates its capture expressions on that hit. Those captures are dropped before being sent, so evaluating them served no purpose, and any evaluation failure was reported as a debugger event for every single hit, with no rate limiting applied. A probe with a broken capture expression on a hot method could therefore flood the debugger UI with error events. Capture expressions are also skipped when the hit is rate limited, which matches how the log message is handled. --- .../com/datadog/debugger/probe/LogProbe.java | 2 +- .../debugger/agent/CapturedSnapshotTest.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java index 9695e511520..2127550e76f 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java @@ -725,7 +725,7 @@ private void addFilteredCaptureExpressions( } private void processCaptureExpressions(CapturedContext context, LogStatus logStatus) { - if (captureExpressions == null) { + if (captureExpressions == null || !logStatus.isSampled() || !logStatus.getCondition()) { return; } for (CaptureExpression captureExpression : captureExpressions) { diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 9d70e07c3e7..e48b7514685 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -3020,6 +3020,31 @@ public void captureExpressionsWithNullCondition() throws IOException, URISyntaxE assertEquals("Cannot dereference field: fld", evaluationErrors.get(0).getMessage()); } + @Test + public void captureExpressionsWithRejectingCondition() throws IOException, URISyntaxException { + final String CLASS_NAME = "CapturedSnapshot08"; + LogProbe probe = + createProbeBuilder(PROBE_ID, CLASS_NAME, "doit", null) + .evaluateAt(MethodLocation.EXIT) + .captureSnapshot(false) + .when(new ProbeCondition(DSL.when(DSL.eq(DSL.value(1), DSL.value(2))), "1 == 2")) + .template("plain log", Collections.emptyList()) + .captureExpressions( + Collections.singletonList( + new LogProbe.CaptureExpression( + "unknown_symbol", + new ValueScript(ref("doesNotExist"), "doesNotExist"), + null))) + .build(); + TestSnapshotListener listener = installProbes(probe); + Class testClass = compileAndLoadClass(CLASS_NAME); + for (int i = 0; i < 5; i++) { + int result = Reflect.onClass(testClass).call("main", "1").get(); + assertEquals(3, result); + } + assertEquals(0, listener.snapshots.size()); + } + @Test public void captureExpressionsPrimitives() throws IOException, URISyntaxException { final String CLASS_NAME = "CapturedSnapshot08"; From 44df0ecf509a52330f19d11d83cd36eef5a6aa0e Mon Sep 17 00:00:00 2001 From: Tyler Finethy Date: Thu, 13 Aug 2026 14:12:40 -0400 Subject: [PATCH 2/2] Keep capture expressions in debug sessions Gate capture expression evaluation on the effective send decision (LogStatus.shouldSend()) instead of the raw sampling flag, so probes in an active debug session keep their capture expressions when their own sampler rejects the hit. --- .../com/datadog/debugger/probe/LogProbe.java | 2 +- .../datadog/debugger/probe/LogProbeTest.java | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java index 2127550e76f..0d4ac850c14 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/probe/LogProbe.java @@ -725,7 +725,7 @@ private void addFilteredCaptureExpressions( } private void processCaptureExpressions(CapturedContext context, LogStatus logStatus) { - if (captureExpressions == null || !logStatus.isSampled() || !logStatus.getCondition()) { + if (captureExpressions == null || !logStatus.shouldSend()) { return; } for (CaptureExpression captureExpression : captureExpressions) { diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java index a3c25c48021..c74396b10e5 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/probe/LogProbeTest.java @@ -5,12 +5,16 @@ import static java.lang.String.format; import static java.lang.Thread.currentThread; import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import com.datadog.debugger.agent.DebuggerAgentHelper; +import com.datadog.debugger.el.DSL; +import com.datadog.debugger.el.ProbeCondition; +import com.datadog.debugger.el.ValueScript; import com.datadog.debugger.probe.LogProbe.Builder; import com.datadog.debugger.probe.LogProbe.LogStatus; import com.datadog.debugger.sink.DebuggerSink; @@ -19,6 +23,7 @@ import datadog.context.ContextScope; import datadog.trace.api.Config; import datadog.trace.api.IdGenerationStrategy; +import datadog.trace.api.sampling.ConstantSampler; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.EvaluationError; import datadog.trace.bootstrap.debugger.MethodLocation; @@ -342,6 +347,49 @@ public void fillSnapshot_shouldSend_evalErrors() { "errorExit", snapshot.getCaptures().getReturn().getCapturedThrowable().getMessage()); } + @Test + public void captureExpressionsInActiveDebugSession() { + DebuggerAgentHelper.injectSink(new DebuggerSink(getConfig(), mock(ProbeStatusSink.class))); + TracerAPI tracer = + CoreTracer.builder().idGenerationStrategy(IdGenerationStrategy.fromName("random")).build(); + AgentTracer.registerIfAbsent(tracer); + AgentSpan span = tracer.startSpan("log probe capture expression testing", "test span"); + try (ContextScope scope = tracer.activateManualSpan(span)) { + span.setTag(Tags.PROPAGATED_DEBUG, DEBUG_SESSION_ID + ":1"); + // the probe sampler always rejects: the active session decision must still win + ProbeRateLimiter.setSamplerSupplier(rate -> new ConstantSampler(false)); + LogProbe logProbe = + createLog("log line") + .probeId(ProbeId.newId()) + .evaluateAt(MethodLocation.EXIT) + .tags(format("session_id:%s", DEBUG_SESSION_ID)) + .when(new ProbeCondition(DSL.when(DSL.eq(DSL.value(1), DSL.value(1))), "1 == 1")) + .captureExpressions( + singletonList( + new LogProbe.CaptureExpression( + "greeting", new ValueScript(DSL.value("hello"), "'hello'"), null))) + .build(); + logProbe.initSamplers(); + CapturedContext entryContext = capturedContext(span, logProbe); + CapturedContext exitContext = capturedContext(span, logProbe); + logProbe.evaluate(entryContext, new LogStatus(logProbe), MethodLocation.ENTRY, false); + logProbe.evaluate(exitContext, new LogStatus(logProbe), MethodLocation.EXIT, false); + Snapshot snapshot = new Snapshot(currentThread(), logProbe, 3); + assertTrue(logProbe.fillSnapshot(entryContext, exitContext, emptyList(), snapshot)); + assertEquals( + "hello", + snapshot + .getCaptures() + .getReturn() + .getCaptureExpressions() + .get("greeting") + .getValue() + .toString()); + } finally { + ProbeRateLimiter.setSamplerSupplier(null); + } + } + private Builder createLog(String template) { return LogProbe.builder() .language(LANGUAGE)