From dceca289e92135ea27f85bb6e066205b8ca16828 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bempel Date: Fri, 28 Feb 2025 18:55:57 +0100 Subject: [PATCH] Add In-Product Enablement (#8461) Introduce the ability to start and stop Debugger features: - Dynamic Instrumentation - Exception Replay - Code Origin - Distributed Debugger dynamically based on RemoteConfig record: APM_TRACING DebuggerAgent is now run every time at startup to have the base of some feature ready and be able to start the minimum required foe each feature. Ability to stop also the feature at any time to uninstall probes. Add smoke tests --- .circleci/config.continue.yml.j2 | 2 +- .../java/datadog/trace/bootstrap/Agent.java | 29 ++- .../bootstrap/debugger/DebuggerContext.java | 74 ++++++ .../datadog/debugger/agent/DebuggerAgent.java | 212 ++++++++++++------ .../agent/DefaultProductConfigUpdater.java | 73 ++++++ .../debugger/symbol/SymDBEnablement.java | 5 +- .../DefaultProductConfigUpdaterTest.java | 24 ++ .../ExceptionProbeInstrumentationTest.java | 8 + .../ServerDebuggerTestApplication.java | 17 ++ .../debugger/TestApplicationHelper.java | 11 + .../smoketest/BaseIntegrationTest.java | 63 +++++- .../smoketest/CodeOriginIntegrationTest.java | 2 +- .../InProductEnablementIntegrationTest.java | 77 +++++++ .../datadog/smoketest/RemoteConfigHelper.java | 125 ++++++++--- .../main/java/datadog/trace/core/DDSpan.java | 6 +- .../trace/core/TracingConfigPoller.java | 29 ++- .../datadog/remoteconfig/Capabilities.java | 4 + 17 files changed, 630 insertions(+), 131 deletions(-) create mode 100644 dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DefaultProductConfigUpdater.java create mode 100644 dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DefaultProductConfigUpdaterTest.java create mode 100644 dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/InProductEnablementIntegrationTest.java diff --git a/.circleci/config.continue.yml.j2 b/.circleci/config.continue.yml.j2 index a44d1f95e6d..491d9be6ccf 100644 --- a/.circleci/config.continue.yml.j2 +++ b/.circleci/config.continue.yml.j2 @@ -36,7 +36,7 @@ instrumentation_modules: &instrumentation_modules "dd-java-agent/instrumentation debugger_modules: &debugger_modules "dd-java-agent/agent-debugger|dd-java-agent/agent-bootstrap|dd-java-agent/agent-builder|internal-api|communication|dd-trace-core" profiling_modules: &profiling_modules "dd-java-agent/agent-profiling" -default_system_tests_commit: &default_system_tests_commit 0509dbd094c9cbf15f58db96f62276a0adff7efa +default_system_tests_commit: &default_system_tests_commit 6980534f333b3f7a35d83df2230f00f4e26642f5 parameters: nightly: diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java index c0b79066e9b..62e5a845dd0 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java @@ -96,9 +96,9 @@ private enum AgentFeature { CIVISIBILITY_AGENTLESS(CiVisibilityConfig.CIVISIBILITY_AGENTLESS_ENABLED, false), USM(UsmConfig.USM_ENABLED, false), TELEMETRY(GeneralConfig.TELEMETRY_ENABLED, true), - DEBUGGER(DebuggerConfig.DYNAMIC_INSTRUMENTATION_ENABLED, false), - EXCEPTION_DEBUGGING(DebuggerConfig.EXCEPTION_REPLAY_ENABLED, false), - SPAN_ORIGIN(TraceInstrumentationConfig.CODE_ORIGIN_FOR_SPANS_ENABLED, false), + DYNAMIC_INSTRUMENTATION(DebuggerConfig.DYNAMIC_INSTRUMENTATION_ENABLED, false), + EXCEPTION_REPLAY(DebuggerConfig.EXCEPTION_REPLAY_ENABLED, false), + CODE_ORIGIN(TraceInstrumentationConfig.CODE_ORIGIN_FOR_SPANS_ENABLED, false), DATA_JOBS(GeneralConfig.DATA_JOBS_ENABLED, false), AGENTLESS_LOG_SUBMISSION(GeneralConfig.AGENTLESS_LOG_SUBMISSION_ENABLED, false); @@ -149,9 +149,10 @@ public boolean isEnabledByDefault() { private static boolean ciVisibilityEnabled = false; private static boolean usmEnabled = false; private static boolean telemetryEnabled = true; - private static boolean debuggerEnabled = false; - private static boolean exceptionDebuggingEnabled = false; - private static boolean spanOriginEnabled = false; + private static boolean dynamicInstrumentationEnabled = false; + private static boolean exceptionReplayEnabled = false; + private static boolean codeOriginEnabled = false; + private static boolean distributedDebuggerEnabled = false; private static boolean agentlessLogSubmissionEnabled = false; /** @@ -261,9 +262,9 @@ public static void start( || isFeatureEnabled(AgentFeature.DEPRECATED_REMOTE_CONFIG); cwsEnabled = isFeatureEnabled(AgentFeature.CWS); telemetryEnabled = isFeatureEnabled(AgentFeature.TELEMETRY); - debuggerEnabled = isFeatureEnabled(AgentFeature.DEBUGGER); - exceptionDebuggingEnabled = isFeatureEnabled(AgentFeature.EXCEPTION_DEBUGGING); - spanOriginEnabled = isFeatureEnabled(AgentFeature.SPAN_ORIGIN); + dynamicInstrumentationEnabled = isFeatureEnabled(AgentFeature.DYNAMIC_INSTRUMENTATION); + exceptionReplayEnabled = isFeatureEnabled(AgentFeature.EXCEPTION_REPLAY); + codeOriginEnabled = isFeatureEnabled(AgentFeature.CODE_ORIGIN); agentlessLogSubmissionEnabled = isFeatureEnabled(AgentFeature.AGENTLESS_LOG_SUBMISSION); if (profilingEnabled) { @@ -1114,7 +1115,10 @@ private static void shutdownProfilingAgent(final boolean sync) { } private static void maybeStartDebugger(Instrumentation inst, Class scoClass, Object sco) { - if (!debuggerEnabled && !exceptionDebuggingEnabled && !spanOriginEnabled) { + if (isExplicitlyDisabled(DebuggerConfig.DYNAMIC_INSTRUMENTATION_ENABLED) + && isExplicitlyDisabled(DebuggerConfig.EXCEPTION_REPLAY_ENABLED) + && isExplicitlyDisabled(TraceInstrumentationConfig.CODE_ORIGIN_FOR_SPANS_ENABLED) + && isExplicitlyDisabled(DebuggerConfig.DISTRIBUTED_DEBUGGER_ENABLED)) { return; } if (!remoteConfigEnabled) { @@ -1124,6 +1128,11 @@ private static void maybeStartDebugger(Instrumentation inst, Class scoClass, startDebuggerAgent(inst, scoClass, sco); } + private static boolean isExplicitlyDisabled(String booleanKey) { + return Config.get().configProvider().isSet(booleanKey) + && !Config.get().configProvider().getBoolean(booleanKey); + } + private static synchronized void startDebuggerAgent( Instrumentation inst, Class scoClass, Object sco) { StaticEventLogger.begin("Debugger"); diff --git a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java index 6840146f969..c524f065557 100644 --- a/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java +++ b/dd-java-agent/agent-debugger/debugger-bootstrap/src/main/java/datadog/trace/bootstrap/debugger/DebuggerContext.java @@ -50,6 +50,22 @@ public String tag() { public abstract String tag(); } + public interface ProductConfigUpdater { + void updateConfig( + Boolean dynamicInstrumentationEnabled, + Boolean exceptionReplayEnabled, + Boolean codeOriginEnabled, + Boolean liveDebuggingEnabled); + + boolean isDynamicInstrumentationEnabled(); + + boolean isExceptionReplayEnabled(); + + boolean isCodeOriginEnabled(); + + boolean isDistributedDebuggerEnabled(); + } + public interface ProbeResolver { ProbeImplementation resolve(String encodedProbeId); } @@ -103,6 +119,7 @@ public interface CodeOriginRecorder { String captureCodeOrigin(Method method, boolean entry, boolean instrument); } + private static volatile ProductConfigUpdater productConfigUpdater; private static volatile ProbeResolver probeResolver; private static volatile ClassFilter classFilter; private static volatile ClassNameFilter classNameFilter; @@ -112,6 +129,10 @@ public interface CodeOriginRecorder { private static volatile ExceptionDebugger exceptionDebugger; private static volatile CodeOriginRecorder codeOriginRecorder; + public static void initProductConfigUpdater(ProductConfigUpdater productConfigUpdater) { + DebuggerContext.productConfigUpdater = productConfigUpdater; + } + public static void initProbeResolver(ProbeResolver probeResolver) { DebuggerContext.probeResolver = probeResolver; } @@ -144,6 +165,59 @@ public static void initCodeOrigin(CodeOriginRecorder codeOriginRecorder) { DebuggerContext.codeOriginRecorder = codeOriginRecorder; } + public static void updateConfig( + Boolean dynamicInstrumentationEnabled, + Boolean exceptionReplayEnabled, + Boolean codeOriginEnabled, + Boolean liveDebuggingEnabled) { + LOGGER.debug( + "Updating config: dynamicInstrumentationEnabled: {}, exceptionReplayEnabled: {}, codeOriginEnabled: {}, liveDebuggingEnabled: {}", + dynamicInstrumentationEnabled, + exceptionReplayEnabled, + codeOriginEnabled, + liveDebuggingEnabled); + ProductConfigUpdater updater = productConfigUpdater; + if (updater != null) { + updater.updateConfig( + dynamicInstrumentationEnabled, + exceptionReplayEnabled, + codeOriginEnabled, + liveDebuggingEnabled); + } + } + + public static boolean isDynamicInstrumentationEnabled() { + ProductConfigUpdater updater = productConfigUpdater; + if (updater != null) { + return updater.isDynamicInstrumentationEnabled(); + } + return Config.get().isDynamicInstrumentationEnabled(); + } + + public static boolean isExceptionReplayEnabled() { + ProductConfigUpdater updater = productConfigUpdater; + if (updater != null) { + return updater.isExceptionReplayEnabled(); + } + return Config.get().isDebuggerExceptionEnabled(); + } + + public static boolean isCodeOriginEnabled() { + ProductConfigUpdater updater = productConfigUpdater; + if (updater != null) { + return updater.isCodeOriginEnabled(); + } + return Config.get().isDebuggerCodeOriginEnabled(); + } + + public static boolean isDistributedDebuggerEnabled() { + ProductConfigUpdater updater = productConfigUpdater; + if (updater != null) { + return updater.isDistributedDebuggerEnabled(); + } + return Config.get().isDistributedDebuggerEnabled(); + } + /** * Returns the probe details based on the probe id provided. If no probe is found, try to * re-transform the class using the callingClass parameter No-op if no implementation available diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java index 4b8cef476ca..2a585172849 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java @@ -1,5 +1,7 @@ package com.datadog.debugger.agent; +import static com.datadog.debugger.agent.ConfigurationAcceptor.Source.CODE_ORIGIN; +import static com.datadog.debugger.agent.ConfigurationAcceptor.Source.EXCEPTION; import static com.datadog.debugger.agent.ConfigurationAcceptor.Source.REMOTE_CONFIG; import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP; @@ -40,6 +42,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.zip.ZipOutputStream; import org.slf4j.Logger; @@ -48,15 +52,24 @@ /** Debugger agent implementation */ public class DebuggerAgent { private static final Logger LOGGER = LoggerFactory.getLogger(DebuggerAgent.class); + private static Instrumentation instrumentation; private static ConfigurationPoller configurationPoller; private static DebuggerSink sink; private static String agentVersion; private static JsonSnapshotSerializer snapshotSerializer; + private static ClassNameFilter classNameFilter; private static SymDBEnablement symDBEnablement; + private static volatile ConfigurationUpdater configurationUpdater; + private static volatile DefaultExceptionDebugger exceptionDebugger; + static AtomicBoolean dynamicInstrumentationEnabled = new AtomicBoolean(); + static AtomicBoolean exceptionReplayEnabled = new AtomicBoolean(); + static AtomicBoolean codeOriginEnabled = new AtomicBoolean(); + static AtomicBoolean distributedDebuggerEnabled = new AtomicBoolean(); - public static synchronized void run( - Instrumentation instrumentation, SharedCommunicationObjects sco) { + public static synchronized void run(Instrumentation inst, SharedCommunicationObjects sco) { + instrumentation = inst; Config config = Config.get(); + configurationPoller = sco.configurationPoller(config); ClassesToRetransformFinder classesToRetransformFinder = new ClassesToRetransformFinder(); setupSourceFileTracking(instrumentation, classesToRetransformFinder); Redaction.addUserDefinedKeywords(config); @@ -70,8 +83,8 @@ public static synchronized void run( config, diagnosticEndpoint, ddAgentFeaturesDiscovery.supportsDebuggerDiagnostics()); DebuggerSink debuggerSink = createDebuggerSink(config, probeStatusSink); debuggerSink.start(); - ClassNameFilter classNameFilter = new ClassNameFiltering(config); - ConfigurationUpdater configurationUpdater = + classNameFilter = new ClassNameFiltering(config); + configurationUpdater = new ConfigurationUpdater( instrumentation, DebuggerAgent::createTransformer, @@ -81,6 +94,7 @@ public static synchronized void run( sink = debuggerSink; StatsdMetricForwarder statsdMetricForwarder = new StatsdMetricForwarder(config, probeStatusSink); + DebuggerContext.initProductConfigUpdater(new DefaultProductConfigUpdater()); DebuggerContext.initProbeResolver(configurationUpdater); DebuggerContext.initMetricForwarder(statsdMetricForwarder); DebuggerContext.initClassFilter(new DenyListHelper(null)); // default hard coded deny list @@ -88,20 +102,11 @@ public static synchronized void run( DebuggerContext.initValueSerializer(snapshotSerializer); DebuggerContext.initTracer(new DebuggerTracer(debuggerSink.getProbeStatusSink())); DebuggerContext.initClassNameFilter(classNameFilter); - DefaultExceptionDebugger defaultExceptionDebugger = null; if (config.isDebuggerExceptionEnabled()) { - LOGGER.info("Starting Exception Replay"); - defaultExceptionDebugger = - new DefaultExceptionDebugger( - configurationUpdater, - classNameFilter, - Duration.ofSeconds(config.getDebuggerExceptionCaptureInterval()), - config.getDebuggerMaxExceptionPerSecond()); - DebuggerContext.initExceptionDebugger(defaultExceptionDebugger); + startExceptionReplay(); } if (config.isDebuggerCodeOriginEnabled()) { - LOGGER.info("Starting Code Origin for spans"); - DebuggerContext.initCodeOrigin(new DefaultCodeOriginRecorder(config, configurationUpdater)); + startCodeOriginForSpans(); } if (config.isDynamicInstrumentationInstrumentTheWorld()) { setupInstrumentTheWorldTransformer( @@ -109,8 +114,7 @@ public static synchronized void run( } // Dynamic Instrumentation if (config.isDynamicInstrumentationEnabled()) { - startDynamicInstrumentation( - instrumentation, sco, config, configurationUpdater, debuggerSink, classNameFilter); + startDynamicInstrumentation(); } try { /* @@ -121,22 +125,15 @@ public static synchronized void run( } catch (final IllegalStateException ex) { // The JVM is already shutting down. } - ExceptionProbeManager exceptionProbeManager = - defaultExceptionDebugger != null - ? defaultExceptionDebugger.getExceptionProbeManager() - : null; - TracerFlare.addReporter( - new DebuggerReporter(configurationUpdater, sink, exceptionProbeManager)); + TracerFlare.addReporter(DebuggerAgent::addReportToFlare); } - private static void startDynamicInstrumentation( - Instrumentation instrumentation, - SharedCommunicationObjects sco, - Config config, - ConfigurationUpdater configurationUpdater, - DebuggerSink debuggerSink, - ClassNameFilter classNameFilter) { + public static void startDynamicInstrumentation() { + if (!dynamicInstrumentationEnabled.compareAndSet(false, true)) { + return; + } LOGGER.info("Starting Dynamic Instrumentation"); + Config config = Config.get(); String probeFileLocation = config.getDynamicInstrumentationProbeFile(); if (probeFileLocation != null) { Path probeFilePath = Paths.get(probeFileLocation); @@ -144,14 +141,11 @@ private static void startDynamicInstrumentation( probeFilePath, configurationUpdater, config.getDynamicInstrumentationMaxPayloadSize()); return; } - configurationPoller = sco.configurationPoller(config); if (configurationPoller != null) { if (config.isSymbolDatabaseEnabled()) { SymbolAggregator symbolAggregator = new SymbolAggregator( - classNameFilter, - debuggerSink.getSymbolSink(), - config.getSymbolDatabaseFlushThreshold()); + classNameFilter, sink.getSymbolSink(), config.getSymbolDatabaseFlushThreshold()); symbolAggregator.start(); symDBEnablement = new SymDBEnablement(instrumentation, config, symbolAggregator, classNameFilter); @@ -165,6 +159,84 @@ private static void startDynamicInstrumentation( } } + public static void stopDynamicInstrumentation() { + if (!dynamicInstrumentationEnabled.compareAndSet(true, false)) { + return; + } + LOGGER.info("Stopping Dynamic Instrumentation"); + unsubscribeConfigurationPoller(); + if (configurationUpdater != null) { + // uninstall all probes by providing empty configuration + configurationUpdater.accept(REMOTE_CONFIG, Collections.emptyList()); + } + if (symDBEnablement != null) { + symDBEnablement.stopSymbolExtraction(); + } + } + + public static void startExceptionReplay() { + if (!exceptionReplayEnabled.compareAndSet(false, true)) { + return; + } + LOGGER.info("Starting Exception Replay"); + Config config = Config.get(); + exceptionDebugger = + new DefaultExceptionDebugger( + configurationUpdater, + classNameFilter, + Duration.ofSeconds(config.getDebuggerExceptionCaptureInterval()), + config.getDebuggerMaxExceptionPerSecond()); + DebuggerContext.initExceptionDebugger(exceptionDebugger); + } + + public static void stopExceptionReplay() { + if (!exceptionReplayEnabled.compareAndSet(true, false)) { + return; + } + LOGGER.info("Stopping Exception Replay"); + if (configurationUpdater != null) { + // uninstall all exception probes by providing empty configuration + configurationUpdater.accept(EXCEPTION, Collections.emptyList()); + } + exceptionDebugger = null; + DebuggerContext.initExceptionDebugger(null); + } + + public static void startCodeOriginForSpans() { + if (!codeOriginEnabled.compareAndSet(false, true)) { + return; + } + LOGGER.info("Starting Code Origin for spans"); + DebuggerContext.initCodeOrigin( + new DefaultCodeOriginRecorder(Config.get(), configurationUpdater)); + } + + public static void stopCodeOriginForSpans() { + if (!codeOriginEnabled.compareAndSet(true, false)) { + return; + } + LOGGER.info("Stopping Code Origin for spans"); + if (configurationUpdater != null) { + // uninstall all code origin probes by providing empty configuration + configurationUpdater.accept(CODE_ORIGIN, Collections.emptyList()); + } + DebuggerContext.initCodeOrigin(null); + } + + public static void startDistributedDebugger() { + if (!distributedDebuggerEnabled.compareAndSet(false, true)) { + return; + } + LOGGER.info("Starting Distributed Debugger"); + } + + public static void stopDistributedDebugger() { + if (!distributedDebuggerEnabled.compareAndSet(true, false)) { + return; + } + LOGGER.info("Sopping Distributed Debugger"); + } + private static DebuggerSink createDebuggerSink(Config config, ProbeStatusSink probeStatusSink) { String tags = getDefaultTagsMergedWithGlobalTags(config); SnapshotSink snapshotSink = @@ -256,6 +328,13 @@ private static void subscribeConfigurationPoller( } } + private static void unsubscribeConfigurationPoller() { + if (configurationPoller != null) { + configurationPoller.removeListeners(Product.LIVE_DEBUGGING); + configurationPoller.removeListeners(Product.LIVE_DEBUGGING_SYMBOL_DB); + } + } + static ClassFileTransformer setupInstrumentTheWorldTransformer( Config config, Instrumentation instrumentation, @@ -342,45 +421,30 @@ public void run() { } } - static class DebuggerReporter implements TracerFlare.Reporter { - - private final ConfigurationUpdater configurationUpdater; - private final DebuggerSink sink; - private final ExceptionProbeManager exceptionProbeManager; - - public DebuggerReporter( - ConfigurationUpdater configurationUpdater, - DebuggerSink sink, - ExceptionProbeManager exceptionProbeManager) { - this.configurationUpdater = configurationUpdater; - this.sink = sink; - this.exceptionProbeManager = exceptionProbeManager; - } - - @Override - public void addReportToFlare(ZipOutputStream zip) throws IOException { - String content = - String.join( - System.lineSeparator(), - "Snapshot url: ", - sink.getSnapshotSink().getUrl().toString(), - "Diagnostic url: ", - sink.getProbeStatusSink().getUrl().toString(), - "SymbolDB url: ", - sink.getSymbolSink().getUrl().toString(), - "Probe definitions:", - configurationUpdater.getAppliedDefinitions().toString(), - "Instrumented probes:", - configurationUpdater.getInstrumentationResults().toString(), - "Probe statuses:", - sink.getProbeStatusSink().getProbeStatuses().toString(), - "SymbolDB stats:", - sink.getSymbolSink().getStats().toString(), - "Exception Fingerprints:", - exceptionProbeManager != null - ? exceptionProbeManager.getFingerprints().toString() - : "N/A"); - TracerFlare.addText(zip, "dynamic_instrumentation.txt", content); - } + private static void addReportToFlare(ZipOutputStream zip) throws IOException { + ExceptionProbeManager exceptionProbeManager = + exceptionDebugger != null ? exceptionDebugger.getExceptionProbeManager() : null; + String content = + String.join( + System.lineSeparator(), + "Snapshot url: ", + sink.getSnapshotSink().getUrl().toString(), + "Diagnostic url: ", + sink.getProbeStatusSink().getUrl().toString(), + "SymbolDB url: ", + sink.getSymbolSink().getUrl().toString(), + "Probe definitions:", + configurationUpdater.getAppliedDefinitions().toString(), + "Instrumented probes:", + configurationUpdater.getInstrumentationResults().toString(), + "Probe statuses:", + sink.getProbeStatusSink().getProbeStatuses().toString(), + "SymbolDB stats:", + sink.getSymbolSink().getStats().toString(), + "Exception Fingerprints:", + exceptionProbeManager != null + ? exceptionProbeManager.getFingerprints().toString() + : "N/A"); + TracerFlare.addText(zip, "dynamic_instrumentation.txt", content); } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DefaultProductConfigUpdater.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DefaultProductConfigUpdater.java new file mode 100644 index 00000000000..3b1597de69f --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DefaultProductConfigUpdater.java @@ -0,0 +1,73 @@ +package com.datadog.debugger.agent; + +import datadog.trace.api.Config; +import datadog.trace.api.config.DebuggerConfig; +import datadog.trace.api.config.TraceInstrumentationConfig; +import datadog.trace.bootstrap.debugger.DebuggerContext; + +class DefaultProductConfigUpdater implements DebuggerContext.ProductConfigUpdater { + + @Override + public void updateConfig( + Boolean dynamicInstrumentationEnabled, + Boolean exceptionReplayEnabled, + Boolean codeOriginEnabled, + Boolean liveDebuggingEnabled) { + startOrStopFeature( + DebuggerConfig.DYNAMIC_INSTRUMENTATION_ENABLED, + dynamicInstrumentationEnabled, + DebuggerAgent::startDynamicInstrumentation, + DebuggerAgent::stopDynamicInstrumentation); + startOrStopFeature( + DebuggerConfig.EXCEPTION_REPLAY_ENABLED, + exceptionReplayEnabled, + DebuggerAgent::startExceptionReplay, + DebuggerAgent::stopExceptionReplay); + startOrStopFeature( + TraceInstrumentationConfig.CODE_ORIGIN_FOR_SPANS_ENABLED, + codeOriginEnabled, + DebuggerAgent::startCodeOriginForSpans, + DebuggerAgent::stopCodeOriginForSpans); + startOrStopFeature( + DebuggerConfig.DISTRIBUTED_DEBUGGER_ENABLED, + liveDebuggingEnabled, + DebuggerAgent::startDistributedDebugger, + DebuggerAgent::stopDistributedDebugger); + } + + @Override + public boolean isDynamicInstrumentationEnabled() { + return DebuggerAgent.dynamicInstrumentationEnabled.get(); + } + + @Override + public boolean isExceptionReplayEnabled() { + return DebuggerAgent.exceptionReplayEnabled.get(); + } + + @Override + public boolean isCodeOriginEnabled() { + return DebuggerAgent.codeOriginEnabled.get(); + } + + @Override + public boolean isDistributedDebuggerEnabled() { + return DebuggerAgent.distributedDebuggerEnabled.get(); + } + + private static boolean isExplicitlyDisabled(String booleanKey) { + return Config.get().configProvider().isSet(booleanKey) + && !Config.get().configProvider().getBoolean(booleanKey); + } + + private static void startOrStopFeature( + String booleanKey, Boolean currentStatus, Runnable start, Runnable stop) { + if (!isExplicitlyDisabled(booleanKey) && currentStatus != null) { + if (currentStatus) { + start.run(); + } else { + stop.run(); + } + } + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java index 12b35740d05..b0a5be8c96d 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java @@ -84,7 +84,10 @@ private static SymDbRemoteConfigRecord deserializeSymDb(byte[] content) throws I public void stopSymbolExtraction() { LOGGER.debug("Stopping symbol extraction."); - instrumentation.removeTransformer(symbolExtractionTransformer); + if (symbolExtractionTransformer != null) { + instrumentation.removeTransformer(symbolExtractionTransformer); + symbolExtractionTransformer = null; + } } long getLastUploadTimestamp() { diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DefaultProductConfigUpdaterTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DefaultProductConfigUpdaterTest.java new file mode 100644 index 00000000000..6f7e7716a01 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DefaultProductConfigUpdaterTest.java @@ -0,0 +1,24 @@ +package com.datadog.debugger.agent; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class DefaultProductConfigUpdaterTest { + + @Test + public void enableDisable() { + DefaultProductConfigUpdater productConfigUpdater = new DefaultProductConfigUpdater(); + productConfigUpdater.updateConfig(null, null, null, null); + productConfigUpdater.updateConfig(true, true, true, true); + assertTrue(productConfigUpdater.isDynamicInstrumentationEnabled()); + assertTrue(productConfigUpdater.isExceptionReplayEnabled()); + assertTrue(productConfigUpdater.isCodeOriginEnabled()); + assertTrue(productConfigUpdater.isDistributedDebuggerEnabled()); + productConfigUpdater.updateConfig(false, false, false, false); + assertFalse(productConfigUpdater.isDynamicInstrumentationEnabled()); + assertFalse(productConfigUpdater.isExceptionReplayEnabled()); + assertFalse(productConfigUpdater.isCodeOriginEnabled()); + assertFalse(productConfigUpdater.isDistributedDebuggerEnabled()); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/ExceptionProbeInstrumentationTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/ExceptionProbeInstrumentationTest.java index b5b63f463e5..d4013f3ebc9 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/ExceptionProbeInstrumentationTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/ExceptionProbeInstrumentationTest.java @@ -17,6 +17,7 @@ import com.datadog.debugger.agent.ClassesToRetransformFinder; import com.datadog.debugger.agent.Configuration; import com.datadog.debugger.agent.ConfigurationUpdater; +import com.datadog.debugger.agent.DebuggerAgent; import com.datadog.debugger.agent.DebuggerAgentHelper; import com.datadog.debugger.agent.DebuggerTransformer; import com.datadog.debugger.agent.JsonSnapshotSerializer; @@ -56,6 +57,7 @@ import org.joor.Reflect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; @@ -82,6 +84,11 @@ public class ExceptionProbeInstrumentationTest { private MockSampler probeSampler; private MockSampler globalSampler; + @BeforeAll + public static void beforeAll() { + setFieldInConfig(Config.get(), "agentUrl", "http://localhost:8126"); + } + @BeforeEach public void before() { CoreTracer tracer = CoreTracer.builder().build(); @@ -92,6 +99,7 @@ public void before() { ProbeRateLimiter.setSamplerSupplier(rate -> rate < 101 ? probeSampler : globalSampler); ProbeRateLimiter.setGlobalSnapshotRate(1000); // to activate the call to DebuggerContext.handleException + DebuggerAgent.startExceptionReplay(); setFieldInConfig(Config.get(), "debuggerExceptionEnabled", true); setFieldInConfig(Config.get(), "dynamicInstrumentationClassFileDumpEnabled", true); } diff --git a/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/ServerDebuggerTestApplication.java b/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/ServerDebuggerTestApplication.java index 821ef155a03..e17753545ea 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/ServerDebuggerTestApplication.java +++ b/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/ServerDebuggerTestApplication.java @@ -97,6 +97,17 @@ protected void waitForReTransformation(String className) { } } + protected void waitForSpecificLine(String line) { + System.out.println("waitForSpecificLine..."); + try { + lastMatchedLine = + TestApplicationHelper.waitForSpecificLine(LOG_FILENAME, line, lastMatchedLine); + System.out.println("line found!"); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + protected void execute(String methodName, String arg) { Consumer method = methodsByName.get(methodName); if (method == null) { @@ -254,6 +265,12 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio app.waitForReTransformation(className); break; } + case "/app/waitForSpecificLine": + { + String feature = request.getRequestUrl().queryParameter("line"); + app.waitForSpecificLine(feature); + break; + } case "/app/execute": { String methodName = request.getRequestUrl().queryParameter("methodname"); diff --git a/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/TestApplicationHelper.java b/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/TestApplicationHelper.java index b19aa2175c3..b69e697b704 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/TestApplicationHelper.java +++ b/dd-smoke-tests/debugger-integration-tests/src/main/java/datadog/smoketest/debugger/TestApplicationHelper.java @@ -90,6 +90,17 @@ public static String waitForReTransformation( Duration.ofSeconds(TIMEOUT_S)); } + public static String waitForSpecificLine(String logFileName, String specificLine, String fromLine) + throws IOException { + return waitForSpecificLogLine( + Paths.get(logFileName), + fromLine != null ? line -> line.contains(fromLine) : null, + line -> line.contains(specificLine), + () -> {}, + Duration.ofMillis(SLEEP_MS), + Duration.ofSeconds(TIMEOUT_S)); + } + public static void waitForUpload(String logFileName, int expectedUploads) throws IOException { if (expectedUploads == -1) { System.out.println("wait for " + TIMEOUT_S + "s"); diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/BaseIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/BaseIntegrationTest.java index d366c671f48..053c331c482 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/BaseIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/BaseIntegrationTest.java @@ -14,7 +14,9 @@ import com.datadog.debugger.sink.Snapshot; import com.datadog.debugger.util.MoshiHelper; import com.datadog.debugger.util.MoshiSnapshotTestHelper; +import com.squareup.moshi.Json; import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.ProbeRateLimiter; @@ -80,7 +82,10 @@ public abstract class BaseIntegrationTest { protected static final MockResponse EMPTY_200_RESPONSE = new MockResponse().setResponseCode(200); private static final ByteString DIAGNOSTICS_STR = ByteString.encodeUtf8("diagnostics"); - private static final String CONFIG_ID = UUID.randomUUID().toString(); + private static final String LD_CONFIG_ID = UUID.randomUUID().toString(); + private static final String APM_CONFIG_ID = UUID.randomUUID().toString(); + public static final String LIVE_DEBUGGING_PRODUCT = "LIVE_DEBUGGING"; + public static final String APM_TRACING_PRODUCT = "APM_TRACING"; protected MockWebServer datadogAgentServer; private MockDispatcher probeMockDispatcher; @@ -90,6 +95,7 @@ public abstract class BaseIntegrationTest { protected Path logFilePath; protected Process targetProcess; private Configuration currentConfiguration; + private ConfigOverrides configOverrides; private boolean configProvided; protected final Object configLock = new Object(); protected final List> intakeRequestListeners = @@ -147,6 +153,7 @@ protected List getDebuggerCommandParams() { "-Ddd.profiling.enabled=false", "-Ddatadog.slf4j.simpleLogger.defaultLogLevel=info", "-Ddatadog.slf4j.simpleLogger.log.com.datadog.debugger=debug", + "-Ddatadog.slf4j.simpleLogger.log.datadog.remoteconfig=debug", "-Ddd.jmxfetch.start-delay=0", "-Ddd.jmxfetch.enabled=false", "-Ddd.dynamic.instrumentation.enabled=true", @@ -413,10 +420,12 @@ private MockResponse datadogAgentDispatch(RecordedRequest request) { return EMPTY_200_RESPONSE; } - private MockResponse handleConfigRequests() { + protected MockResponse handleConfigRequests() { Configuration configuration; + ConfigOverrides configOverrides; synchronized (configLock) { configuration = getCurrentConfiguration(); + configOverrides = getConfigOverrides(); configProvided = true; configLock.notifyAll(); } @@ -426,9 +435,22 @@ private MockResponse handleConfigRequests() { try { JsonAdapter adapter = MoshiConfigTestHelper.createMoshiConfig().adapter(Configuration.class); - String json = adapter.toJson(configuration); - LOG.info("Sending json config: {}", json); - String remoteConfigJson = RemoteConfigHelper.encode(json, CONFIG_ID); + String liveDebuggingJson = adapter.toJson(configuration); + LOG.info("Sending Live Debugging json: {}", liveDebuggingJson); + List remoteConfigs = new ArrayList<>(); + remoteConfigs.add( + new RemoteConfigHelper.RemoteConfig( + LIVE_DEBUGGING_PRODUCT, liveDebuggingJson, LD_CONFIG_ID)); + if (configOverrides != null) { + JsonAdapter configAdapter = + new Moshi.Builder().build().adapter(ConfigOverrides.class); + String configOverridesJson = configAdapter.toJson(configOverrides); + LOG.info("Sending configOverrides json: {}", configOverridesJson); + remoteConfigs.add( + new RemoteConfigHelper.RemoteConfig( + APM_TRACING_PRODUCT, configOverridesJson, APM_CONFIG_ID)); + } + String remoteConfigJson = RemoteConfigHelper.encode(remoteConfigs); return new MockResponse().setResponseCode(200).setBody(remoteConfigJson); } catch (Exception e) { throw new RuntimeException(e); @@ -441,6 +463,12 @@ private Configuration getCurrentConfiguration() { } } + private ConfigOverrides getConfigOverrides() { + synchronized (configLock) { + return configOverrides; + } + } + protected boolean isConfigProvided() { synchronized (configLock) { return configProvided; @@ -461,6 +489,12 @@ protected void setCurrentConfiguration(Configuration configuration) { } } + protected void setConfigOverrides(ConfigOverrides configOverrides) { + synchronized (configLock) { + this.configOverrides = configOverrides; + } + } + protected Configuration createConfig(LogProbe logProbe) { return createConfig(Arrays.asList(logProbe)); } @@ -614,4 +648,23 @@ int getPort() { return socket.getLocalPort(); } } + + static final class ConfigOverrides { + @Json(name = "lib_config") + public LibConfig libConfig; + } + + static final class LibConfig { + @Json(name = "dynamic_instrumentation_enabled") + public Boolean dynamicInstrumentationEnabled; + + @Json(name = "exception_replay_enabled") + public Boolean exceptionReplayEnabled; + + @Json(name = "code_origin_enabled") + public Boolean codeOriginEnabled; + + @Json(name = "live_debugging_enabled") + public Boolean liveDebuggingEnabled; + } } diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/CodeOriginIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/CodeOriginIntegrationTest.java index 9c22dd34643..1d91d1a2694 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/CodeOriginIntegrationTest.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/CodeOriginIntegrationTest.java @@ -50,7 +50,7 @@ void testCodeOriginTraceAnnotation() throws Exception { assertEquals("runTracedMethod", span.getMeta().get(DD_CODE_ORIGIN_FRAMES_0_METHOD)); assertEquals( "(java.lang.String)", span.getMeta().get(DD_CODE_ORIGIN_FRAMES_0_SIGNATURE)); - assertEquals("134", span.getMeta().get(DD_CODE_ORIGIN_FRAMES_0_LINE)); + assertEquals("145", span.getMeta().get(DD_CODE_ORIGIN_FRAMES_0_LINE)); codeOrigin.set(true); } } diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/InProductEnablementIntegrationTest.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/InProductEnablementIntegrationTest.java new file mode 100644 index 00000000000..8a7c7606df8 --- /dev/null +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/InProductEnablementIntegrationTest.java @@ -0,0 +1,77 @@ +package datadog.smoketest; + +import com.datadog.debugger.probe.LogProbe; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class InProductEnablementIntegrationTest extends ServerAppDebuggerIntegrationTest { + private List additionalJvmArgs = new ArrayList<>(); + + @Override + protected ProcessBuilder createProcessBuilder(Path logFilePath, String... params) { + List commandParams = getDebuggerCommandParams(); + // remove the dynamic instrumentation flag + commandParams.remove("-Ddd.dynamic.instrumentation.enabled=true"); + commandParams.addAll(additionalJvmArgs); + return ProcessBuilderHelper.createProcessBuilder( + commandParams, logFilePath, getAppClass(), params); + } + + @Test + @DisplayName("testDynamicInstrumentationEnablement") + void testDynamicInstrumentationEnablement() throws Exception { + appUrl = startAppAndAndGetUrl(); + setConfigOverrides(createConfigOverrides(true, false)); + LogProbe probe = + LogProbe.builder().probeId(PROBE_ID).where(TEST_APP_CLASS_NAME, TRACED_METHOD_NAME).build(); + setCurrentConfiguration(createConfig(probe)); + waitForFeatureStarted(appUrl, "Dynamic Instrumentation"); + waitForInstrumentation(appUrl); + // disable DI + setConfigOverrides(createConfigOverrides(false, false)); + waitForFeatureStopped(appUrl, "Dynamic Instrumentation"); + waitForReTransformation(appUrl); // wait for retransformation of removed probe + } + + @Test + @DisplayName("testExceptionReplayEnablement") + void testExceptionReplayEnablement() throws Exception { + additionalJvmArgs.add("-Ddd.third.party.excludes=datadog.smoketest"); + appUrl = startAppAndAndGetUrl(); + setConfigOverrides(createConfigOverrides(false, true)); + waitForFeatureStarted(appUrl, "Exception Replay"); + execute(appUrl, TRACED_METHOD_NAME, "oops"); // instrumenting first exception + waitForInstrumentation(appUrl); + // disable ER + setConfigOverrides(createConfigOverrides(false, false)); + waitForFeatureStopped(appUrl, "Exception Replay"); + waitForReTransformation(appUrl); // wait for retransformation of removed probes + } + + private void waitForFeatureStarted(String appUrl, String feature) throws IOException { + String line = "INFO com.datadog.debugger.agent.DebuggerAgent - Starting " + feature; + String url = String.format(appUrl + "/waitForSpecificLine?line=%s", line); + sendRequest(url); + LOG.info("feature {} started", feature); + } + + private void waitForFeatureStopped(String appUrl, String feature) throws IOException { + String line = "INFO com.datadog.debugger.agent.DebuggerAgent - Stopping " + feature; + String url = String.format(appUrl + "/waitForSpecificLine?line=%s", line); + sendRequest(url); + LOG.info("feature {} stopped", feature); + } + + private static ConfigOverrides createConfigOverrides( + boolean dynamicInstrumentationEnabled, boolean exceptionReplayEnabled) { + ConfigOverrides config = new ConfigOverrides(); + config.libConfig = new LibConfig(); + config.libConfig.dynamicInstrumentationEnabled = dynamicInstrumentationEnabled; + config.libConfig.exceptionReplayEnabled = exceptionReplayEnabled; + return config; + } +} diff --git a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/RemoteConfigHelper.java b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/RemoteConfigHelper.java index 17fbcd73a30..d9f7c7cc85d 100644 --- a/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/RemoteConfigHelper.java +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/RemoteConfigHelper.java @@ -2,50 +2,107 @@ import datadog.trace.util.Strings; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; +import java.util.StringJoiner; public class RemoteConfigHelper { - public static String encode(String config, String configId) { - String hashStr = null; - try { - hashStr = Strings.sha256(config); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); + public static class RemoteConfig { + public final String product; + public final String config; + public final String configId; + + public RemoteConfig(String product, String config, String configId) { + this.product = product; + this.config = config; + this.configId = configId; } - String targetsStr = - String.format( - "{\"signed\":\n" - + " { \"_type\":\"targets\",\n" - + " \"spec_version\": \"1.0\",\n" - + " \"version\": \"2\",\n" - + " \"custom\": { \"opaque_backend_state\": \"opaque\" },\n" - + " \"targets\":\n" - + " { \"datadog/2/LIVE_DEBUGGING/%s/config\":{" - + " \"length\": %d,\n" - + " \"custom\": { \"v\": 123 },\n" - + " \"hashes\":\n" - + " {\n" - + " \"sha256\": \"%s\"\n" - + " }" - + " }" - + " }" - + " }" - + "}", - configId, config.length(), hashStr); - String targetsEncoding = new String(Base64.getEncoder().encode(targetsStr.getBytes())); - String encodedConfig = new String(Base64.getEncoder().encode(config.getBytes())); + } + + public static String encode(List remoteConfigs) { + List hashes = buildHashes(remoteConfigs); + String targetsStr = buildTargets(hashes, remoteConfigs); + String targetsEncoded = new String(Base64.getEncoder().encode(targetsStr.getBytes())); + String targetFiles = buildTargetFiles(remoteConfigs); + String clientConfigs = buildClientConfigs(remoteConfigs); return String.format( "{\n" + "\"targets\": \"%s\",\n" + "\"target_files\": [\n" - + " {\n" - + " \"path\": \"datadog/2/LIVE_DEBUGGING/%s/config\",\n" - + " \"raw\": \"%s\"\n" - + "}]," + + " %s\n" + + "]," + "\"client_configs\": [\n" - + " \"datadog/2/LIVE_DEBUGGING/%s/config\"\n" + + " %s\n" + " ]" + "}", - targetsEncoding, configId, encodedConfig, configId); + targetsEncoded, targetFiles, clientConfigs); + } + + private static String buildClientConfigs(List remoteConfigs) { + StringJoiner sj = new StringJoiner(",\n"); + for (RemoteConfig rc : remoteConfigs) { + sj.add( + String.format( + " \"datadog/2/%s/%s/config\"\n", rc.product, rc.configId)); + } + return sj.toString(); + } + + private static String buildTargetFiles(List remoteConfigs) { + StringJoiner sj = new StringJoiner(",\n"); + for (RemoteConfig rc : remoteConfigs) { + String encodedConfig = new String(Base64.getEncoder().encode(rc.config.getBytes())); + sj.add( + String.format( + " {\n" + + " \"path\": \"datadog/2/%s/%s/config\",\n" + + " \"raw\": \"%s\"\n" + + " }\n", + rc.product, rc.configId, encodedConfig)); + } + return sj.toString(); + } + + private static List buildHashes(List remoteConfigs) { + List hashes = new ArrayList<>(); + for (RemoteConfig rc : remoteConfigs) { + try { + hashes.add(Strings.sha256(rc.config)); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + return hashes; + } + + private static String buildTargets(List hashes, List remoteConfigs) { + StringJoiner sj = new StringJoiner(",\n"); + for (int i = 0; i < remoteConfigs.size(); i++) { + RemoteConfig rc = remoteConfigs.get(i); + sj.add( + String.format( + " \"datadog/2/%s/%s/config\":{" + + " \"length\": %d,\n" + + " \"custom\": { \"v\": 123 },\n" + + " \"hashes\":\n" + + " {\n" + + " \"sha256\": \"%s\"\n" + + " }" + + " }", + rc.product, rc.configId, rc.config.length(), hashes.get(i))); + } + String targets = sj.toString(); + return String.format( + "{\"signed\":\n" + + " { \"_type\":\"targets\",\n" + + " \"spec_version\": \"1.0\",\n" + + " \"version\": \"2\",\n" + + " \"custom\": { \"opaque_backend_state\": \"opaque\" },\n" + + " \"targets\":\n" + + " { %s }" + + " }" + + "}", + targets); } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java index 0b576654936..27ec36027ce 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java @@ -356,15 +356,15 @@ public DDSpan addThrowable(Throwable error, byte errorPriority) { setTag(DDTags.ERROR_MSG, message); setTag(DDTags.ERROR_TYPE, error.getClass().getName()); - if (isExceptionDebuggingEnabled()) { + if (isExceptionReplayEnabled()) { DebuggerContext.handleException(error, this); } } return this; } - private boolean isExceptionDebuggingEnabled() { - if (!Config.get().isDebuggerExceptionEnabled()) { + private boolean isExceptionReplayEnabled() { + if (!DebuggerContext.isExceptionReplayEnabled()) { return false; } boolean captureOnlyRootSpan = diff --git a/dd-trace-core/src/main/java/datadog/trace/core/TracingConfigPoller.java b/dd-trace-core/src/main/java/datadog/trace/core/TracingConfigPoller.java index bf5421617e7..60bb3a4a3bc 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/TracingConfigPoller.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/TracingConfigPoller.java @@ -4,6 +4,10 @@ import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_HTTP_HEADER_TAGS; import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_LOGS_INJECTION; import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_DATA_STREAMS_ENABLED; +import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_ENABLE_CODE_ORIGIN; +import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION; +import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_ENABLE_EXCEPTION_REPLAY; +import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_ENABLE_LIVE_DEBUGGING; import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_SAMPLE_RATE; import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_SAMPLE_RULES; import static datadog.remoteconfig.Capabilities.CAPABILITY_APM_TRACING_TRACING_ENABLED; @@ -24,6 +28,7 @@ import datadog.trace.api.Config; import datadog.trace.api.DynamicConfig; import datadog.trace.api.sampling.SamplingRule; +import datadog.trace.bootstrap.debugger.DebuggerContext; import datadog.trace.logging.GlobalLogLevelSwitcher; import datadog.trace.logging.LogLevel; import java.io.ByteArrayInputStream; @@ -65,7 +70,11 @@ public void start(Config config, SharedCommunicationObjects sco) { | CAPABILITY_APM_HTTP_HEADER_TAGS | CAPABILITY_APM_CUSTOM_TAGS | CAPABILITY_APM_TRACING_DATA_STREAMS_ENABLED - | CAPABILITY_APM_TRACING_SAMPLE_RULES); + | CAPABILITY_APM_TRACING_SAMPLE_RULES + | CAPABILITY_APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION + | CAPABILITY_APM_TRACING_ENABLE_EXCEPTION_REPLAY + | CAPABILITY_APM_TRACING_ENABLE_CODE_ORIGIN + | CAPABILITY_APM_TRACING_ENABLE_LIVE_DEBUGGING); } stopPolling = new Updater().register(config, configPoller); } @@ -211,7 +220,11 @@ void applyConfigOverrides(LibConfig libConfig) { maybeOverride(builder::setTraceSampleRate, libConfig.traceSampleRate); maybeOverride(builder::setTracingTags, parseTagListToMap(libConfig.tracingTags)); - + DebuggerContext.updateConfig( + libConfig.dynamicInstrumentationEnabled, + libConfig.exceptionReplayEnabled, + libConfig.codeOriginEnabled, + libConfig.liveDebuggingEnabled); builder.apply(); } @@ -282,6 +295,18 @@ static final class LibConfig { @Json(name = "tracing_sampling_rules") public TracingSamplingRules tracingSamplingRules; + + @Json(name = "dynamic_instrumentation_enabled") + public Boolean dynamicInstrumentationEnabled; + + @Json(name = "exception_replay_enabled") + public Boolean exceptionReplayEnabled; + + @Json(name = "code_origin_enabled") + public Boolean codeOriginEnabled; + + @Json(name = "live_debugging_enabled") + public Boolean liveDebuggingEnabled; } /** Holds the raw JSON string and the parsed rule data. */ diff --git a/remote-config/remote-config-api/src/main/java/datadog/remoteconfig/Capabilities.java b/remote-config/remote-config-api/src/main/java/datadog/remoteconfig/Capabilities.java index 5aea9ca50bd..b8e1124a1b2 100644 --- a/remote-config/remote-config-api/src/main/java/datadog/remoteconfig/Capabilities.java +++ b/remote-config/remote-config-api/src/main/java/datadog/remoteconfig/Capabilities.java @@ -38,4 +38,8 @@ public interface Capabilities { long CAPABILITY_ASM_NETWORK_FINGERPRINT = 1L << 34; long CAPABILITY_ASM_HEADER_FINGERPRINT = 1L << 35; long CAPABILITY_ASM_RASP_CMDI = 1L << 37; + long CAPABILITY_APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION = 1L << 38; + long CAPABILITY_APM_TRACING_ENABLE_EXCEPTION_REPLAY = 1L << 39; + long CAPABILITY_APM_TRACING_ENABLE_CODE_ORIGIN = 1L << 40; + long CAPABILITY_APM_TRACING_ENABLE_LIVE_DEBUGGING = 1L << 41; }