diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationAcceptor.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationAcceptor.java index dd83728833d..e2b2c6601fe 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationAcceptor.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationAcceptor.java @@ -6,6 +6,7 @@ public interface ConfigurationAcceptor { enum Source { REMOTE_CONFIG, + LOCAL_FILE, CODE_ORIGIN, EXCEPTION } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationFileLoader.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationFileLoader.java new file mode 100644 index 00000000000..c33243e21c5 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationFileLoader.java @@ -0,0 +1,145 @@ +package com.datadog.debugger.agent; + +import com.datadog.debugger.probe.LogProbe; +import com.datadog.debugger.probe.MetricProbe; +import com.datadog.debugger.probe.ProbeDefinition; +import com.datadog.debugger.probe.SpanDecorationProbe; +import com.datadog.debugger.probe.SpanProbe; +import com.datadog.debugger.probe.TriggerProbe; +import com.datadog.debugger.util.MoshiHelper; +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonWriter; +import com.squareup.moshi.Moshi; +import com.squareup.moshi.Types; +import datadog.trace.util.SizeCheckedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import okio.Okio; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConfigurationFileLoader { + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationFileLoader.class); + + public static Configuration from(Path probeFilePath, long maxPayloadSize) { + LOGGER.debug("try to load from file..."); + try (InputStream inputStream = + new SizeCheckedInputStream(new FileInputStream(probeFilePath.toFile()), maxPayloadSize)) { + byte[] buffer = new byte[4096]; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(4096); + int bytesRead; + do { + bytesRead = inputStream.read(buffer); + if (bytesRead > -1) { + outputStream.write(buffer, 0, bytesRead); + } + } while (bytesRead > -1); + byte[] configContent = outputStream.toByteArray(); + Moshi moshi = MoshiHelper.createMoshiConfigBuilder().add(new ProbeFileFactory()).build(); + ParameterizedType type = Types.newParameterizedType(List.class, ProbeDefinition.class); + JsonAdapter> adapter = moshi.adapter(type); + List probeDefinitions = + adapter.fromJson( + JsonReader.of(Okio.buffer(Okio.source(new ByteArrayInputStream(configContent))))); + return new Configuration(null, probeDefinitions); + } catch (IOException ex) { + LOGGER.error("Unable to load config file {}: {}", probeFilePath, ex); + return null; + } + } + + private static class ProbeFileFactory implements JsonAdapter.Factory { + @Override + public JsonAdapter create(Type type, Set annotations, Moshi moshi) { + if (Types.equals(type, Types.newParameterizedType(List.class, ProbeDefinition.class))) { + return new ProbeFileAdapter( + moshi.adapter(LogProbe.class), + moshi.adapter(MetricProbe.class), + moshi.adapter(SpanProbe.class), + moshi.adapter(SpanDecorationProbe.class), + moshi.adapter(TriggerProbe.class)); + } + return null; + } + } + + private static class ProbeFileAdapter extends JsonAdapter> { + private final JsonAdapter logProbeAdapter; + private final JsonAdapter metricProbeAdapter; + private final JsonAdapter spanProbeAdapter; + private final JsonAdapter spanDecorationProbeAdapter; + private final JsonAdapter triggerProbeAdapter; + + public ProbeFileAdapter( + JsonAdapter logProbeAdapter, + JsonAdapter metricProbeAdapter, + JsonAdapter spanProbeAdapter, + JsonAdapter spanDecorationProbeAdapter, + JsonAdapter triggerProbeAdapter) { + this.logProbeAdapter = logProbeAdapter; + this.metricProbeAdapter = metricProbeAdapter; + this.spanProbeAdapter = spanProbeAdapter; + this.spanDecorationProbeAdapter = spanDecorationProbeAdapter; + this.triggerProbeAdapter = triggerProbeAdapter; + } + + @Override + public List fromJson(JsonReader reader) throws IOException { + List probeDefinitions = new ArrayList<>(); + reader.beginArray(); + while (reader.hasNext()) { + if (reader.peek() == JsonReader.Token.END_ARRAY) { + reader.endArray(); + break; + } + JsonReader jsonPeekReader = reader.peekJson(); + jsonPeekReader.beginObject(); + while (jsonPeekReader.hasNext()) { + if (jsonPeekReader.selectName(JsonReader.Options.of("type")) == 0) { + String type = jsonPeekReader.nextString(); + switch (type) { + case "LOG_PROBE": + probeDefinitions.add(logProbeAdapter.fromJson(reader)); + break; + case "METRIC_PROBE": + probeDefinitions.add(metricProbeAdapter.fromJson(reader)); + break; + case "SPAN_PROBE": + probeDefinitions.add(spanProbeAdapter.fromJson(reader)); + break; + case "SPAN_DECORATION_PROBE": + probeDefinitions.add(spanDecorationProbeAdapter.fromJson(reader)); + break; + case "TRIGGER_PROBE": + probeDefinitions.add(triggerProbeAdapter.fromJson(reader)); + break; + default: + throw new RuntimeException("Unknown type: " + type); + } + break; + } else { + jsonPeekReader.skipName(); + jsonPeekReader.skipValue(); + } + } + } + return probeDefinitions; + } + + @Override + public void toJson(JsonWriter writer, List value) throws IOException { + // Implement the logic to write the list of ProbeDefinition to JSON + } + } +} 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 38b600fc12c..f18771de9c7 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 @@ -33,12 +33,8 @@ import datadog.trace.bootstrap.debugger.util.Redaction; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.DDTraceCoreInfo; -import datadog.trace.util.SizeCheckedInputStream; import datadog.trace.util.TagsHelper; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.ref.WeakReference; @@ -155,8 +151,14 @@ public static void startDynamicInstrumentation() { String probeFileLocation = config.getDynamicInstrumentationProbeFile(); if (probeFileLocation != null) { Path probeFilePath = Paths.get(probeFileLocation); - loadFromFile( - probeFilePath, configurationUpdater, config.getDynamicInstrumentationMaxPayloadSize()); + Configuration configuration = + ConfigurationFileLoader.from( + probeFilePath, config.getDynamicInstrumentationMaxPayloadSize()); + if (configuration != null) { + LOGGER.debug("Probe definitions loaded from file {}", probeFilePath); + configurationUpdater.accept( + ConfigurationAcceptor.Source.LOCAL_FILE, configuration.getDefinitions()); + } return; } if (configurationPoller != null) { @@ -334,30 +336,6 @@ private static void setupSourceFileTracking( instrumentation.addTransformer(sourceFileTrackingTransformer); } - private static void loadFromFile( - Path probeFilePath, ConfigurationUpdater configurationUpdater, long maxPayloadSize) { - LOGGER.debug("try to load from file..."); - try (InputStream inputStream = - new SizeCheckedInputStream(new FileInputStream(probeFilePath.toFile()), maxPayloadSize)) { - byte[] buffer = new byte[4096]; - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(4096); - int bytesRead; - do { - bytesRead = inputStream.read(buffer); - if (bytesRead > -1) { - outputStream.write(buffer, 0, bytesRead); - } - } while (bytesRead > -1); - Configuration configuration = - DebuggerProductChangesListener.Adapter.deserializeConfiguration( - outputStream.toByteArray()); - LOGGER.debug("Probe definitions loaded from file {}", probeFilePath); - configurationUpdater.accept(REMOTE_CONFIG, configuration.getDefinitions()); - } catch (IOException ex) { - LOGGER.error("Unable to load config file {}: {}", probeFilePath, ex); - } - } - private static void subscribeConfigurationPoller( Config config, ConfigurationUpdater configurationUpdater, SymDBEnablement symDBEnablement) { LOGGER.debug("Subscribing to Live Debugging..."); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java index 788534d1497..b246b32dafa 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/MoshiHelper.java @@ -17,6 +17,10 @@ public class MoshiHelper { public static Moshi createMoshiConfig() { + return createMoshiConfigBuilder().build(); + } + + public static Moshi.Builder createMoshiConfigBuilder() { ProbeCondition.ProbeConditionJsonAdapter probeConditionJsonAdapter = new ProbeCondition.ProbeConditionJsonAdapter(); return new Moshi.Builder() @@ -25,8 +29,7 @@ public static Moshi createMoshiConfig() { .add(ValueScript.class, new ValueScript.ValueScriptAdapter()) .add(LogProbe.Segment.class, new LogProbe.Segment.SegmentJsonAdapter()) .add(Where.SourceLine[].class, new Where.SourceLineAdapter()) - .add(ProbeDefinition.Tag[].class, new ProbeDefinition.TagAdapter()) - .build(); + .add(ProbeDefinition.Tag[].class, new ProbeDefinition.TagAdapter()); } public static Moshi createMoshiSnapshot() { diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationFileLoaderTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationFileLoaderTest.java new file mode 100644 index 00000000000..3dd1f04cc94 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationFileLoaderTest.java @@ -0,0 +1,31 @@ +package com.datadog.debugger.agent; + +import static org.junit.jupiter.api.Assertions.*; + +import com.datadog.debugger.probe.LogProbe; +import com.datadog.debugger.probe.MetricProbe; +import com.datadog.debugger.probe.ProbeDefinition; +import com.datadog.debugger.probe.SpanDecorationProbe; +import com.datadog.debugger.probe.SpanProbe; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ConfigurationFileLoaderTest { + + @Test + public void load() throws Exception { + Path probeFilePath = + Paths.get(ConfigurationFileLoaderTest.class.getResource("/test_probe_file.json").toURI()); + Configuration configuration = ConfigurationFileLoader.from(probeFilePath, 1024 * 1024); + assertNotNull(configuration); + List definitions = configuration.getDefinitions(); + assertEquals(5, definitions.size()); + assertInstanceOf(MetricProbe.class, definitions.get(0)); + assertInstanceOf(LogProbe.class, definitions.get(1)); + assertInstanceOf(LogProbe.class, definitions.get(2)); + assertInstanceOf(SpanProbe.class, definitions.get(3)); + assertInstanceOf(SpanDecorationProbe.class, definitions.get(4)); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentTest.java index 1331a80a232..85c2a5f4605 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/DebuggerAgentTest.java @@ -83,11 +83,8 @@ public void tearDown() throws IOException { @EnabledOnJre({JAVA_8, JAVA_11}) public void runDisabled() { setFieldInConfig(Config.get(), "dynamicInstrumentationEnabled", false); - URL probeDefinitionUrl = DebuggerAgentTest.class.getResource("/test_probe.json"); - System.setProperty("dd.dynamic.instrumentation.config-file", probeDefinitionUrl.getFile()); DebuggerAgent.run(inst, new SharedCommunicationObjects()); verify(inst, never()).addTransformer(any(), eq(true)); - System.clearProperty("dd.dynamic.instrumentation.config-file"); } @Test @@ -148,7 +145,7 @@ public void runEnabledWithUnsupportedDatadogAgent() throws InterruptedException @Test @EnabledOnJre({JAVA_8, JAVA_11}) public void readFromFile() throws URISyntaxException { - URL res = getClass().getClassLoader().getResource("test_probe2.json"); + URL res = getClass().getClassLoader().getResource("test_probe_file.json"); String probeDefinitionPath = Paths.get(res.toURI()).toFile().getAbsolutePath(); setFieldInConfig(Config.get(), "serviceName", "petclinic"); setFieldInConfig(Config.get(), "dynamicInstrumentationEnabled", true); diff --git a/dd-java-agent/agent-debugger/src/test/resources/test_probe_file.json b/dd-java-agent/agent-debugger/src/test/resources/test_probe_file.json new file mode 100644 index 00000000000..9acd77856c9 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/resources/test_probe_file.json @@ -0,0 +1,91 @@ +[ + { + "id": "123356536", + "version": 0, + "language": "java", + "type": "LOG_PROBE", + "where": { + "typeName": "java.lang.Object", + "methodName": "toString", + "signature": "java.lang.String ()" + } + }, + { + "id": "100c9a5c-45ad-49dc-818b-c570d31e11d1", + "version": 0, + "type": "LOG_PROBE", + "where": { + "sourceFile": "index.js", + "lines": ["25"] + }, + "template": "Hello World", + "segments": [{ + "str": "Hello World" + }], + "captureSnapshot": true, + "capture": { "maxReferenceDepth": 3 }, + "sampling": { "snapshotsPerSecond": 100 } + }, + { + "id": "123356537", + "language": "java", + "type": "METRIC_PROBE", + "where": { + "typeName": "VetController", + "methodName": "showVetList" + }, + "tags": ["version:v123", "env:staging"], + "kind": "COUNT", + "metricName": "datadog.debugger.showVetList.calls", + "value": { + "dsl": "42", + "json": 42 + } + }, + { + "id": "ad4cba6f-d476-4554-b5ed-80dd941a40d8", + "version": 0, + "type": "SPAN_DECORATION_PROBE", + "language": "java", + "where": { + "typeName": "com.datadog.debugger.demomonitor.DemoRequestScheduledJob", + "methodName": "fireRequests", + "signature": "()" + }, + "tags": [], + "evaluateAt": "EXIT", + "targetSpan": "ROOT", + "decorations": [ + { + "tags": [ + { + "name": "client", + "value": { + "segments": [ + { + "dsl": "client", + "json": { + "ref": "client" + } + } + ], + "template": "{client}" + } + } + ] + } + ] + }, + { + "id": "70b55d06-f9fa-403b-a329-4f2f960aed01", + "version": 0, + "type": "SPAN_PROBE", + "language": "java", + "where": { + "typeName": "MetadataClientUtils", + "methodName": "listTableWithContinuation" + }, + "tags": [], + "evaluateAt": "EXIT" + } +]