Skip to content

Commit ccc2a54

Browse files
authored
Config Visibility: report all sources and their values (#268)
* minor env adjustments * struct changes to accomodate for reporting all sources * compute all sources in vectors and pick overload for it * add new test for telemetry configs vector with precedence * adapt telemetry test * Answer to comments
1 parent 139191e commit ccc2a54

8 files changed

Lines changed: 293 additions & 109 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ dist/
99
out/
1010
MODULE.bazel.lock
1111
.vscode
12+
.cache/
13+
.cursor/
14+
.DS_Store

include/datadog/config.h

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#pragma once
22

3+
#include <string>
4+
#include <type_traits>
5+
#include <unordered_map>
6+
#include <utility>
7+
#include <vector>
8+
39
#include "error.h"
410
#include "optional.h"
511

@@ -56,14 +62,94 @@ struct ConfigMetadata {
5662
: name(n), value(std::move(v)), origin(orig), error(std::move(err)) {}
5763
};
5864

65+
// Returns the final configuration value using the following
66+
// precedence order: environment > user code > default, and populates two maps:
67+
// 1. `telemetry_configs`: Records ALL configuration sources that were provided,
68+
// ordered from lowest to highest precedence.
69+
// 2. `metadata`: Records ONLY the winning configuration value (highest
70+
// precedence). Template Parameters:
71+
// Value: The type of the configuration value
72+
// Stringifier: Optional function type to convert Value to string
73+
// (defaults to std::nullptr_t, which uses string construction)
74+
// DefaultValue: Type of the fallback value (defaults to std::nullptr_t)
75+
//
76+
// Parameters:
77+
// from_env: Optional value from environment variables (highest precedence)
78+
// from_user: Optional value from user code (middle precedence)
79+
// telemetry_configs: Output map that will be populated with all config
80+
// sources found for this config_name, in precedence order
81+
// metadata: Output map that will be populated with the winning config value
82+
// for this config_name
83+
// config_name: The configuration parameter name identifier
84+
// fallback: Optional default value (lowest precedence). Pass nullptr to
85+
// indicate no default.
86+
// to_string_fn: Optional custom function to convert Value to string.
87+
// Required for non-string types. For string-like types, uses
88+
// default string construction if not provided.
89+
//
90+
// Returns:
91+
// The chosen configuration value based on precedence, or Value{} if no value
92+
// was provided.
93+
template <typename Value, typename Stringifier = std::nullptr_t,
94+
typename DefaultValue = std::nullptr_t>
95+
Value resolve_and_record_config(
96+
const Optional<Value>& from_env, const Optional<Value>& from_user,
97+
std::unordered_map<ConfigName, std::vector<ConfigMetadata>>*
98+
telemetry_configs,
99+
std::unordered_map<ConfigName, ConfigMetadata>* metadata,
100+
ConfigName config_name, DefaultValue fallback = nullptr,
101+
Stringifier to_string_fn = nullptr) {
102+
auto stringify = [&](const Value& v) -> std::string {
103+
if constexpr (!std::is_same_v<Stringifier, std::nullptr_t>) {
104+
return to_string_fn(v); // use provided function
105+
} else if constexpr (std::is_constructible_v<std::string, Value>) {
106+
return std::string(v); // default behaviour (works for string-like types)
107+
} else {
108+
static_assert(!std::is_same_v<Value, Value>,
109+
"Non-string types require a stringifier function");
110+
return ""; // unreachable
111+
}
112+
};
113+
114+
std::vector<ConfigMetadata> telemetry_entries;
115+
Optional<Value> chosen_value;
116+
117+
auto add_entry = [&](ConfigMetadata::Origin origin, const Value& val) {
118+
std::string val_str = stringify(val);
119+
telemetry_entries.emplace_back(
120+
ConfigMetadata{config_name, val_str, origin});
121+
chosen_value = val;
122+
};
123+
124+
// Add DEFAULT entry if fallback was provided (detected by type)
125+
if constexpr (!std::is_same_v<DefaultValue, std::nullptr_t>) {
126+
add_entry(ConfigMetadata::Origin::DEFAULT, fallback);
127+
}
128+
129+
if (from_user) {
130+
add_entry(ConfigMetadata::Origin::CODE, *from_user);
131+
}
132+
133+
if (from_env) {
134+
add_entry(ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env);
135+
}
136+
137+
(*telemetry_configs)[config_name] = std::move(telemetry_entries);
138+
if (!(*telemetry_configs)[config_name].empty()) {
139+
(*metadata)[config_name] = (*telemetry_configs)[config_name].back();
140+
}
141+
142+
return chosen_value.value_or(Value{});
143+
}
144+
59145
// Return a pair containing the configuration origin and value of a
60146
// configuration value chosen from one of the specified `from_env`,
61147
// `from_config`, and `fallback`. This function defines the relative precedence
62148
// among configuration values originating from the environment, programmatic
63149
// configuration, and default configuration.
64150
template <typename Value, typename DefaultValue>
65-
std::pair<ConfigMetadata::Origin, Value> pick(const Optional<Value> &from_env,
66-
const Optional<Value> &from_user,
151+
std::pair<ConfigMetadata::Origin, Value> pick(const Optional<Value>& from_env,
152+
const Optional<Value>& from_user,
67153
DefaultValue fallback) {
68154
if (from_env) {
69155
return {ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env};

include/datadog/telemetry/product.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <string>
77
#include <unordered_map>
8+
#include <vector>
89

910
namespace datadog::telemetry {
1011

@@ -30,7 +31,7 @@ struct Product final {
3031
/// Optional error message related to the product status.
3132
tracing::Optional<std::string> error_message;
3233
/// Map of configuration settings for the product.
33-
std::unordered_map<tracing::ConfigName, tracing::ConfigMetadata>
34+
std::unordered_map<tracing::ConfigName, std::vector<tracing::ConfigMetadata>>
3435
configurations;
3536
};
3637

src/datadog/telemetry/telemetry_impl.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -587,11 +587,12 @@ std::string Telemetry::app_started_payload() {
587587

588588
for (const auto& product : config_.products) {
589589
auto& configurations = product.configurations;
590-
for (const auto& [_, config_metadata] : configurations) {
590+
for (const auto& [_, config_metadatas] : configurations) {
591591
// if (config_metadata.value.empty()) continue;
592-
593-
configuration_json.emplace_back(
594-
generate_configuration_field(config_metadata));
592+
for (const auto& config_metadata : config_metadatas) {
593+
configuration_json.emplace_back(
594+
generate_configuration_field(config_metadata));
595+
}
595596
}
596597

597598
/// NOTE(@dmehala): Telemetry API is tightly related to APM tracing and

src/datadog/trace_sampler_config.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,12 @@ Expected<FinalizedTraceSamplerConfig> finalize_config(
225225
result.rules.emplace_back(std::move(finalized_rule));
226226
}
227227

228-
const auto [origin, max_per_second] =
229-
pick(env_config->max_per_second, config.max_per_second, 100);
230-
result.metadata[ConfigName::TRACE_SAMPLING_LIMIT] = ConfigMetadata(
231-
ConfigName::TRACE_SAMPLING_LIMIT, std::to_string(max_per_second), origin);
228+
std::unordered_map<ConfigName, std::vector<ConfigMetadata>>
229+
telemetry_configs_tmp;
230+
double max_per_second = resolve_and_record_config(
231+
env_config->max_per_second, config.max_per_second, &telemetry_configs_tmp,
232+
&result.metadata, ConfigName::TRACE_SAMPLING_LIMIT, 100.0,
233+
[](const double &d) { return std::to_string(d); });
232234

233235
const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL};
234236
if (!(max_per_second > 0) ||

0 commit comments

Comments
 (0)