diff --git a/include/datadog/environment.h b/include/datadog/environment.h index f2846b37..478d6ac3 100644 --- a/include/datadog/environment.h +++ b/include/datadog/environment.h @@ -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, nullptr) #define ENV_DEFAULT_RESOLVED_IN_CODE(X) X #define WITH_COMMA(ARG, TYPE, DEFAULT_VALUE) ARG, @@ -95,7 +96,7 @@ enum Variable { DD_LIST_ENVIRONMENT_VARIABLES(WITH_COMMA) }; #define QUOTED_WITH_COMMA(ARG, TYPE, DEFAULT_VALUE) \ WITH_COMMA(QUOTED(ARG), TYPE, DEFAULT_VALUE) -inline const char *const variable_names[] = { +inline const char* const variable_names[] = { DD_LIST_ENVIRONMENT_VARIABLES(QUOTED_WITH_COMMA)}; #undef QUOTED @@ -110,6 +111,10 @@ StringView name(Variable variable); // `nullopt` if that variable is not set in the environment. Optional 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 diff --git a/include/datadog/tracer_config.h b/include/datadog/tracer_config.h index 2ee4cd02..f342a61b 100644 --- a/include/datadog/tracer_config.h +++ b/include/datadog/tracer_config.h @@ -225,6 +225,7 @@ class FinalizedTracerConfig final { bool log_on_startup; bool generate_128bit_trace_ids; Optional runtime_id; + Optional root_session_id; Clock clock; std::string integration_name; std::string integration_version; diff --git a/include/datadog/tracer_signature.h b/include/datadog/tracer_signature.h index 042b4d37..73decae7 100644 --- a/include/datadog/tracer_signature.h +++ b/include/datadog/tracer_signature.h @@ -33,6 +33,7 @@ namespace tracing { struct TracerSignature { RuntimeID runtime_id; + std::string root_session_id; std::string default_service; std::string default_environment; std::string library_version; @@ -40,8 +41,10 @@ struct TracerSignature { 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), diff --git a/src/datadog/environment.cpp b/src/datadog/environment.cpp index 4dc3eb3f..4ad7b547 100644 --- a/src/datadog/environment.cpp +++ b/src/datadog/environment.cpp @@ -11,19 +11,28 @@ namespace environment { StringView name(Variable variable) { return variable_names[variable]; } Optional 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(Variable variable, StringView value) { + const char* name = variable_names[variable]; +#ifdef _WIN32 + _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; } } diff --git a/src/datadog/telemetry/telemetry.cpp b/src/datadog/telemetry/telemetry.cpp index 3cd51545..0cac32ce 100644 --- a/src/datadog/telemetry/telemetry.cpp +++ b/src/datadog/telemetry/telemetry.cpp @@ -53,10 +53,13 @@ void init(FinalizedConfiguration configuration, std::shared_ptr client, std::shared_ptr 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, diff --git a/src/datadog/telemetry/telemetry_impl.cpp b/src/datadog/telemetry/telemetry_impl.cpp index d9464dd7..9072c23f 100644 --- a/src/datadog/telemetry/telemetry_impl.cpp +++ b/src/datadog/telemetry/telemetry_impl.cpp @@ -302,13 +302,18 @@ 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"); + 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); + } if (debug_enabled) { headers.set("DD-Telemetry-Debug-Enabled", "true"); } @@ -356,14 +361,18 @@ 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); + 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); + } if (debug_enabled) { headers.set("DD-Telemetry-Debug-Enabled", "true"); } diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 7603b92b..2437db43 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -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(config)), collector_(/* see constructor body */), span_sampler_( @@ -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, + signature_.root_session_id); + telemetry::init(config.telemetry, signature_, logger_, config.http_client, config.event_scheduler, config.agent_url); if (config.report_hostname) { diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index a10b17a8..adf8cf79 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -407,6 +407,10 @@ Expected finalize_config(const TracerConfig &user_config, final_config.runtime_id = user_config.runtime_id; } + if (auto val = lookup(environment::_DD_ROOT_CPP_SESSION_ID)) { + final_config.root_session_id = std::string{*val}; + } + final_config.process_tags = user_config.process_tags; auto agent_finalized = diff --git a/supported-configurations.json b/supported-configurations.json index 4ee3558a..85aa9f79 100644 --- a/supported-configurations.json +++ b/supported-configurations.json @@ -279,6 +279,13 @@ "implementation": "A", "type": "STRING" } + ], + "_DD_ROOT_CPP_SESSION_ID": [ + { + "default": null, + "implementation": "A", + "type": "STRING" + } ] }, "version": "2" diff --git a/test/remote_config/test_remote_config.cpp b/test/remote_config/test_remote_config.cpp index 232de5af..9a22bd48 100644 --- a/test/remote_config/test_remote_config.cpp +++ b/test/remote_config/test_remote_config.cpp @@ -47,8 +47,10 @@ auto logger = std::make_shared(); 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"}; @@ -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"}; diff --git a/test/telemetry/test_telemetry.cpp b/test/telemetry/test_telemetry.cpp index 83590773..35731e63 100644 --- a/test/telemetry/test_telemetry.cpp +++ b/test/telemetry/test_telemetry.cpp @@ -89,8 +89,10 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry lifecycle") { auto client = std::make_shared(); auto scheduler = std::make_shared(); + 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"}; @@ -353,6 +355,65 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry lifecycle") { } } +TELEMETRY_IMPLEMENTATION_TEST("session ID headers") { + auto logger = std::make_shared(); + auto client = std::make_shared(); + auto scheduler = std::make_shared(); + 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->request_headers.items.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; @@ -364,8 +425,10 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry API") { auto client = std::make_shared(); auto scheduler = std::make_shared(); + 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"}; @@ -869,8 +932,10 @@ TELEMETRY_IMPLEMENTATION_TEST("Tracer telemetry configuration") { auto client = std::make_shared(); auto scheduler = std::make_shared(); + 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"}; diff --git a/test/test_datadog_agent.cpp b/test/test_datadog_agent.cpp index 92ee9a06..ac7c4a3e 100644 --- a/test/test_datadog_agent.cpp +++ b/test/test_datadog_agent.cpp @@ -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(*finalized); diff --git a/tools/config-inversion/main.cpp b/tools/config-inversion/main.cpp index 1bef85dd..c162651b 100644 --- a/tools/config-inversion/main.cpp +++ b/tools/config-inversion/main.cpp @@ -11,12 +11,17 @@ namespace fs = std::filesystem; namespace env = datadog::tracing::environment; template -std::string to_string_any(const T& value) { +nlohmann::json to_json_default(const T& value) { std::ostringstream oss; oss << value; return oss.str(); } +template <> +nlohmann::json to_json_default(std::nullptr_t const&) { + return nullptr; +} + nlohmann::json build_configuration() { nlohmann::json j; j["version"] = "2"; @@ -31,7 +36,7 @@ nlohmann::json build_configuration() { #define X(NAME, TYPE, DEFAULT_VALUE) \ do { \ auto obj = nlohmann::json::object(); \ - obj["default"] = to_string_any(DEFAULT_VALUE); \ + obj["default"] = to_json_default(DEFAULT_VALUE); \ obj["implementation"] = "A"; \ obj["type"] = QUOTED(TYPE); \ supported_configurations[QUOTED(NAME)] = nlohmann::json::array({obj}); \