From 5281b9bd2bd9322ead80a61c2bf09b4420e5abee Mon Sep 17 00:00:00 2001 From: "benjamin.a" Date: Tue, 29 Sep 2026 09:10:47 +0900 Subject: [PATCH 1/2] Log a warning when retransforming already-loaded classes fails RedefinitionLoggingListener was only registered with debug logging, so a failed retransformation batch was silent at the default level. Because every already-loaded class is retransformed in a single batch, one failure leaves all of them uninstrumented, as seen in #12540. Register the listener always, and log a one-line warning with the batch size and cause when debug logging is off. Debug logging keeps the full class list and stack trace. --- .../trace/agent/tooling/AgentInstaller.java | 7 +++ ...RedefinitionLoggingListenerForkedTest.java | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 dd-java-agent/agent-installer/src/test/java/datadog/trace/agent/tooling/RedefinitionLoggingListenerForkedTest.java diff --git a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index d56b2ab6ac8..b84d15fd099 100644 --- a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -164,6 +164,7 @@ public static ClassFileTransformer installBytebuddyAgent( .with(AgentStrategies.transformerDecorator()) .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentStrategies.rediscoveryStrategy()) + .with(new RedefinitionLoggingListener()) .with(AgentStrategies.locationStrategy()) .with(AgentStrategies.poolStrategy()) .with(AgentBuilder.DescriptionStrategy.Default.POOL_ONLY) @@ -403,6 +404,12 @@ public Iterable>> onError( final List> types) { if (DEBUG) { log.debug("Exception while retransforming {} classes: {}", batch.size(), batch, throwable); + } else { + // a failed batch leaves every class in it uninstrumented, so don't keep this silent + log.warn( + "Exception while retransforming {} classes, instrumentation may be incomplete: {}", + batch.size(), + throwable.toString()); } return Collections.emptyList(); } diff --git a/dd-java-agent/agent-installer/src/test/java/datadog/trace/agent/tooling/RedefinitionLoggingListenerForkedTest.java b/dd-java-agent/agent-installer/src/test/java/datadog/trace/agent/tooling/RedefinitionLoggingListenerForkedTest.java new file mode 100644 index 00000000000..8c7fdda7161 --- /dev/null +++ b/dd-java-agent/agent-installer/src/test/java/datadog/trace/agent/tooling/RedefinitionLoggingListenerForkedTest.java @@ -0,0 +1,46 @@ +package datadog.trace.agent.tooling; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +class RedefinitionLoggingListenerForkedTest { + + @Test + void warnsWhenRetransformationBatchFailsWithoutDebugLogging() { + // AgentInstaller.DEBUG is fixed when the class initializes, so keep it off in this fork + ((Logger) LoggerFactory.getLogger(AgentInstaller.class)).setLevel(Level.INFO); + Logger logger = + (Logger) LoggerFactory.getLogger(AgentInstaller.RedefinitionLoggingListener.class); + logger.setLevel(Level.INFO); + ListAppender appender = new ListAppender<>(); + appender.start(); + logger.addAppender(appender); + try { + List> batch = Arrays.asList(String.class, Integer.class); + + Iterable>> retry = + new AgentInstaller.RedefinitionLoggingListener() + .onError( + 0, batch, new UnsupportedOperationException("class redefinition failed"), batch); + + assertFalse(retry.iterator().hasNext()); + assertEquals(1, appender.list.size()); + ILoggingEvent event = appender.list.get(0); + assertEquals(Level.WARN, event.getLevel()); + assertTrue(event.getFormattedMessage().contains("retransforming 2 classes")); + assertTrue(event.getFormattedMessage().contains("class redefinition failed")); + } finally { + logger.detachAppender(appender); + } + } +} From db9ada7d17f507361af5686481d9a8f0be2e9b61 Mon Sep 17 00:00:00 2001 From: "benjamin.a" Date: Wed, 30 Sep 2026 15:05:46 +0900 Subject: [PATCH 2/2] Drop the redundant listener registration from the debug block RedefinitionLoggingListener is now registered on the main builder chain, so the debug block only needs TransformLoggingListener. --- .../java/datadog/trace/agent/tooling/AgentInstaller.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java index b84d15fd099..8347a923e11 100644 --- a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java +++ b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/AgentInstaller.java @@ -177,12 +177,7 @@ public static ClassFileTransformer installBytebuddyAgent( .ignore(globalIgnoresMatcher(skipAdditionalLibraryMatcher)); if (DEBUG) { - agentBuilder = - agentBuilder - .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) - .with(AgentStrategies.rediscoveryStrategy()) - .with(new RedefinitionLoggingListener()) - .with(new TransformLoggingListener()); + agentBuilder = agentBuilder.with(new TransformLoggingListener()); } for (final AgentBuilder.Listener listener : listeners) {