Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
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 {
@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(1) final Map<String, String> environment) {
String rootSessionId = Config.get().getRootSessionId();
if (rootSessionId != null && environment != null) {
environment.put("_DD_ROOT_JAVA_SESSION_ID", rootSessionId);
}

if (!ProcessImplInstrumentationHelpers.ONLINE) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -331,4 +332,18 @@ 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)
builder.environment()
Process p = builder.start()
def output = p.inputStream.text.trim()
def terminated = p.waitFor(5, TimeUnit.SECONDS)

then:
terminated
output == Config.get().getRootSessionId()
}
}
13 changes: 13 additions & 0 deletions internal-api/src/main/java/datadog/trace/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,12 @@ public class Config {
*/
static class RuntimeIdHolder {
static final String runtimeId = RandomUtils.randomUUID().toString();
static final String rootSessionId = initRootSessionId();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is this a value that we should include in tracer logs? We already include runtimeId in the logs 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to? backend will start correlating telemetry by root session id but maybe we can optionally include it in logs if they are runtime id does note equal root it

Copy link
Copy Markdown
Contributor

@mhlidd mhlidd Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicity's sake, I'm in favor of just including the raw value in logs unless you have objections

(ref)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works


private static String initRootSessionId() {
String inherited = ConfigHelper.env("_DD_ROOT_JAVA_SESSION_ID");
return inherited != null ? inherited : runtimeId;
}
}

static class HostNameHolder {
Expand Down Expand Up @@ -3123,6 +3129,10 @@ public String getRuntimeId() {
return runtimeIdEnabled ? RuntimeIdHolder.runtimeId : "";
}

public String getRootSessionId() {
return runtimeIdEnabled ? RuntimeIdHolder.rootSessionId : "";
}

public Long getProcessId() {
return PidHelper.getPidAsLong();
}
Expand Down Expand Up @@ -5863,6 +5873,9 @@ public String toString() {
+ ", runtimeId='"
+ getRuntimeId()
+ '\''
+ ", rootSessionId='"
+ getRootSessionId()
+ '\''
+ ", runtimeVersion='"
+ runtimeVersion
+ ", apiKey="
Expand Down
10 changes: 10 additions & 0 deletions telemetry/src/main/java/datadog/telemetry/TelemetryRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -84,7 +85,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
Expand All @@ -94,6 +96,17 @@ 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}/
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
}

Expand Down
Loading