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 78a069f8c33..e55d6e99ef9 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 @@ -101,9 +101,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); @@ -154,9 +154,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; /** @@ -272,9 +273,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) { @@ -1133,7 +1134,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) { @@ -1143,6 +1147,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..9f1b08fa0fe 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,11 +1,12 @@ 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; import com.datadog.debugger.codeorigin.DefaultCodeOriginRecorder; import com.datadog.debugger.exception.DefaultExceptionDebugger; -import com.datadog.debugger.exception.ExceptionProbeManager; import com.datadog.debugger.sink.DebuggerSink; import com.datadog.debugger.sink.ProbeStatusSink; import com.datadog.debugger.sink.SnapshotSink; @@ -40,6 +41,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,20 +51,66 @@ /** Debugger agent implementation */ public class DebuggerAgent { private static final Logger LOGGER = LoggerFactory.getLogger(DebuggerAgent.class); + private static Instrumentation instrumentation; + private static SharedCommunicationObjects sharedCommunicationObjects; private static ConfigurationPoller configurationPoller; private static DebuggerSink sink; private static String agentVersion; private static JsonSnapshotSerializer snapshotSerializer; - private static SymDBEnablement symDBEnablement; + private static volatile ClassNameFilter classNameFilter; + private static volatile SymDBEnablement symDBEnablement; + private static volatile ConfigurationUpdater configurationUpdater; + private static volatile DefaultExceptionDebugger exceptionDebugger; + private static final AtomicBoolean commonInitDone = new AtomicBoolean(); + static final AtomicBoolean dynamicInstrumentationEnabled = new AtomicBoolean(); + static final AtomicBoolean exceptionReplayEnabled = new AtomicBoolean(); + static final AtomicBoolean codeOriginEnabled = new AtomicBoolean(); + static final AtomicBoolean distributedDebuggerEnabled = new AtomicBoolean(); + private static ClassesToRetransformFinder classesToRetransformFinder; - public static synchronized void run( - Instrumentation instrumentation, SharedCommunicationObjects sco) { + public static synchronized void run(Instrumentation inst, SharedCommunicationObjects sco) { + instrumentation = inst; + sharedCommunicationObjects = sco; Config config = Config.get(); - ClassesToRetransformFinder classesToRetransformFinder = new ClassesToRetransformFinder(); - setupSourceFileTracking(instrumentation, classesToRetransformFinder); + DebuggerContext.initProductConfigUpdater(new DefaultProductConfigUpdater()); + classesToRetransformFinder = new ClassesToRetransformFinder(); + if (config.isDynamicInstrumentationEnabled() || config.isDebuggerExceptionEnabled()) { + // only activate Source File Tracking if DI or ER is enabled from the start + setupSourceFileTracking(instrumentation, classesToRetransformFinder); + } + if (config.isDebuggerCodeOriginEnabled()) { + startCodeOriginForSpans(); + } + if (config.isDebuggerExceptionEnabled()) { + startExceptionReplay(); + } + if (config.isDynamicInstrumentationEnabled()) { + startDynamicInstrumentation(); + if (config.isDynamicInstrumentationInstrumentTheWorld()) { + setupInstrumentTheWorldTransformer(config, instrumentation, sink); + } + } + try { + /* + Note: shutdown hooks are tricky because JVM holds reference for them forever preventing + GC for anything that is reachable from it. + */ + Runtime.getRuntime().addShutdownHook(new ShutdownHook(configurationPoller, sink)); + } catch (final IllegalStateException ex) { + // The JVM is already shutting down. + } + TracerFlare.addReporter(DebuggerAgent::addReportToFlare); + } + + private static void commonInit(Config config) { + if (!commonInitDone.compareAndSet(false, true)) { + return; + } + configurationPoller = sharedCommunicationObjects.configurationPoller(config); Redaction.addUserDefinedKeywords(config); Redaction.addUserDefinedTypes(config); - DDAgentFeaturesDiscovery ddAgentFeaturesDiscovery = sco.featuresDiscovery(config); + DDAgentFeaturesDiscovery ddAgentFeaturesDiscovery = + sharedCommunicationObjects.featuresDiscovery(config); ddAgentFeaturesDiscovery.discoverIfOutdated(); agentVersion = ddAgentFeaturesDiscovery.getVersion(); String diagnosticEndpoint = getDiagnosticEndpoint(config, ddAgentFeaturesDiscovery); @@ -70,8 +119,7 @@ public static synchronized void run( config, diagnosticEndpoint, ddAgentFeaturesDiscovery.supportsDebuggerDiagnostics()); DebuggerSink debuggerSink = createDebuggerSink(config, probeStatusSink); debuggerSink.start(); - ClassNameFilter classNameFilter = new ClassNameFiltering(config); - ConfigurationUpdater configurationUpdater = + configurationUpdater = new ConfigurationUpdater( instrumentation, DebuggerAgent::createTransformer, @@ -79,64 +127,27 @@ public static synchronized void run( debuggerSink, classesToRetransformFinder); sink = debuggerSink; - StatsdMetricForwarder statsdMetricForwarder = - new StatsdMetricForwarder(config, probeStatusSink); DebuggerContext.initProbeResolver(configurationUpdater); - DebuggerContext.initMetricForwarder(statsdMetricForwarder); + DebuggerContext.initMetricForwarder(new StatsdMetricForwarder(config, probeStatusSink)); DebuggerContext.initClassFilter(new DenyListHelper(null)); // default hard coded deny list snapshotSerializer = new JsonSnapshotSerializer(); 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); - } - if (config.isDebuggerCodeOriginEnabled()) { - LOGGER.info("Starting Code Origin for spans"); - DebuggerContext.initCodeOrigin(new DefaultCodeOriginRecorder(config, configurationUpdater)); - } - if (config.isDynamicInstrumentationInstrumentTheWorld()) { - setupInstrumentTheWorldTransformer( - config, instrumentation, debuggerSink, statsdMetricForwarder); - } - // Dynamic Instrumentation - if (config.isDynamicInstrumentationEnabled()) { - startDynamicInstrumentation( - instrumentation, sco, config, configurationUpdater, debuggerSink, classNameFilter); - } - try { - /* - Note: shutdown hooks are tricky because JVM holds reference for them forever preventing - GC for anything that is reachable from it. - */ - Runtime.getRuntime().addShutdownHook(new ShutdownHook(configurationPoller, debuggerSink)); - } catch (final IllegalStateException ex) { - // The JVM is already shutting down. + } + + private static void initClassNameFilter() { + if (classNameFilter == null) { + classNameFilter = new ClassNameFiltering(Config.get()); } - ExceptionProbeManager exceptionProbeManager = - defaultExceptionDebugger != null - ? defaultExceptionDebugger.getExceptionProbeManager() - : null; - TracerFlare.addReporter( - new DebuggerReporter(configurationUpdater, sink, exceptionProbeManager)); } - 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(); + commonInit(config); String probeFileLocation = config.getDynamicInstrumentationProbeFile(); if (probeFileLocation != null) { Path probeFilePath = Paths.get(probeFileLocation); @@ -144,14 +155,12 @@ private static void startDynamicInstrumentation( probeFilePath, configurationUpdater, config.getDynamicInstrumentationMaxPayloadSize()); return; } - configurationPoller = sco.configurationPoller(config); if (configurationPoller != null) { if (config.isSymbolDatabaseEnabled()) { + initClassNameFilter(); 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 +174,90 @@ 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(); + symDBEnablement = null; + } + } + + public static void startExceptionReplay() { + if (!exceptionReplayEnabled.compareAndSet(false, true)) { + return; + } + LOGGER.info("Starting Exception Replay"); + Config config = Config.get(); + commonInit(config); + initClassNameFilter(); + 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"); + Config config = Config.get(); + commonInit(config); + initClassNameFilter(); + DebuggerContext.initClassNameFilter(classNameFilter); + DebuggerContext.initCodeOrigin(new DefaultCodeOriginRecorder(config, 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,16 +349,19 @@ 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, - DebuggerSink debuggerSink, - StatsdMetricForwarder statsdMetricForwarder) { + Config config, Instrumentation instrumentation, DebuggerSink debuggerSink) { LOGGER.info("install Instrument-The-World transformer"); DebuggerTransformer transformer = createTransformer(config, Configuration.builder().build(), null, debuggerSink); DebuggerContext.initProbeResolver(transformer::instrumentTheWorldResolver); - DebuggerContext.initMetricForwarder(statsdMetricForwarder); instrumentation.addTransformer(transformer); return transformer; } @@ -342,45 +438,49 @@ 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; + private static void addReportToFlare(ZipOutputStream zip) throws IOException { + String snapshotUrl = null; + String diagnosticUrl = null; + String symbolDbUrl = null; + String probeStatuses = null; + String symbolDBStats = null; + if (sink != null) { + snapshotUrl = sink.getSnapshotSink().getUrl().toString(); + diagnosticUrl = sink.getProbeStatusSink().getUrl().toString(); + symbolDbUrl = sink.getSymbolSink().getUrl().toString(); + probeStatuses = sink.getProbeStatusSink().getProbeStatuses().toString(); + symbolDBStats = sink.getSymbolSink().getStats().toString(); } - - @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); + String probeDefinitions = null; + String instrumentedProbes = null; + if (configurationUpdater != null) { + probeDefinitions = configurationUpdater.getAppliedDefinitions().toString(); + instrumentedProbes = configurationUpdater.getInstrumentationResults().toString(); + } + String exceptionFingerprints = null; + if (exceptionDebugger != null) { + exceptionFingerprints = + exceptionDebugger.getExceptionProbeManager().getFingerprints().toString(); } + String content = + String.join( + System.lineSeparator(), + "Snapshot url: ", + snapshotUrl, + "Diagnostic url: ", + diagnosticUrl, + "SymbolDB url: ", + symbolDbUrl, + "Probe definitions:", + probeDefinitions, + "Instrumented probes:", + instrumentedProbes, + "Probe statuses:", + probeStatuses, + "SymbolDB stats:", + symbolDBStats, + "Exception Fingerprints:", + exceptionFingerprints); + 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..1bfe5cc2c54 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DefaultProductConfigUpdater.java @@ -0,0 +1,79 @@ +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class DefaultProductConfigUpdater implements DebuggerContext.ProductConfigUpdater { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultProductConfigUpdater.class); + + @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)) { + LOGGER.debug("Feature {} is explicitly disabled", booleanKey); + return; + } + if (currentStatus != null && 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/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 5f32095d915..bed200dfb13 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 @@ -2655,8 +2655,7 @@ private TestSnapshotListener setupInstrumentTheWorldTransformer( config, instr, new DebuggerSink( - config, new ProbeStatusSink(config, config.getFinalDebuggerSnapshotUrl(), false)), - null); + config, new ProbeStatusSink(config, config.getFinalDebuggerSnapshotUrl(), false))); DebuggerContext.initClassFilter(new DenyListHelper(null)); return listener; } 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..36814893855 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DefaultProductConfigUpdaterTest.java @@ -0,0 +1,33 @@ +package com.datadog.debugger.agent; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import datadog.communication.ddagent.DDAgentFeaturesDiscovery; +import datadog.communication.ddagent.SharedCommunicationObjects; +import java.lang.instrument.Instrumentation; +import org.junit.jupiter.api.Test; + +class DefaultProductConfigUpdaterTest { + + @Test + public void enableDisable() { + SharedCommunicationObjects sco = mock(SharedCommunicationObjects.class); + when(sco.featuresDiscovery(any())).thenReturn(mock(DDAgentFeaturesDiscovery.class)); + DebuggerAgent.run(mock(Instrumentation.class), sco); + 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 f895b503134..060b7b370c0 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 53101ae222c..473448936a1 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 @@ -45,7 +45,7 @@ void testCodeOriginTraceAnnotation() throws Exception { assertEquals("runTracedMethod", span.getMeta().get(DD_CODE_ORIGIN_FRAME_METHOD)); assertEquals( "(java.lang.String)", span.getMeta().get(DD_CODE_ORIGIN_FRAME_SIGNATURE)); - assertEquals("134", span.getMeta().get(DD_CODE_ORIGIN_FRAME_LINE)); + assertEquals("145", span.getMeta().get(DD_CODE_ORIGIN_FRAME_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..d80d697bcb2 --- /dev/null +++ b/dd-smoke-tests/debugger-integration-tests/src/test/java/datadog/smoketest/InProductEnablementIntegrationTest.java @@ -0,0 +1,95 @@ +package datadog.smoketest; + +import static datadog.smoketest.debugger.TestApplicationHelper.waitForSpecificLine; + +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("testDynamicInstrumentationEnablementStaticallyDisabled") + void testDynamicInstrumentationEnablementStaticallyDisabled() throws Exception { + // explicitly disable dynamic instrumentation, preventing enablement + additionalJvmArgs.add("-Ddd.dynamic.instrumentation.enabled=false"); + 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)); + waitForSpecificLine(appUrl, "Feature dynamic.instrumentation.enabled is explicitly disabled"); + } + + @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; + waitForSpecificLine(appUrl, line); + LOG.info("feature {} started", feature); + } + + private void waitForFeatureStopped(String appUrl, String feature) throws IOException { + String line = "INFO com.datadog.debugger.agent.DebuggerAgent - Stopping " + feature; + waitForSpecificLine(appUrl, line); + LOG.info("feature {} stopped", feature); + } + + private void waitForSpecificLine(String appUrl, String line) throws IOException { + String url = String.format(appUrl + "/waitForSpecificLine?line=%s", line); + sendRequest(url); + } + + 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 5fafa0f29a8..771d22e3758 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; }