@@ -198,6 +198,39 @@ nlohmann::json encode_distributions(
198198 return j;
199199}
200200
201+ nlohmann::json encode_configuration_field (const ConfigMetadata& config_metadata,
202+ std::size_t seq_id) {
203+ auto j = nlohmann::json{{" name" , to_string (config_metadata.name )},
204+ {" value" , config_metadata.value },
205+ {" seq_id" , seq_id}};
206+
207+ switch (config_metadata.origin ) {
208+ case ConfigMetadata::Origin::ENVIRONMENT_VARIABLE :
209+ j[" origin" ] = " env_var" ;
210+ break ;
211+ case ConfigMetadata::Origin::CODE :
212+ j[" origin" ] = " code" ;
213+ break ;
214+ case ConfigMetadata::Origin::REMOTE_CONFIG :
215+ j[" origin" ] = " remote_config" ;
216+ break ;
217+ case ConfigMetadata::Origin::DEFAULT :
218+ j[" origin" ] = " default" ;
219+ break ;
220+ }
221+
222+ if (config_metadata.error ) {
223+ // clang-format off
224+ j[" error" ] = {
225+ {" code" , config_metadata.error ->code },
226+ {" message" , config_metadata.error ->message }
227+ };
228+ // clang-format on
229+ }
230+
231+ return j;
232+ }
233+
201234} // namespace
202235
203236Telemetry::Telemetry (FinalizedConfiguration config,
@@ -214,6 +247,16 @@ Telemetry::Telemetry(FinalizedConfiguration config,
214247 clock_(std::move(clock)),
215248 scheduler_(event_scheduler),
216249 host_info_(get_host_info()) {
250+ // Initialize current_configuration_ from the product configurations provided
251+ // at startup. Each config name maps to a vector ordered by precedence
252+ // (lowest to highest), so we take the last entry as the effective value.
253+ for (const auto & product : config_.products ) {
254+ for (const auto & [config_name, config_metadatas] : product.configurations ) {
255+ if (!config_metadatas.empty ()) {
256+ current_configuration_[config_name] = config_metadatas.back ();
257+ }
258+ }
259+ }
217260 app_started ();
218261 schedule_tasks ();
219262}
@@ -227,6 +270,12 @@ void Telemetry::schedule_tasks() {
227270 tasks_.emplace_back (scheduler_->schedule_recurring_event (
228271 config_.metrics_interval , [this ]() mutable { capture_metrics (); }));
229272 }
273+
274+ tasks_.emplace_back (scheduler_->schedule_recurring_event (
275+ config_.extended_heartbeat_interval ,
276+ [this ]() {
277+ send_payload (" app-extended-heartbeat" , extended_heartbeat_payload ());
278+ }));
230279}
231280
232281Telemetry::~Telemetry () {
@@ -249,6 +298,7 @@ Telemetry::Telemetry(Telemetry&& rhs)
249298 rates_(std::move(rhs.rates_)),
250299 rates_snapshot_(std::move(rhs.rates_snapshot_)),
251300 distributions_(std::move(rhs.distributions_)),
301+ current_configuration_(std::move(rhs.current_configuration_)),
252302 seq_id_(rhs.seq_id_),
253303 config_seq_ids_(rhs.config_seq_ids_),
254304 host_info_(rhs.host_info_) {
@@ -274,6 +324,7 @@ Telemetry& Telemetry::operator=(Telemetry&& rhs) {
274324 std::swap (distributions_, rhs.distributions_ );
275325 std::swap (seq_id_, rhs.seq_id_ );
276326 std::swap (config_seq_ids_, rhs.config_seq_ids_ );
327+ std::swap (current_configuration_, rhs.current_configuration_ );
277328 std::swap (host_info_, rhs.host_info_ );
278329 schedule_tasks ();
279330 }
@@ -425,13 +476,13 @@ void Telemetry::send_payload(StringView request_type, std::string payload) {
425476void Telemetry::send_configuration_change () {
426477 if (configuration_snapshot_.empty ()) return ;
427478
428- std::vector<ConfigMetadata> current_configuration ;
429- std::swap (current_configuration , configuration_snapshot_);
479+ std::vector<ConfigMetadata> pending ;
480+ std::swap (pending , configuration_snapshot_);
430481
431482 auto configuration_json = nlohmann::json::array ();
432- for (const auto & config_metadata : current_configuration ) {
483+ for (const auto & config_metadata : pending ) {
433484 configuration_json.emplace_back (
434- generate_configuration_field (config_metadata));
485+ report_new_config_field (config_metadata));
435486 }
436487
437488 auto telemetry_body =
@@ -591,7 +642,7 @@ std::string Telemetry::app_started_payload() {
591642 // if (config_metadata.value.empty()) continue;
592643 for (const auto & config_metadata : config_metadatas) {
593644 configuration_json.emplace_back (
594- generate_configuration_field (config_metadata));
645+ report_new_config_field (config_metadata));
595646 }
596647 }
597648
@@ -678,6 +729,36 @@ std::string Telemetry::app_started_payload() {
678729 return batch.dump ();
679730}
680731
732+ std::string Telemetry::extended_heartbeat_payload () {
733+ auto configuration_json = nlohmann::json::array ();
734+ for (const auto & [_, config_metadata] : current_configuration_) {
735+ // Use the current seq_id without incrementing — this is a snapshot
736+ // refresh, not a new change event.
737+ auto seq_id = config_seq_ids_[config_metadata.name ];
738+ configuration_json.emplace_back (
739+ encode_configuration_field (config_metadata, seq_id));
740+ }
741+
742+ auto payload = nlohmann::json{{" configuration" , configuration_json}};
743+
744+ if (!config_.integration_name .empty ()) {
745+ payload[" integrations" ] = nlohmann::json::array ({
746+ nlohmann::json{{" name" , config_.integration_name },
747+ {" version" , config_.integration_version },
748+ {" enabled" , true }},
749+ });
750+ }
751+
752+ auto extended_heartbeat = nlohmann::json{
753+ {" request_type" , " app-extended-heartbeat" },
754+ {" payload" , std::move (payload)},
755+ };
756+
757+ auto batch = generate_telemetry_body (" message-batch" );
758+ batch[" payload" ] = nlohmann::json::array ({std::move (extended_heartbeat)});
759+ return batch.dump ();
760+ }
761+
681762nlohmann::json Telemetry::generate_telemetry_body (std::string request_type) {
682763 std::time_t tracer_time = std::chrono::duration_cast<std::chrono::seconds>(
683764 clock_ ().wall .time_since_epoch ())
@@ -711,49 +792,21 @@ nlohmann::json Telemetry::generate_telemetry_body(std::string request_type) {
711792 });
712793}
713794
714- nlohmann::json Telemetry::generate_configuration_field (
795+ nlohmann::json Telemetry::report_new_config_field (
715796 const ConfigMetadata& config_metadata) {
716797 // NOTE(@dmehala): `seq_id` should start at 1 so that the go backend can
717798 // detect between non set fields.
718799 config_seq_ids_[config_metadata.name ] += 1 ;
719- auto seq_id = config_seq_ids_[config_metadata.name ];
720-
721- auto j = nlohmann::json{{" name" , to_string (config_metadata.name )},
722- {" value" , config_metadata.value },
723- {" seq_id" , seq_id}};
724-
725- switch (config_metadata.origin ) {
726- case ConfigMetadata::Origin::ENVIRONMENT_VARIABLE :
727- j[" origin" ] = " env_var" ;
728- break ;
729- case ConfigMetadata::Origin::CODE :
730- j[" origin" ] = " code" ;
731- break ;
732- case ConfigMetadata::Origin::REMOTE_CONFIG :
733- j[" origin" ] = " remote_config" ;
734- break ;
735- case ConfigMetadata::Origin::DEFAULT :
736- j[" origin" ] = " default" ;
737- break ;
738- }
739-
740- if (config_metadata.error ) {
741- // clang-format off
742- j[" error" ] = {
743- {" code" , config_metadata.error ->code },
744- {" message" , config_metadata.error ->message }
745- };
746- // clang-format on
747- }
748-
749- return j;
800+ return encode_configuration_field (config_metadata,
801+ config_seq_ids_[config_metadata.name ]);
750802}
751803
752804void Telemetry::capture_configuration_change (
753805 const std::vector<tracing::ConfigMetadata>& new_configuration) {
754- configuration_snapshot_.insert (configuration_snapshot_.begin (),
755- new_configuration.begin (),
756- new_configuration.end ());
806+ for (const auto & config : new_configuration) {
807+ configuration_snapshot_.push_back (config);
808+ current_configuration_[config.name ] = config;
809+ }
757810}
758811
759812void Telemetry::capture_metrics () {
0 commit comments