Skip to content

Commit 874bb37

Browse files
committed
ApmTracingConfig id and ConfigurationProvider
1 parent 51c3adc commit 874bb37

File tree

5 files changed

+234
-80
lines changed

5 files changed

+234
-80
lines changed

datadog-opentelemetry/tests/integration_tests/opentelemetry_api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ async fn test_remote_config_sampling_rates() {
191191
r##"{
192192
"path": "datadog/2/APM_TRACING/1234/config",
193193
"msg": {
194+
"id": "42",
194195
"lib_config": {
195196
"tracing_sampling_rules": [
196197
{

dd-trace/src/configuration/configuration.rs

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,24 @@ impl<T: ConfigurationValueProvider> ConfigurationValueProvider for ConfigItemRef
191191
}
192192
}
193193

194+
/// A trait for providing configuration data for telemetry reporting.
195+
///
196+
/// This trait standardizes how configuration items expose their current state
197+
/// as `ddtelemetry::data::Configuration` payloads for telemetry collection.
198+
/// It enables the configuration system to report configuration values, their
199+
/// origins, and associated metadata to Datadog.
200+
pub trait ConfigurationProvider {
201+
/// Returns a telemetry configuration object representing the current state of this configuration item.
202+
///
203+
/// # Parameters
204+
///
205+
/// - `config_id`: Optional identifier for remote configuration scenarios.
206+
/// When provided, this ID is included in the returned `Configuration` to
207+
/// track which remote configuration is responsible for the current value.
208+
///
209+
fn get_configuration(&self, config_id: Option<String>) -> Configuration;
210+
}
211+
194212
/// A trait for converting configuration values to their string representation for telemetry.
195213
///
196214
/// This trait is used to serialize configuration values into strings that can be sent
@@ -288,14 +306,16 @@ impl<T: Clone + ConfigurationValueProvider> ConfigItem<T> {
288306
ConfigSourceOrigin::Default
289307
}
290308
}
309+
}
291310

311+
impl<T: Clone + ConfigurationValueProvider> ConfigurationProvider for ConfigItem<T> {
292312
/// Gets a Configuration object used as telemetry payload
293-
fn get_configuration(&self) -> Configuration {
313+
fn get_configuration(&self, config_id: Option<String>) -> Configuration {
294314
Configuration {
295315
name: self.name.to_string(),
296316
value: self.value().get_configuration_value(),
297317
origin: self.source().into(),
298-
config_id: None,
318+
config_id,
299319
}
300320
}
301321
}
@@ -387,14 +407,18 @@ impl<T: ConfigurationValueProvider + Clone + Deref> ConfigItemWithOverride<T> {
387407
ConfigItemRef::Ref(self.config_item.value())
388408
}
389409
}
410+
}
390411

412+
impl<T: Clone + ConfigurationValueProvider + Deref> ConfigurationProvider
413+
for ConfigItemWithOverride<T>
414+
{
391415
/// Gets a Configuration object used as telemetry payload
392-
fn get_configuration(&self) -> Configuration {
416+
fn get_configuration(&self, config_id: Option<String>) -> Configuration {
393417
Configuration {
394418
name: self.config_item.name.to_string(),
395419
value: self.value().get_configuration_value(),
396420
origin: self.source().into(),
397-
config_id: None,
421+
config_id,
398422
}
399423
}
400424
}
@@ -970,33 +994,33 @@ impl Config {
970994
Self::builder_with_sources(&CompositeSource::default_sources())
971995
}
972996

973-
pub fn get_telemetry_configuration(&self) -> Vec<Configuration> {
997+
pub fn get_telemetry_configuration(&self) -> Vec<&dyn ConfigurationProvider> {
974998
vec![
975-
self.service.get_configuration(),
976-
self.env.get_configuration(),
977-
self.version.get_configuration(),
978-
self.global_tags.get_configuration(),
979-
self.agent_host.get_configuration(),
980-
self.trace_agent_port.get_configuration(),
981-
self.trace_agent_url.get_configuration(),
982-
self.dogstatsd_agent_host.get_configuration(),
983-
self.dogstatsd_agent_port.get_configuration(),
984-
self.dogstatsd_agent_url.get_configuration(),
985-
self.trace_sampling_rules.get_configuration(),
986-
self.trace_rate_limit.get_configuration(),
987-
self.enabled.get_configuration(),
988-
self.log_level_filter.get_configuration(),
989-
self.trace_stats_computation_enabled.get_configuration(),
990-
self.telemetry_enabled.get_configuration(),
991-
self.telemetry_log_collection_enabled.get_configuration(),
992-
self.telemetry_heartbeat_interval.get_configuration(),
993-
self.trace_propagation_style.get_configuration(),
994-
self.trace_propagation_style_extract.get_configuration(),
995-
self.trace_propagation_style_inject.get_configuration(),
996-
self.trace_propagation_extract_first.get_configuration(),
997-
self.remote_config_enabled.get_configuration(),
998-
self.remote_config_poll_interval.get_configuration(),
999-
self.datadog_tags_max_length.get_configuration(),
999+
&self.service,
1000+
&self.env,
1001+
&self.version,
1002+
&self.global_tags,
1003+
&self.agent_host,
1004+
&self.trace_agent_port,
1005+
&self.trace_agent_url,
1006+
&self.dogstatsd_agent_host,
1007+
&self.dogstatsd_agent_port,
1008+
&self.dogstatsd_agent_url,
1009+
&self.trace_sampling_rules,
1010+
&self.trace_rate_limit,
1011+
&self.enabled,
1012+
&self.log_level_filter,
1013+
&self.trace_stats_computation_enabled,
1014+
&self.telemetry_enabled,
1015+
&self.telemetry_log_collection_enabled,
1016+
&self.telemetry_heartbeat_interval,
1017+
&self.trace_propagation_style,
1018+
&self.trace_propagation_style_extract,
1019+
&self.trace_propagation_style_inject,
1020+
&self.trace_propagation_extract_first,
1021+
&self.remote_config_enabled,
1022+
&self.remote_config_poll_interval,
1023+
&self.datadog_tags_max_length,
10001024
]
10011025
}
10021026

@@ -1118,14 +1142,18 @@ impl Config {
11181142
*self.trace_propagation_extract_first.value()
11191143
}
11201144

1121-
pub fn update_sampling_rules_from_remote(&self, rules_json: &str) -> Result<(), String> {
1145+
pub fn update_sampling_rules_from_remote(
1146+
&self,
1147+
rules_json: &str,
1148+
config_id: Option<String>,
1149+
) -> Result<(), String> {
11221150
// Parse the JSON into SamplingRuleConfig objects
11231151
let rules: Vec<SamplingRuleConfig> = serde_json::from_str(rules_json)
11241152
.map_err(|e| format!("Failed to parse sampling rules JSON: {e}"))?;
11251153

11261154
// If remote config sends empty rules, clear remote config to fall back to local rules
11271155
if rules.is_empty() {
1128-
self.clear_remote_sampling_rules();
1156+
self.clear_remote_sampling_rules(config_id);
11291157
} else {
11301158
self.trace_sampling_rules.set_override_value(
11311159
ParsedSamplingRules { rules },
@@ -1137,7 +1165,7 @@ impl Config {
11371165
&RemoteConfigUpdate::SamplingRules(self.trace_sampling_rules().to_vec()),
11381166
);
11391167

1140-
telemetry::notify_update_configuration(self.trace_sampling_rules.get_configuration());
1168+
telemetry::notify_configuration_update(&self.trace_sampling_rules, config_id);
11411169
}
11421170

11431171
Ok(())
@@ -1152,14 +1180,14 @@ impl Config {
11521180
}
11531181
}
11541182

1155-
pub fn clear_remote_sampling_rules(&self) {
1183+
pub fn clear_remote_sampling_rules(&self, config_id: Option<String>) {
11561184
self.trace_sampling_rules.unset_override_value();
11571185

11581186
self.remote_config_callbacks.lock().unwrap().notify_update(
11591187
&RemoteConfigUpdate::SamplingRules(self.trace_sampling_rules().to_vec()),
11601188
);
11611189

1162-
telemetry::notify_update_configuration(self.trace_sampling_rules.get_configuration());
1190+
telemetry::notify_configuration_update(&self.trace_sampling_rules, config_id);
11631191
}
11641192

11651193
/// Add a callback to be called when sampling rules are updated via remote configuration
@@ -1984,7 +2012,7 @@ mod tests {
19842012

19852013
let rules_json = serde_json::to_string(&new_rules).unwrap();
19862014
config
1987-
.update_sampling_rules_from_remote(&rules_json)
2015+
.update_sampling_rules_from_remote(&rules_json, None)
19882016
.unwrap();
19892017

19902018
// Callback should be called with the new rules
@@ -1995,7 +2023,7 @@ mod tests {
19952023
*callback_called.lock().unwrap() = false;
19962024
callback_rules.lock().unwrap().clear();
19972025

1998-
config.clear_remote_sampling_rules();
2026+
config.clear_remote_sampling_rules(None);
19992027

20002028
// Callback should be called with fallback rules (empty in this case since no env/code rules
20012029
// set)
@@ -2118,7 +2146,7 @@ mod tests {
21182146
let remote_rules_json =
21192147
r#"[{"sample_rate": 0.8, "service": "remote-service", "provenance": "remote"}]"#;
21202148
config
2121-
.update_sampling_rules_from_remote(remote_rules_json)
2149+
.update_sampling_rules_from_remote(remote_rules_json, None)
21222150
.unwrap();
21232151

21242152
// Verify remote rules override local rules
@@ -2132,7 +2160,7 @@ mod tests {
21322160
// 3. Remote config sends empty array []
21332161
let empty_remote_rules_json = "[]";
21342162
config
2135-
.update_sampling_rules_from_remote(empty_remote_rules_json)
2163+
.update_sampling_rules_from_remote(empty_remote_rules_json, None)
21362164
.unwrap();
21372165

21382166
// Empty remote rules automatically fall back to local rules
@@ -2145,7 +2173,7 @@ mod tests {
21452173

21462174
// 4. Verify explicit clearing still works (for completeness)
21472175
// Since we're already on local rules, clear should keep us on local rules
2148-
config.clear_remote_sampling_rules();
2176+
config.clear_remote_sampling_rules(None);
21492177

21502178
// Should remain on local rules
21512179
assert_eq!(config.trace_sampling_rules().len(), 1);
@@ -2398,7 +2426,7 @@ mod tests {
23982426
r#"[{"sample_rate":0.5,"service":"web-api","name":null,"resource":null,"tags":{},"provenance":"customer"}]"#
23992427
).unwrap();
24002428

2401-
let configuration = &config.trace_sampling_rules.get_configuration();
2429+
let configuration = &config.trace_sampling_rules.get_configuration(None);
24022430
assert_eq!(configuration.origin, ConfigurationOrigin::EnvVar);
24032431

24042432
// Converting configuration value to json helps with comparison as serialized properties may
@@ -2414,7 +2442,7 @@ mod tests {
24142442
.trace_sampling_rules
24152443
.set_override_value(expected_rc.clone(), ConfigSourceOrigin::RemoteConfig);
24162444

2417-
let configuration_after_rc = &config.trace_sampling_rules.get_configuration();
2445+
let configuration_after_rc = &config.trace_sampling_rules.get_configuration(None);
24182446
assert_eq!(
24192447
configuration_after_rc.origin,
24202448
ConfigurationOrigin::RemoteConfig
@@ -2427,7 +2455,7 @@ mod tests {
24272455
// Reset ConfigItemRc RC previous value
24282456
config.trace_sampling_rules.unset_override_value();
24292457

2430-
let configuration = &config.trace_sampling_rules.get_configuration();
2458+
let configuration = &config.trace_sampling_rules.get_configuration(None);
24312459
assert_eq!(configuration.origin, ConfigurationOrigin::EnvVar);
24322460
assert_eq!(
24332461
ParsedSamplingRules::from_str(&configuration.value).unwrap(),

dd-trace/src/configuration/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ pub mod remote_config;
77
mod sources;
88

99
pub use configuration::{
10-
Config, ConfigBuilder, RemoteConfigUpdate, SamplingRuleConfig, TracePropagationStyle,
10+
Config, ConfigBuilder, ConfigurationProvider, RemoteConfigUpdate, SamplingRuleConfig,
11+
TracePropagationStyle,
1112
};

dd-trace/src/configuration/remote_config.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ where
190190
/// See: https://github.com/DataDog/dd-go/blob/prod/remote-config/apps/rc-schema-validation/schemas/apm-tracing.json
191191
#[derive(Debug, Clone, Deserialize)]
192192
struct ApmTracingConfig {
193+
id: String,
193194
lib_config: LibConfig, // lib_config is a required property
194195
}
195196

@@ -845,7 +846,8 @@ impl ProductHandler for ApmTracingHandler {
845846
let rules_json = serde_json::to_string(&rules_value)
846847
.map_err(|e| anyhow::anyhow!("Failed to serialize sampling rules: {}", e))?;
847848

848-
match config.update_sampling_rules_from_remote(&rules_json) {
849+
match config.update_sampling_rules_from_remote(&rules_json, Some(tracing_config.id))
850+
{
849851
Ok(()) => {
850852
crate::dd_debug!(
851853
"RemoteConfigClient: Applied sampling rules from remote config"
@@ -862,7 +864,7 @@ impl ProductHandler for ApmTracingHandler {
862864
crate::dd_debug!(
863865
"RemoteConfigClient: APM tracing config received but tracing_sampling_rules is null"
864866
);
865-
config.clear_remote_sampling_rules();
867+
config.clear_remote_sampling_rules(Some(tracing_config.id));
866868
}
867869
} else {
868870
crate::dd_debug!(
@@ -1088,6 +1090,7 @@ mod tests {
10881090
#[test]
10891091
fn test_apm_tracing_config_parsing() {
10901092
let json = r#"{
1093+
"id": "42",
10911094
"lib_config": {
10921095
"tracing_sampling_rules": [
10931096
{
@@ -1115,6 +1118,7 @@ mod tests {
11151118
fn test_apm_tracing_config_full_schema() {
11161119
// Test parsing a more complete configuration
11171120
let json = r#"{
1121+
"id": "42",
11181122
"lib_config": {
11191123
"tracing_sampling_rules": [
11201124
{
@@ -1303,7 +1307,7 @@ mod tests {
13031307
target_files: Some(vec![
13041308
TargetFile {
13051309
path: "datadog/2/APM_TRACING/apm-tracing-sampling/config".to_string(),
1306-
raw: "eyJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(), // base64 encoded APM config
1310+
raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(), // base64 encoded APM config
13071311
},
13081312
]),
13091313
client_configs: Some(vec![
@@ -1343,7 +1347,7 @@ mod tests {
13431347
cached_files[0].path,
13441348
"datadog/2/APM_TRACING/apm-tracing-sampling/config"
13451349
);
1346-
assert_eq!(cached_files[0].length, 124);
1350+
assert_eq!(cached_files[0].length, 140);
13471351
assert_eq!(cached_files[0].hashes.len(), 1);
13481352
assert_eq!(cached_files[0].hashes[0].algorithm, "sha256");
13491353

@@ -1419,7 +1423,7 @@ mod tests {
14191423
target_files: Some(vec![
14201424
TargetFile {
14211425
path: "datadog/2/APM_TRACING/test-config/config".to_string(),
1422-
raw: "eyJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(),
1426+
raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(),
14231427
},
14241428
]),
14251429
client_configs: Some(vec![
@@ -1648,7 +1652,7 @@ mod tests {
16481652

16491653
// Test processing config - this should not panic for valid JSON
16501654
let config = Arc::new(Config::builder().build());
1651-
let config_json = r#"{"lib_config": {"tracing_sampling_rules": [{"sample_rate": 0.5, "service": "test"}]}}"#;
1655+
let config_json = r#"{"id": "42", "lib_config": {"tracing_sampling_rules": [{"sample_rate": 0.5, "service": "test"}]}}"#;
16521656

16531657
// This should succeed
16541658
let result = handler.process_config(config_json, &config);
@@ -1673,7 +1677,7 @@ mod tests {
16731677
target_files: Some(vec![
16741678
TargetFile {
16751679
path: "datadog/2/APM_TRACING/config1/config".to_string(),
1676-
raw: "eyJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZS0xIn1dfX0=".to_string(),
1680+
raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZS0xIn1dfX0=".to_string(),
16771681
},
16781682
]),
16791683
client_configs: Some(vec![
@@ -1700,11 +1704,11 @@ mod tests {
17001704
target_files: Some(vec![
17011705
TargetFile {
17021706
path: "datadog/2/APM_TRACING/config2/config".to_string(),
1703-
raw: "eyJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjc1LCAic2VydmljZSI6ICJ0ZXN0LXNlcnZpY2UtMiJ9XX19".to_string(),
1707+
raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJpZCI6IjQyIiwgInRyYWNpbmdfc2FtcGxpbmdfcnVsZXMiOiBbeyJzYW1wbGVfcmF0ZSI6IDAuNzUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZS0yIn1dfX0=".to_string(),
17041708
},
17051709
TargetFile {
17061710
path: "datadog/2/APM_TRACING/config3/config".to_string(),
1707-
raw: "eyJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjI1LCAic2VydmljZSI6ICJ0ZXN0LXNlcnZpY2UtMiJ9XX19".to_string(),
1711+
raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJpZCI6IjQyIiwgInRyYWNpbmdfc2FtcGxpbmdfcnVsZXMiOiBbeyJzYW1wbGVfcmF0ZSI6IDAuMjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZS0yIn1dfX0=".to_string(),
17081712
},
17091713
]),
17101714
client_configs: Some(vec![
@@ -1776,7 +1780,7 @@ mod tests {
17761780
target_files: Some(vec![
17771781
TargetFile {
17781782
path: "datadog/2/APM_TRACING/good_config/config".to_string(),
1779-
raw: "eyJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(),
1783+
raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjUsICJzZXJ2aWNlIjogInRlc3Qtc2VydmljZSJ9XX19".to_string(),
17801784
},
17811785
]),
17821786
client_configs: Some(vec![
@@ -1878,7 +1882,7 @@ mod tests {
18781882
target_files: Some(vec![
18791883
TargetFile {
18801884
path: "datadog/2/APM_TRACING/test-sampling/config".to_string(),
1881-
raw: "eyJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjc1LCAic2VydmljZSI6ICJ0ZXN0LWFwcC1zZXJ2aWNlIn1dfX0=".to_string(), // base64 encoded sampling rules
1885+
raw: "eyJpZCI6ICI0MiIsICJsaWJfY29uZmlnIjogeyJ0cmFjaW5nX3NhbXBsaW5nX3J1bGVzIjogW3sic2FtcGxlX3JhdGUiOiAwLjc1LCAic2VydmljZSI6ICJ0ZXN0LWFwcC1zZXJ2aWNlIn1dfX0=".to_string(), // base64 encoded sampling rules
18821886
},
18831887
]),
18841888
client_configs: Some(vec![
@@ -1918,7 +1922,7 @@ mod tests {
19181922

19191923
#[test]
19201924
fn test_deserialize_tracing_sampling_rules_null() {
1921-
let config_json = r#"{"lib_config": {"tracing_sampling_rules": null}}"#;
1925+
let config_json = r#"{"id": "42", "lib_config": {"tracing_sampling_rules": null}}"#;
19221926
let tracing_config: ApmTracingConfig =
19231927
serde_json::from_str(config_json).expect("Json should be parsed");
19241928

@@ -1932,7 +1936,7 @@ mod tests {
19321936

19331937
#[test]
19341938
fn test_deserialize_tracing_sampling_rules_missing() {
1935-
let config_json = r#"{"lib_config": {}}"#;
1939+
let config_json = r#"{"id": "42", "lib_config": {}}"#;
19361940
let tracing_config: ApmTracingConfig =
19371941
serde_json::from_str(config_json).expect("Json should be parsed");
19381942

0 commit comments

Comments
 (0)