diff --git a/BUILD.bazel b/BUILD.bazel index 5c5b72a5..df17ac56 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -45,6 +45,7 @@ cc_library( "src/datadog/propagation_style.cpp", "src/datadog/random.cpp", "src/datadog/random.h", + "src/datadog/root_session_id.h", "src/datadog/rate.cpp", "src/datadog/remote_config/product.cpp", "src/datadog/remote_config/remote_config.cpp", diff --git a/include/datadog/environment.h b/include/datadog/environment.h index a8afb162..824e4a3a 100644 --- a/include/datadog/environment.h +++ b/include/datadog/environment.h @@ -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`) diff --git a/include/datadog/tracer_config.h b/include/datadog/tracer_config.h index 2ee4cd02..170c4980 100644 --- a/include/datadog/tracer_config.h +++ b/include/datadog/tracer_config.h @@ -189,6 +189,11 @@ struct TracerConfig { // This option is ignored if `resource_renaming_enabled` is not `true`. Optional resource_renaming_always_simplified_endpoint; + // Root session ID for stable telemetry correlation across forked workers. + // Integrations (nginx, httpd, kong) should set this in the master process + // before workers fork so all Tracers share the same root. + Optional 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 @@ -225,6 +230,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..3d496dc1 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,10 +41,19 @@ struct TracerSignature { 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) {} diff --git a/src/datadog/root_session_id.h b/src/datadog/root_session_id.h new file mode 100644 index 00000000..14dc48d4 --- /dev/null +++ b/src/datadog/root_session_id.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace datadog::tracing::root_session_id { + +inline const std::string& get_or_init(const std::string& runtime_id) { + static const std::string id = runtime_id; + return id; +} + +} // namespace datadog::tracing::root_session_id diff --git a/src/datadog/telemetry/telemetry_impl.cpp b/src/datadog/telemetry/telemetry_impl.cpp index 4617fafd..09ec61a3 100644 --- a/src/datadog/telemetry/telemetry_impl.cpp +++ b/src/datadog/telemetry/telemetry_impl.cpp @@ -60,6 +60,15 @@ HTTPClient::URL make_telemetry_endpoint(HTTPClient::URL url) { return url; } +void set_session_headers(DictWriter& headers, + const tracing::TracerSignature& signature) { + const auto& session_id = signature.runtime_id.string(); + headers.set("DD-Session-ID", session_id); + if (signature.root_session_id != session_id) { + headers.set("DD-Root-Session-ID", signature.root_session_id); + } +} + void cancel_tasks(std::vector& tasks) { for (auto& cancel_task : tasks) { cancel_task(); @@ -309,13 +318,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, + &signature = 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, signature); if (debug_enabled) { headers.set("DD-Telemetry-Debug-Enabled", "true"); } @@ -363,14 +374,16 @@ 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, + &signature = + 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, signature); if (debug_enabled) { headers.set("DD-Telemetry-Debug-Enabled", "true"); } diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 7603b92b..aae9037b 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -24,6 +24,7 @@ #include "msgpack.h" #include "platform_util.h" #include "random.h" +#include "root_session_id.h" #include "span_data.h" #include "span_sampler.h" #include "tags.h" @@ -48,8 +49,10 @@ 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_, + root_session_id::get_or_init( + 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_( diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index a10b17a8..1383ce47 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -403,10 +403,8 @@ Expected finalize_config(const TracerConfig &user_config, final_config.injection_styles.erase(it); } - if (user_config.runtime_id) { - final_config.runtime_id = user_config.runtime_id; - } - + final_config.runtime_id = user_config.runtime_id; + final_config.root_session_id = user_config.root_session_id; final_config.process_tags = user_config.process_tags; auto agent_finalized = diff --git a/test/mocks/http_clients.h b/test/mocks/http_clients.h index e369b299..a742bb56 100644 --- a/test/mocks/http_clients.h +++ b/test/mocks/http_clients.h @@ -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 post( const URL&, HeadersSetter set_headers, std::string body, diff --git a/test/telemetry/test_telemetry.cpp b/test/telemetry/test_telemetry.cpp index e05c867c..80db9e97 100644 --- a/test/telemetry/test_telemetry.cpp +++ b/test/telemetry/test_telemetry.cpp @@ -366,6 +366,67 @@ 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 session_rid = RuntimeID::generate(); + const TracerSignature tracer_signature(session_rid, "testsvc", "test"); + + Telemetry telemetry{ + *finalize_config(), tracer_signature, 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 tracer_signature(session_rid, root_rid.string(), + "testsvc", "test"); + + Telemetry telemetry{ + *finalize_config(), tracer_signature, 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 tracer_signature(session_rid, root_rid.string(), + "testsvc", "test"); + + Telemetry telemetry{ + *finalize_config(), tracer_signature, 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;