Skip to content

Commit c041595

Browse files
khanayan123claude
andcommitted
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 <noreply@anthropic.com>
1 parent 828c9cc commit c041595

File tree

6 files changed

+111
-1
lines changed

6 files changed

+111
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import datadog.trace.api.Config;
4+
import net.bytebuddy.asm.Advice;
5+
6+
class ProcessBuilderSessionIdAdvice {
7+
@Advice.OnMethodEnter(suppress = Throwable.class)
8+
public static void beforeStart(@Advice.This final ProcessBuilder self) {
9+
Config config = Config.get();
10+
String rootSessionId = config.getRootSessionId();
11+
if (rootSessionId != null) {
12+
self.environment().put("_DD_ROOT_JAVA_SESSION_ID", rootSessionId);
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;
5+
6+
import com.google.auto.service.AutoService;
7+
import datadog.trace.agent.tooling.Instrumenter;
8+
import datadog.trace.agent.tooling.InstrumenterModule;
9+
import datadog.trace.api.Platform;
10+
11+
@AutoService(InstrumenterModule.class)
12+
public class ProcessBuilderSessionIdInstrumentation extends InstrumenterModule.Tracing
13+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
14+
15+
public ProcessBuilderSessionIdInstrumentation() {
16+
super("process-session-id");
17+
}
18+
19+
@Override
20+
protected boolean defaultEnabled() {
21+
return super.defaultEnabled() && !Platform.isNativeImageBuilder();
22+
}
23+
24+
@Override
25+
public String instrumentedType() {
26+
return "java.lang.ProcessBuilder";
27+
}
28+
29+
@Override
30+
public void methodAdvice(MethodTransformer transformer) {
31+
transformer.applyAdvice(
32+
named("start").and(takesNoArguments()),
33+
packageName + ".ProcessBuilderSessionIdAdvice");
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package datadog.trace.instrumentation.java.lang
2+
3+
import datadog.trace.agent.test.AgentTestRunner
4+
import datadog.trace.api.Config
5+
6+
class ProcessBuilderSessionIdSpecification extends AgentTestRunner {
7+
8+
def "ProcessBuilder injects root session ID into child environment"() {
9+
setup:
10+
def command = ['sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID']
11+
def pb = new ProcessBuilder(command)
12+
13+
when:
14+
def process = pb.start()
15+
def output = process.inputStream.text.trim()
16+
process.waitFor()
17+
18+
then:
19+
output == Config.get().getRootSessionId()
20+
}
21+
22+
def "child process inherits root session ID not runtime ID"() {
23+
setup:
24+
def command = ['sh', '-c', 'echo $_DD_ROOT_JAVA_SESSION_ID']
25+
def pb = new ProcessBuilder(command)
26+
27+
when:
28+
def process = pb.start()
29+
def output = process.inputStream.text.trim()
30+
process.waitFor()
31+
32+
then:
33+
output == Config.get().getRootSessionId()
34+
Config.get().getRootSessionId() == Config.get().getRuntimeId()
35+
}
36+
}

internal-api/src/main/java/datadog/trace/api/Config.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@
687687
import static datadog.trace.util.ConfigStrings.propertyNameToEnvironmentVariableName;
688688
import static datadog.trace.util.json.JsonPathParser.parseJsonPaths;
689689

690+
import datadog.environment.EnvironmentVariables;
690691
import datadog.environment.JavaVirtualMachine;
691692
import datadog.environment.OperatingSystem;
692693
import datadog.environment.SystemProperties;
@@ -792,6 +793,12 @@ public class Config {
792793
*/
793794
static class RuntimeIdHolder {
794795
static final String runtimeId = RandomUtils.randomUUID().toString();
796+
static final String rootSessionId = initRootSessionId();
797+
798+
private static String initRootSessionId() {
799+
String inherited = EnvironmentVariables.get("_DD_ROOT_JAVA_SESSION_ID");
800+
return inherited != null ? inherited : runtimeId;
801+
}
795802
}
796803

797804
static class HostNameHolder {
@@ -3123,6 +3130,10 @@ public String getRuntimeId() {
31233130
return runtimeIdEnabled ? RuntimeIdHolder.runtimeId : "";
31243131
}
31253132

3133+
public String getRootSessionId() {
3134+
return RuntimeIdHolder.rootSessionId;
3135+
}
3136+
31263137
public Long getProcessId() {
31273138
return PidHelper.getPidAsLong();
31283139
}

telemetry/src/main/java/datadog/telemetry/TelemetryRequest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ public Request.Builder httpRequest() {
8080
builder.addHeader("DD-Telemetry-Debug-Enabled", "true");
8181
}
8282

83+
Config config = Config.get();
84+
String sessionId = config.getRuntimeId();
85+
if (sessionId != null && !sessionId.isEmpty()) {
86+
builder.addHeader("DD-Session-ID", sessionId);
87+
}
88+
String rootSessionId = config.getRootSessionId();
89+
if (rootSessionId != null && !rootSessionId.equals(sessionId)) {
90+
builder.addHeader("DD-Root-Session-ID", rootSessionId);
91+
}
92+
8393
return builder;
8494
}
8595

telemetry/src/test/groovy/datadog/telemetry/TestTelemetryRouter.groovy

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ class TestTelemetryRouter extends TelemetryRouter {
8484
'DD-Client-Library-Language',
8585
'DD-Client-Library-Version',
8686
'DD-Telemetry-API-Version',
87-
'DD-Telemetry-Request-Type'
87+
'DD-Telemetry-Request-Type',
88+
'DD-Session-ID'
8889
])
8990
assert this.request.header('Content-Type') == 'application/json; charset=utf-8'
9091
assert this.request.header('Content-Length').toInteger() > 0
@@ -94,6 +95,8 @@ class TestTelemetryRouter extends TelemetryRouter {
9495
assert this.request.header('DD-Telemetry-Request-Type') == requestType.toString()
9596
def entityId = this.request.header('Datadog-Entity-ID')
9697
assert entityId == null || entityId.startsWith("in-") || entityId.startsWith("cin-")
98+
def sessionId = this.request.header('DD-Session-ID')
99+
assert sessionId =~ /[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}/
97100
return this
98101
}
99102

0 commit comments

Comments
 (0)