From c0415956517c2ac74142c77e2ea182854518566d Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Fri, 20 Mar 2026 12:27:29 -0400 Subject: [PATCH 1/6] Add stable session ID headers to telemetry requests Implements the Stable Service Instance Identifier RFC for Java. - Add DD-Session-ID header (= runtime_id) to all telemetry requests - Add DD-Root-Session-ID header when process is a child (inherited from parent) - Read _DD_ROOT_JAVA_SESSION_ID from environment at init time - Auto-propagate _DD_ROOT_JAVA_SESSION_ID to child processes via ProcessBuilder instrumentation Co-Authored-By: Claude Opus 4.6 --- .../lang/ProcessBuilderSessionIdAdvice.java | 15 ++++++++ ...rocessBuilderSessionIdInstrumentation.java | 35 ++++++++++++++++++ ...rocessBuilderSessionIdSpecification.groovy | 36 +++++++++++++++++++ .../main/java/datadog/trace/api/Config.java | 11 ++++++ .../datadog/telemetry/TelemetryRequest.java | 10 ++++++ .../telemetry/TestTelemetryRouter.groovy | 5 ++- 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java create mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java new file mode 100644 index 00000000000..fa28eb2e247 --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java @@ -0,0 +1,15 @@ +package datadog.trace.instrumentation.java.lang; + +import datadog.trace.api.Config; +import net.bytebuddy.asm.Advice; + +class ProcessBuilderSessionIdAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void beforeStart(@Advice.This final ProcessBuilder self) { + Config config = Config.get(); + String rootSessionId = config.getRootSessionId(); + if (rootSessionId != null) { + self.environment().put("_DD_ROOT_JAVA_SESSION_ID", rootSessionId); + } + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java new file mode 100644 index 00000000000..e54fa3c27fe --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java @@ -0,0 +1,35 @@ +package datadog.trace.instrumentation.java.lang; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.api.Platform; + +@AutoService(InstrumenterModule.class) +public class ProcessBuilderSessionIdInstrumentation extends InstrumenterModule.Tracing + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + public ProcessBuilderSessionIdInstrumentation() { + super("process-session-id"); + } + + @Override + protected boolean defaultEnabled() { + return super.defaultEnabled() && !Platform.isNativeImageBuilder(); + } + + @Override + public String instrumentedType() { + return "java.lang.ProcessBuilder"; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("start").and(takesNoArguments()), + packageName + ".ProcessBuilderSessionIdAdvice"); + } +} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy new file mode 100644 index 00000000000..c40247c6d8e --- /dev/null +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy @@ -0,0 +1,36 @@ +package datadog.trace.instrumentation.java.lang + +import datadog.trace.agent.test.AgentTestRunner +import datadog.trace.api.Config + +class ProcessBuilderSessionIdSpecification extends AgentTestRunner { + + def "ProcessBuilder injects root session ID into child environment"() { + setup: + def command = ['sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID'] + def pb = new ProcessBuilder(command) + + when: + def process = pb.start() + def output = process.inputStream.text.trim() + process.waitFor() + + then: + output == Config.get().getRootSessionId() + } + + def "child process inherits root session ID not runtime ID"() { + setup: + def command = ['sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID'] + def pb = new ProcessBuilder(command) + + when: + def process = pb.start() + def output = process.inputStream.text.trim() + process.waitFor() + + then: + output == Config.get().getRootSessionId() + Config.get().getRootSessionId() == Config.get().getRuntimeId() + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 5ef24d913fd..39151620a14 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -687,6 +687,7 @@ import static datadog.trace.util.ConfigStrings.propertyNameToEnvironmentVariableName; import static datadog.trace.util.json.JsonPathParser.parseJsonPaths; +import datadog.environment.EnvironmentVariables; import datadog.environment.JavaVirtualMachine; import datadog.environment.OperatingSystem; import datadog.environment.SystemProperties; @@ -792,6 +793,12 @@ public class Config { */ static class RuntimeIdHolder { static final String runtimeId = RandomUtils.randomUUID().toString(); + static final String rootSessionId = initRootSessionId(); + + private static String initRootSessionId() { + String inherited = EnvironmentVariables.get("_DD_ROOT_JAVA_SESSION_ID"); + return inherited != null ? inherited : runtimeId; + } } static class HostNameHolder { @@ -3123,6 +3130,10 @@ public String getRuntimeId() { return runtimeIdEnabled ? RuntimeIdHolder.runtimeId : ""; } + public String getRootSessionId() { + return RuntimeIdHolder.rootSessionId; + } + public Long getProcessId() { return PidHelper.getPidAsLong(); } diff --git a/telemetry/src/main/java/datadog/telemetry/TelemetryRequest.java b/telemetry/src/main/java/datadog/telemetry/TelemetryRequest.java index 9bc2802ba00..5c756d73ad1 100644 --- a/telemetry/src/main/java/datadog/telemetry/TelemetryRequest.java +++ b/telemetry/src/main/java/datadog/telemetry/TelemetryRequest.java @@ -80,6 +80,16 @@ public Request.Builder httpRequest() { builder.addHeader("DD-Telemetry-Debug-Enabled", "true"); } + Config config = Config.get(); + String sessionId = config.getRuntimeId(); + if (sessionId != null && !sessionId.isEmpty()) { + builder.addHeader("DD-Session-ID", sessionId); + } + String rootSessionId = config.getRootSessionId(); + if (rootSessionId != null && !rootSessionId.equals(sessionId)) { + builder.addHeader("DD-Root-Session-ID", rootSessionId); + } + return builder; } diff --git a/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy b/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy index a1b3384375b..dd0a4c60ded 100644 --- a/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy +++ b/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy @@ -84,7 +84,8 @@ class TestTelemetryRouter extends TelemetryRouter { 'DD-Client-Library-Language', 'DD-Client-Library-Version', 'DD-Telemetry-API-Version', - 'DD-Telemetry-Request-Type' + 'DD-Telemetry-Request-Type', + 'DD-Session-ID' ]) assert this.request.header('Content-Type') == 'application/json; charset=utf-8' assert this.request.header('Content-Length').toInteger() > 0 @@ -94,6 +95,8 @@ class TestTelemetryRouter extends TelemetryRouter { assert this.request.header('DD-Telemetry-Request-Type') == requestType.toString() def entityId = this.request.header('Datadog-Entity-ID') assert entityId == null || entityId.startsWith("in-") || entityId.startsWith("cin-") + def sessionId = this.request.header('DD-Session-ID') + assert sessionId =~ /[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}/ return this } From a18eeb9ea909e5fa35c9d0a0b6a72bc86cf9e3ef Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Fri, 20 Mar 2026 12:36:12 -0400 Subject: [PATCH 2/6] Consolidate session ID propagation into existing ProcessImplStartAdvice Remove separate ProcessBuilderSessionId instrumentation files and fold env var injection into the existing ProcessImplStartAdvice. Adds _DD_ROOT_JAVA_SESSION_ID to the child process environment map directly in the ProcessImpl.start() hook. Co-Authored-By: Claude Opus 4.6 --- .../lang/ProcessBuilderSessionIdAdvice.java | 15 -------- ...rocessBuilderSessionIdInstrumentation.java | 35 ------------------ .../java/lang/ProcessImplStartAdvice.java | 12 ++++++- ...rocessBuilderSessionIdSpecification.groovy | 36 ------------------- ...essImplInstrumentationSpecification.groovy | 14 ++++++++ 5 files changed, 25 insertions(+), 87 deletions(-) delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java delete mode 100644 dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java deleted file mode 100644 index fa28eb2e247..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdAdvice.java +++ /dev/null @@ -1,15 +0,0 @@ -package datadog.trace.instrumentation.java.lang; - -import datadog.trace.api.Config; -import net.bytebuddy.asm.Advice; - -class ProcessBuilderSessionIdAdvice { - @Advice.OnMethodEnter(suppress = Throwable.class) - public static void beforeStart(@Advice.This final ProcessBuilder self) { - Config config = Config.get(); - String rootSessionId = config.getRootSessionId(); - if (rootSessionId != null) { - self.environment().put("_DD_ROOT_JAVA_SESSION_ID", rootSessionId); - } - } -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java deleted file mode 100644 index e54fa3c27fe..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdInstrumentation.java +++ /dev/null @@ -1,35 +0,0 @@ -package datadog.trace.instrumentation.java.lang; - -import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; - -import com.google.auto.service.AutoService; -import datadog.trace.agent.tooling.Instrumenter; -import datadog.trace.agent.tooling.InstrumenterModule; -import datadog.trace.api.Platform; - -@AutoService(InstrumenterModule.class) -public class ProcessBuilderSessionIdInstrumentation extends InstrumenterModule.Tracing - implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { - - public ProcessBuilderSessionIdInstrumentation() { - super("process-session-id"); - } - - @Override - protected boolean defaultEnabled() { - return super.defaultEnabled() && !Platform.isNativeImageBuilder(); - } - - @Override - public String instrumentedType() { - return "java.lang.ProcessBuilder"; - } - - @Override - public void methodAdvice(MethodTransformer transformer) { - transformer.applyAdvice( - named("start").and(takesNoArguments()), - packageName + ".ProcessBuilderSessionIdAdvice"); - } -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java index 71f5bde8cdc..191f96a3848 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java @@ -1,13 +1,23 @@ package datadog.trace.instrumentation.java.lang; +import datadog.trace.api.Config; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.api.java.lang.ProcessImplInstrumentationHelpers; +import java.util.Map; import net.bytebuddy.asm.Advice; class ProcessImplStartAdvice { + @SuppressWarnings("unchecked") @Advice.OnMethodEnter(suppress = Throwable.class) - public static AgentSpan beforeStart(@Advice.Argument(0) final String[] command) { + public static AgentSpan beforeStart( + @Advice.Argument(0) final String[] command, + @Advice.Argument(value = 1, readOnly = false) Map environment) { + String rootSessionId = Config.get().getRootSessionId(); + if (rootSessionId != null && environment != null) { + environment.put("_DD_ROOT_JAVA_SESSION_ID", rootSessionId); + } + if (!ProcessImplInstrumentationHelpers.ONLINE) { return null; } diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy deleted file mode 100644 index c40247c6d8e..00000000000 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessBuilderSessionIdSpecification.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package datadog.trace.instrumentation.java.lang - -import datadog.trace.agent.test.AgentTestRunner -import datadog.trace.api.Config - -class ProcessBuilderSessionIdSpecification extends AgentTestRunner { - - def "ProcessBuilder injects root session ID into child environment"() { - setup: - def command = ['sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID'] - def pb = new ProcessBuilder(command) - - when: - def process = pb.start() - def output = process.inputStream.text.trim() - process.waitFor() - - then: - output == Config.get().getRootSessionId() - } - - def "child process inherits root session ID not runtime ID"() { - setup: - def command = ['sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID'] - def pb = new ProcessBuilder(command) - - when: - def process = pb.start() - def output = process.inputStream.text.trim() - process.waitFor() - - then: - output == Config.get().getRootSessionId() - Config.get().getRootSessionId() == Config.get().getRuntimeId() - } -} diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy index f2c33d9145d..ebf9aa8aaad 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy @@ -3,6 +3,7 @@ package datadog.trace.instrumentation.java.lang import datadog.trace.agent.test.InstrumentationSpecification import datadog.trace.agent.test.asserts.SpanAssert import datadog.trace.agent.test.utils.TraceUtils +import datadog.trace.api.Config import datadog.trace.bootstrap.ActiveSubsystems import java.util.concurrent.TimeUnit @@ -331,4 +332,17 @@ class ProcessImplInstrumentationSpecification extends InstrumentationSpecificati ['/does/not/exist/cmd', '--pass', 'abc', '--token=def'] | '["/does/not/exist/cmd","--pass","?","--token=?"]' ['/does/not/exist/md5', '-s', 'pony'] | '["/does/not/exist/md5","?","?"]' } + + void 'child process inherits root session ID'() { + when: + def command = ['/bin/sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID'] + def builder = new ProcessBuilder(command) + Process p = builder.start() + def output = p.inputStream.text.trim() + def terminated = p.waitFor(5, TimeUnit.SECONDS) + + then: + terminated + output == Config.get().getRootSessionId() + } } From 0f14d1c2a26bf2569a7357e7ac510b4043841a22 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Fri, 20 Mar 2026 12:40:45 -0400 Subject: [PATCH 3/6] Add DD-Root-Session-ID assertion to telemetry header tests Verify DD-Session-ID equals runtime ID and DD-Root-Session-ID is absent when rootSessionId == runtimeId (non-child process), or present with the correct value when they differ. Co-Authored-By: Claude Opus 4.6 --- .../datadog/telemetry/TestTelemetryRouter.groovy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy b/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy index dd0a4c60ded..2aecd2a18ee 100644 --- a/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy +++ b/telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy @@ -7,6 +7,7 @@ import datadog.telemetry.api.DistributionSeries import datadog.telemetry.api.LogMessage import datadog.telemetry.api.Metric import datadog.telemetry.api.RequestType +import datadog.trace.api.Config import datadog.trace.api.ConfigSetting import datadog.trace.api.telemetry.Endpoint import datadog.trace.api.telemetry.ProductChange @@ -97,6 +98,15 @@ class TestTelemetryRouter extends TelemetryRouter { assert entityId == null || entityId.startsWith("in-") || entityId.startsWith("cin-") def sessionId = this.request.header('DD-Session-ID') assert sessionId =~ /[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}/ + assert sessionId == Config.get().getRuntimeId() + // DD-Root-Session-ID should only be present when inherited from a parent process + // (i.e., when rootSessionId != runtimeId). In normal test context, they're equal. + def rootSessionId = this.request.header('DD-Root-Session-ID') + if (Config.get().getRootSessionId() == Config.get().getRuntimeId()) { + assert rootSessionId == null + } else { + assert rootSessionId == Config.get().getRootSessionId() + } return this } From d35c5043b20d2b68fa714668182e09798bb3980b Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Fri, 20 Mar 2026 13:17:03 -0400 Subject: [PATCH 4/6] Fix config-inversion lint and advice bytecode issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use ConfigHelper.env() instead of EnvironmentVariables.get() for _DD_ROOT_JAVA_SESSION_ID to satisfy config-inversion-linter. The underscore prefix bypasses supported-config validation (internal var). - Remove readOnly=false from @Advice.Argument(1) — we mutate the map via put(), not reassign the reference. readOnly=false generates unnecessary bytecode that breaks bootstrap class instrumentation. Co-Authored-By: Claude Opus 4.6 --- .../instrumentation/java/lang/ProcessImplStartAdvice.java | 3 +-- internal-api/src/main/java/datadog/trace/api/Config.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java index 191f96a3848..1914967ee66 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/main/java/datadog/trace/instrumentation/java/lang/ProcessImplStartAdvice.java @@ -8,11 +8,10 @@ import net.bytebuddy.asm.Advice; class ProcessImplStartAdvice { - @SuppressWarnings("unchecked") @Advice.OnMethodEnter(suppress = Throwable.class) public static AgentSpan beforeStart( @Advice.Argument(0) final String[] command, - @Advice.Argument(value = 1, readOnly = false) Map environment) { + @Advice.Argument(1) final Map environment) { String rootSessionId = Config.get().getRootSessionId(); if (rootSessionId != null && environment != null) { environment.put("_DD_ROOT_JAVA_SESSION_ID", rootSessionId); diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 39151620a14..c7b65dc2a35 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -687,7 +687,6 @@ import static datadog.trace.util.ConfigStrings.propertyNameToEnvironmentVariableName; import static datadog.trace.util.json.JsonPathParser.parseJsonPaths; -import datadog.environment.EnvironmentVariables; import datadog.environment.JavaVirtualMachine; import datadog.environment.OperatingSystem; import datadog.environment.SystemProperties; @@ -796,7 +795,7 @@ static class RuntimeIdHolder { static final String rootSessionId = initRootSessionId(); private static String initRootSessionId() { - String inherited = EnvironmentVariables.get("_DD_ROOT_JAVA_SESSION_ID"); + String inherited = ConfigHelper.env("_DD_ROOT_JAVA_SESSION_ID"); return inherited != null ? inherited : runtimeId; } } From 7b9d5d4b5b1089b70f9c649eb0c5e42ecc482c1a Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Fri, 20 Mar 2026 14:15:25 -0400 Subject: [PATCH 5/6] Address PR feedback: include rootSessionId in logs, respect runtimeIdEnabled, fix test - Add rootSessionId to Config.toString() for tracer log visibility - Gate getRootSessionId() behind runtimeIdEnabled like getRuntimeId() - Force environment map initialization in test to fix test_inst failures Co-Authored-By: Claude Opus 4.6 --- .../java/lang/ProcessImplInstrumentationSpecification.groovy | 1 + internal-api/src/main/java/datadog/trace/api/Config.java | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy index ebf9aa8aaad..2c899256a79 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy @@ -337,6 +337,7 @@ class ProcessImplInstrumentationSpecification extends InstrumentationSpecificati when: def command = ['/bin/sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID'] def builder = new ProcessBuilder(command) + builder.environment() // force lazy environment map initialization so advice can inject Process p = builder.start() def output = p.inputStream.text.trim() def terminated = p.waitFor(5, TimeUnit.SECONDS) diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index c7b65dc2a35..fcaac7a9b55 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3130,7 +3130,7 @@ public String getRuntimeId() { } public String getRootSessionId() { - return RuntimeIdHolder.rootSessionId; + return runtimeIdEnabled ? RuntimeIdHolder.rootSessionId : ""; } public Long getProcessId() { @@ -5873,6 +5873,9 @@ public String toString() { + ", runtimeId='" + getRuntimeId() + '\'' + + ", rootSessionId='" + + getRootSessionId() + + '\'' + ", runtimeVersion='" + runtimeVersion + ", apiKey=" From 1f53945a22a1168044ec5f3748cf5b933a0261f7 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Fri, 20 Mar 2026 14:19:19 -0400 Subject: [PATCH 6/6] Remove unnecessary comment Co-Authored-By: Claude Opus 4.6 --- .../java/lang/ProcessImplInstrumentationSpecification.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy index 2c899256a79..b54492e2e6f 100644 --- a/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation/java/java-lang/java-lang-1.8/src/test/groovy/datadog/trace/instrumentation/java/lang/ProcessImplInstrumentationSpecification.groovy @@ -337,7 +337,7 @@ class ProcessImplInstrumentationSpecification extends InstrumentationSpecificati when: def command = ['/bin/sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID'] def builder = new ProcessBuilder(command) - builder.environment() // force lazy environment map initialization so advice can inject + builder.environment() Process p = builder.start() def output = p.inputStream.text.trim() def terminated = p.waitFor(5, TimeUnit.SECONDS)