Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e40dd5d
Implement stable session identifier headers for telemetry
khanayan123 Mar 25, 2026
3fab2f5
Resync supported-configurations.json via config-inversion tool
khanayan123 Mar 25, 2026
d805351
Fix config-inversion: output null default for _DD_ROOT_CPP_SESSION_ID
khanayan123 Mar 25, 2026
1f8a254
Match registry: _DD_ROOT_CPP_SESSION_ID implementation B, default ""
khanayan123 Mar 25, 2026
468dd13
Revert to implementation A with null default for _DD_ROOT_CPP_SESSION_ID
khanayan123 Mar 25, 2026
f124ca3
Merge branch 'main' into ayan.khan/stable-session-id-headers
khanayan123 Mar 26, 2026
c106643
Update include/datadog/environment.h
khanayan123 Mar 31, 2026
25fb7ea
fix: address PR review — use empty string default, move env lookup
khanayan123 Mar 31, 2026
d323be4
fix: revert config-inversion changes, no longer needed with "" default
khanayan123 Mar 31, 2026
3d9252b
fix: revert unrelated formatting, deduplicate session headers, fix Wi…
khanayan123 Mar 31, 2026
b3c0f73
Update test/telemetry/test_telemetry.cpp
khanayan123 Mar 31, 2026
b673a7e
fix: include request_headers in MockHTTPClient::clear()
khanayan123 Mar 31, 2026
0b7fd49
fix: address review feedback from xlamorlette
khanayan123 Apr 2, 2026
64223a6
fix: move config registry link to top of file, remove inline link
khanayan123 Apr 2, 2026
2453d16
chore: remove inline comment on _DD_ROOT_CPP_SESSION_ID
khanayan123 Apr 2, 2026
d3c3b39
refactor: simplify telemetry init() using 3-param TracerSignature con…
khanayan123 Apr 2, 2026
a571544
Merge remote-tracking branch 'origin/main' into ayan.khan/stable-sess…
khanayan123 Apr 2, 2026
d47c926
style: fix clang-format violation in telemetry.cpp
khanayan123 Apr 2, 2026
b8a48ff
fix: address review feedback from xlamorlette
khanayan123 Apr 2, 2026
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
9 changes: 8 additions & 1 deletion include/datadog/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ namespace environment {

// Central registry for supported environment variables.
// All configurations must be registered here.
// See also:
// https://feature-parity.us1.prod.dog/configurations?viewType=configurations
//
// This registry is the single source of truth for:
// - env variable name allowlist (`include/datadog/environment.h`)
Expand Down Expand Up @@ -82,7 +84,8 @@ namespace environment {
MACRO(DD_APM_TRACING_ENABLED, BOOLEAN, true) \
MACRO(DD_TRACE_RESOURCE_RENAMING_ENABLED, BOOLEAN, false) \
MACRO(DD_TRACE_RESOURCE_RENAMING_ALWAYS_SIMPLIFIED_ENDPOINT, BOOLEAN, false) \
MACRO(DD_EXTERNAL_ENV, STRING, "")
MACRO(DD_EXTERNAL_ENV, STRING, "") \
MACRO(_DD_ROOT_CPP_SESSION_ID, STRING, "")

#define ENV_DEFAULT_RESOLVED_IN_CODE(X) X
#define WITH_COMMA(ARG, TYPE, DEFAULT_VALUE) ARG,
Expand Down Expand Up @@ -111,6 +114,10 @@ StringView name(Variable variable);
// `nullopt` if that variable is not set in the environment.
Optional<StringView> lookup(Variable variable);

// Set the specified environment `variable` to `value`. Does not overwrite if
// already set (equivalent to setenv(..., 0) on POSIX).
void set_if_not_set(Variable variable, StringView value);

std::string to_json();

} // namespace environment
Expand Down
6 changes: 6 additions & 0 deletions include/datadog/tracer_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ struct TracerConfig {
// This option is ignored if `resource_renaming_enabled` is not `true`.
Optional<bool> resource_renaming_always_simplified_endpoint;

// Root session ID inherited from the parent process via
// `_DD_ROOT_CPP_SESSION_ID`. Tracks the runtime ID of the first C++ process
// in a fork/spawn chain.
Optional<std::string> root_session_id;

/// A mapping of process-specific tags used to uniquely identify processes.
///
/// The `process_tags` map allows associating arbitrary string-based keys and
Expand Down Expand Up @@ -225,6 +230,7 @@ class FinalizedTracerConfig final {
bool log_on_startup;
bool generate_128bit_trace_ids;
Optional<RuntimeID> runtime_id;
Optional<std::string> root_session_id;
Clock clock;
std::string integration_name;
std::string integration_version;
Expand Down
16 changes: 12 additions & 4 deletions include/datadog/tracer_signature.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,25 @@ namespace tracing {

struct TracerSignature {
RuntimeID runtime_id;
std::string root_session_id;
std::string default_service;
std::string default_environment;
std::string library_version;
StringView library_language;
StringView library_language_version;

TracerSignature() = delete;
TracerSignature(RuntimeID id, std::string service, std::string environment)
: runtime_id(id),
default_service(std::move(service)),
default_environment(std::move(environment)),
TracerSignature(RuntimeID runtime_id, std::string default_service,
std::string default_environment)
: TracerSignature(runtime_id, runtime_id.string(),
std::move(default_service),
std::move(default_environment)) {}
TracerSignature(RuntimeID runtime_id, std::string root_session_id,
std::string default_service, std::string default_environment)
: runtime_id(runtime_id),
root_session_id(std::move(root_session_id)),
default_service(std::move(default_service)),
default_environment(std::move(default_environment)),
library_version(tracer_version),
library_language("cpp"),
library_language_version(DD_TRACE_STRINGIFY(__cplusplus), 6) {}
Expand Down
19 changes: 15 additions & 4 deletions src/datadog/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,30 @@ namespace environment {
StringView name(Variable variable) { return variable_names[variable]; }

Optional<StringView> lookup(Variable variable) {
const char *name = variable_names[variable];
const char *value = std::getenv(name);
const char* name = variable_names[variable];
const char* value = std::getenv(name);
if (!value) {
return nullopt;
}
return StringView{value};
}

void set_if_not_set(Variable variable, StringView value) {
const char* name = variable_names[variable];
#ifdef _WIN32
if (!std::getenv(name)) {
_putenv_s(name, value.data());
}
#else
setenv(name, value.data(), /*overwrite=*/0);
#endif
}

std::string to_json() {
auto result = nlohmann::json::object({});

for (const char *name : variable_names) {
if (const char *value = std::getenv(name)) {
for (const char* name : variable_names) {
if (const char* value = std::getenv(name)) {
result[name] = value;
}
}
Expand Down
17 changes: 14 additions & 3 deletions src/datadog/telemetry/telemetry_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ HTTPClient::URL make_telemetry_endpoint(HTTPClient::URL url) {
return url;
}

void set_session_headers(DictWriter& headers,
const tracing::TracerSignature& sig) {
headers.set("DD-Session-ID", sig.runtime_id.string());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit, opt: store sig.runtime_id.string() in a variable to make intent clearer:

const std::string& session_id = sig.runtime_id.string();
headers.set("DD-Session-ID", session_id);
if (sig.root_session_id != session_id) {
  headers.set("DD-Root-Session-ID", sig.root_session_id);
}

if (sig.root_session_id != sig.runtime_id.string()) {
headers.set("DD-Root-Session-ID", sig.root_session_id);
}
}

void cancel_tasks(std::vector<tracing::EventScheduler::Cancel>& tasks) {
for (auto& cancel_task : tasks) {
cancel_task();
Expand Down Expand Up @@ -309,13 +317,15 @@ void Telemetry::app_started() {
auto payload = app_started_payload();

auto on_headers = [payload_size = payload.size(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The code lines 312-356 is very similar to the one lines 368-409.
The on_response handlers differ, and more counters are incremented in send_payload(), but I think that all what is in send_payload() is a more complete version of what is in app_started().
So I think that app_started() could be hugely simplified by simply calling send_payload() as follows:

void Telemetry::app_started() {
  send_payload("app-started", app_started_payload());
}

debug_enabled = config_.debug](DictWriter& headers) {
debug_enabled = config_.debug,
&sig = tracer_signature_](DictWriter& headers) {
headers.set("Content-Type", "application/json");
headers.set("Content-Length", std::to_string(payload_size));
headers.set("DD-Telemetry-API-Version", "v2");
headers.set("DD-Client-Library-Language", "cpp");
headers.set("DD-Client-Library-Version", tracer_version);
headers.set("DD-Telemetry-Request-Type", "app-started");
set_session_headers(headers, sig);
if (debug_enabled) {
headers.set("DD-Telemetry-Debug-Enabled", "true");
}
Expand Down Expand Up @@ -363,14 +373,15 @@ void Telemetry::app_closing() {

void Telemetry::send_payload(StringView request_type, std::string payload) {
auto set_telemetry_headers = [request_type, payload_size = payload.size(),
debug_enabled =
config_.debug](DictWriter& headers) {
debug_enabled = config_.debug,
&sig = tracer_signature_](DictWriter& headers) {
headers.set("Content-Type", "application/json");
headers.set("Content-Length", std::to_string(payload_size));
headers.set("DD-Telemetry-API-Version", "v2");
headers.set("DD-Client-Library-Language", "cpp");
headers.set("DD-Client-Library-Version", tracer_version);
headers.set("DD-Telemetry-Request-Type", request_type);
set_session_headers(headers, sig);
if (debug_enabled) {
headers.set("DD-Telemetry-Debug-Enabled", "true");
}
Expand Down
8 changes: 6 additions & 2 deletions src/datadog/tracer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ Tracer::Tracer(const FinalizedTracerConfig& config,
: logger_(config.logger),
runtime_id_(config.runtime_id ? *config.runtime_id
: RuntimeID::generate()),
signature_{runtime_id_, config.defaults.service,
config.defaults.environment},
signature_{runtime_id_,
config.root_session_id.value_or(runtime_id_.string()),
config.defaults.service, config.defaults.environment},
config_manager_(std::make_shared<ConfigManager>(config)),
collector_(/* see constructor body */),
span_sampler_(
Expand All @@ -64,6 +65,9 @@ Tracer::Tracer(const FinalizedTracerConfig& config,
baggage_extraction_enabled_(false),
tracing_enabled_(config.tracing_enabled),
resource_renaming_mode_(config.resource_renaming_mode) {
environment::set_if_not_set(environment::_DD_ROOT_CPP_SESSION_ID,
signature_.root_session_id);

telemetry::init(config.telemetry, signature_, logger_, config.http_client,
config.event_scheduler, config.agent_url);
if (config.report_hostname) {
Expand Down
8 changes: 8 additions & 0 deletions src/datadog/tracer_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ Expected<TracerConfig> load_tracer_env_config(Logger &logger) {
return std::move(error);
}

if (auto val = lookup(environment::_DD_ROOT_CPP_SESSION_ID)) {
env_cfg.root_session_id = std::string{*val};
}

return env_cfg;
}

Expand Down Expand Up @@ -407,6 +411,10 @@ Expected<FinalizedTracerConfig> finalize_config(const TracerConfig &user_config,
final_config.runtime_id = user_config.runtime_id;
}

if (env_config->root_session_id) {
final_config.root_session_id = env_config->root_session_id;
}

final_config.process_tags = user_config.process_tags;

auto agent_finalized =
Expand Down
7 changes: 7 additions & 0 deletions supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@
"implementation": "A",
"type": "STRING"
}
],
"_DD_ROOT_CPP_SESSION_ID": [
{
"default": "",
"implementation": "A",
"type": "STRING"
}
]
},
"version": "2"
Expand Down
5 changes: 4 additions & 1 deletion test/mocks/http_clients.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ struct MockHTTPClient : public HTTPClient {
ErrorHandler on_error_;
std::string request_body;

void clear() { request_body = ""; }
void clear() {
request_body = "";
request_headers.items.clear();
}

Expected<void> post(
const URL&, HeadersSetter set_headers, std::string body,
Expand Down
12 changes: 4 additions & 8 deletions test/remote_config/test_remote_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,8 @@ auto logger = std::make_shared<NullLogger>();

REMOTE_CONFIG_TEST("initial state payload") {
// Verify the initial payload structure for a remote configuration instance.
const TracerSignature tracer_signature{
/* runtime_id = */ RuntimeID::generate(),
/* service = */ "testsvc",
/* environment = */ "test"};
auto runtime_id = RuntimeID::generate();
const TracerSignature tracer_signature{runtime_id, "testsvc", "test"};

auto tracing_listener = std::make_shared<FakeListener>();
tracing_listener->products = rc::product::APM_TRACING;
Expand Down Expand Up @@ -92,10 +90,8 @@ REMOTE_CONFIG_TEST("initial state payload") {
// TODO: test all combination of product and capabilities generation

REMOTE_CONFIG_TEST("response processing") {
const TracerSignature tracer_signature{
/* runtime_id = */ RuntimeID::generate(),
/* service = */ "testsvc",
/* environment = */ "test"};
auto runtime_id = RuntimeID::generate();
const TracerSignature tracer_signature{runtime_id, "testsvc", "test"};

SECTION("ill formatted input",
"inputs not following the Remote Configuration JSON schema should "
Expand Down
79 changes: 67 additions & 12 deletions test/telemetry/test_telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,8 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry lifecycle") {
auto client = std::make_shared<MockHTTPClient>();
auto scheduler = std::make_shared<FakeEventScheduler>();

const TracerSignature tracer_signature{
/* runtime_id = */ RuntimeID::generate(),
/* service = */ "testsvc",
/* environment = */ "test"};
auto runtime_id = RuntimeID::generate();
const TracerSignature tracer_signature{runtime_id, "testsvc", "test"};

auto url = HTTPClient::URL::parse("http://localhost:8000");

Expand Down Expand Up @@ -366,6 +364,67 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry lifecycle") {
}
}

TELEMETRY_IMPLEMENTATION_TEST("session ID headers") {
auto logger = std::make_shared<MockLogger>();
auto client = std::make_shared<MockHTTPClient>();
auto scheduler = std::make_shared<FakeEventScheduler>();
auto url = HTTPClient::URL::parse("http://localhost:8000");

SECTION("root process: DD-Session-ID present, DD-Root-Session-ID absent") {
auto session_rid = RuntimeID::generate();
const TracerSignature sig{session_rid, "testsvc", "test"};

Telemetry telemetry{*finalize_config(), sig, logger, client,
scheduler, *url};

auto it = client->request_headers.items.find("DD-Session-ID");
REQUIRE(it != client->request_headers.items.end());
CHECK(it->second == session_rid.string());

CHECK(client->request_headers.items.find("DD-Root-Session-ID") ==
client->request_headers.items.end());
}

SECTION("child process: DD-Root-Session-ID present when different") {
auto session_rid = RuntimeID::generate();
auto root_rid = RuntimeID::generate();
const TracerSignature sig{session_rid, root_rid.string(), "testsvc",
"test"};

Telemetry telemetry{*finalize_config(), sig, logger, client,
scheduler, *url};

auto session_it = client->request_headers.items.find("DD-Session-ID");
REQUIRE(session_it != client->request_headers.items.end());
CHECK(session_it->second == session_rid.string());

auto root_it = client->request_headers.items.find("DD-Root-Session-ID");
REQUIRE(root_it != client->request_headers.items.end());
CHECK(root_it->second == root_rid.string());
}

SECTION("heartbeat includes session headers") {
auto session_rid = RuntimeID::generate();
auto root_rid = RuntimeID::generate();
const TracerSignature sig{session_rid, root_rid.string(), "testsvc",
"test"};

Telemetry telemetry{*finalize_config(), sig, logger, client,
scheduler, *url};

client->clear();
scheduler->trigger_heartbeat();

auto session_it = client->request_headers.items.find("DD-Session-ID");
REQUIRE(session_it != client->request_headers.items.end());
CHECK(session_it->second == session_rid.string());

auto root_it = client->request_headers.items.find("DD-Root-Session-ID");
REQUIRE(root_it != client->request_headers.items.end());
CHECK(root_it->second == root_rid.string());
}
}

TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry API") {
const Clock clock = [] {
TimePoint result;
Expand All @@ -377,10 +436,8 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry API") {
auto client = std::make_shared<MockHTTPClient>();
auto scheduler = std::make_shared<FakeEventScheduler>();

const TracerSignature tracer_signature{
/* runtime_id = */ RuntimeID::generate(),
/* service = */ "testsvc",
/* environment = */ "test"};
auto runtime_id = RuntimeID::generate();
const TracerSignature tracer_signature{runtime_id, "testsvc", "test"};

auto url = HTTPClient::URL::parse("http://localhost:8000");

Expand Down Expand Up @@ -981,10 +1038,8 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry configuration") {
auto client = std::make_shared<MockHTTPClient>();
auto scheduler = std::make_shared<FakeEventScheduler>();

const TracerSignature tracer_signature{
/* runtime_id = */ RuntimeID::generate(),
/* service = */ "testsvc",
/* environment = */ "test"};
auto runtime_id = RuntimeID::generate();
const TracerSignature tracer_signature{runtime_id, "testsvc", "test"};

auto url = HTTPClient::URL::parse("http://localhost:8000");

Expand Down
3 changes: 2 additions & 1 deletion test/test_datadog_agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ DATADOG_AGENT_TEST("Remote Configuration") {
auto finalized = finalize_config(config);
REQUIRE(finalized);

const TracerSignature signature(RuntimeID::generate(), "testsvc", "test");
auto rid = RuntimeID::generate();
const TracerSignature signature(rid, "testsvc", "test");

// TODO: set telemetry mock
auto config_manager = std::make_shared<ConfigManager>(*finalized);
Expand Down
Loading