Skip to content
Open
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
7 changes: 6 additions & 1 deletion include/datadog/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,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 @@ -110,6 +111,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(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
5 changes: 4 additions & 1 deletion include/datadog/tracer_signature.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@ 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)
TracerSignature(RuntimeID id, std::string root_session, std::string service,
std::string environment)
: runtime_id(id),
root_session_id(std::move(root_session)),
default_service(std::move(service)),
default_environment(std::move(environment)),
library_version(tracer_version),
Expand Down
11 changes: 11 additions & 0 deletions src/datadog/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ Optional<StringView> lookup(Variable variable) {
return StringView{value};
}

void set(Variable variable, StringView value) {
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.

@xlamorlette-datadog this feature requires that we set an environment variable, so that child processes will receive it. However, this means now our tracer is both reading (std::getenv) and writing (setenv) environment variables. Will we need to add a lock to both operations to ensure only one thread is getting/setting an environment variable?

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({});

Expand Down
11 changes: 7 additions & 4 deletions src/datadog/telemetry/telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@ void init(FinalizedConfiguration configuration,
std::shared_ptr<tracing::HTTPClient> client,
std::shared_ptr<tracing::EventScheduler> event_scheduler,
tracing::HTTPClient::URL agent_url, tracing::Clock clock) {
instance(Ctor_param{configuration,
tracing::TracerSignature(tracing::RuntimeID::generate(),
tracing::get_process_name(), ""),
logger, client, event_scheduler, agent_url, clock});
auto runtime_id = tracing::RuntimeID::generate();
auto root_session_id = runtime_id.string();
instance(Ctor_param{
configuration,
tracing::TracerSignature(runtime_id, std::move(root_session_id),
tracing::get_process_name(), ""),
logger, client, event_scheduler, agent_url, clock});
}

void init(FinalizedConfiguration configuration,
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());
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 @@ -302,13 +310,15 @@ void Telemetry::app_started() {
auto payload = app_started_payload();

auto on_headers = [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", "app-started");
set_session_headers(headers, sig);
if (debug_enabled) {
headers.set("DD-Telemetry-Debug-Enabled", "true");
}
Expand Down Expand Up @@ -356,14 +366,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(environment::_DD_ROOT_CPP_SESSION_ID,
Copy link
Copy Markdown
Contributor

@zacharycmontoya zacharycmontoya Mar 31, 2026

Choose a reason for hiding this comment

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

FYI there will be an edge case here. Since the C++ tracer doesn't have a singleton tracer instance, we may be creating one tracer instance per worker thread (e.g. web server) and if we do not assign a runtime ID at tracer construction time then each worker thread will get a unique runtime ID and (race condition) a unique root session ID. Other times we may get the same root session ID since the other thread could read the environment variable set by this line of code

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call, the race exists if multiple Tracers are constructed concurrently before any of them sets the env var.

Would you prefer we fix this with a std::call_once approach? Something like a get_or_init_root_session_id(fallback) function that atomically reads-or-sets the env var on first call, so all Tracers in the process get the same root session ID. That would also let us remove the config plumbing since the Tracer would resolve it directly.

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 @@ -279,6 +279,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
8 changes: 6 additions & 2 deletions test/remote_config/test_remote_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ auto logger = std::make_shared<NullLogger>();

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

Expand Down Expand Up @@ -92,8 +94,10 @@ REMOTE_CONFIG_TEST("initial state payload") {
// TODO: test all combination of product and capabilities generation

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

Expand Down
71 changes: 68 additions & 3 deletions test/telemetry/test_telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry lifecycle") {
auto client = std::make_shared<MockHTTPClient>();
auto scheduler = std::make_shared<FakeEventScheduler>();

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

Expand Down Expand Up @@ -353,6 +355,65 @@ 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 rid = RuntimeID::generate();
const TracerSignature sig{rid, rid.string(), "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 == 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 rid = RuntimeID::generate();
auto root_rid = RuntimeID::generate();
const TracerSignature sig{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 == 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 rid = RuntimeID::generate();
auto root_rid = RuntimeID::generate();
const TracerSignature sig{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 == 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 @@ -364,8 +425,10 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry API") {
auto client = std::make_shared<MockHTTPClient>();
auto scheduler = std::make_shared<FakeEventScheduler>();

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

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

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

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, rid.string(), "testsvc", "test");

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