From bd148fb06e285f31d7f254ece73d4b15c0b7c3bf Mon Sep 17 00:00:00 2001 From: jean-philippe bempel Date: Fri, 27 Jun 2025 14:38:01 +0200 Subject: [PATCH] Update file probe format now it's only an array of probe with type as attribute. This is same format than stored in RemoteConfig. We need to parse it to look up using the type and reuse the moshi adapters to deserialize the probes. Add source as LOCAL_FILE --- .../debugger/agent/ConfigurationAcceptor.java | 1 + .../agent/ConfigurationFileLoader.java | 145 ++++++++++++++++++ .../datadog/debugger/agent/DebuggerAgent.java | 38 +---- .../datadog/debugger/util/MoshiHelper.java | 7 +- .../agent/ConfigurationFileLoaderTest.java | 31 ++++ .../debugger/agent/DebuggerAgentTest.java | 5 +- .../src/test/resources/test_probe_file.json | 91 +++++++++++ 7 files changed, 282 insertions(+), 36 deletions(-) create mode 100644 dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ConfigurationFileLoader.java create mode 100644 dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ConfigurationFileLoaderTest.java create mode 100644 dd-java-agent/agent-debugger/src/test/resources/test_probe_file.json 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 36eafade871..d12292b75b5 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) { @@ -330,30 +332,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" + } +]