Skip to content

Commit d65b658

Browse files
authored
Aj/yaml apm replace tags (#602)
* feat: yaml APM replace tags rule parsing * feat: Custom deserializer for replace tags. yaml -> JSON so we can rely on the same method because ReplaceRule is totally private * remove aj * feat: merge w/ libdatadog main * feat: Parse http obfuscation config from yaml * feat: licenses
1 parent a7b45a1 commit d65b658

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

apm_replace_rule.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use datadog_trace_obfuscation::replacer::{parse_rules_from_string, ReplaceRule};
2+
use serde::de::{Deserializer, SeqAccess, Visitor};
3+
use serde::{Deserialize, Serialize};
4+
use serde_json;
5+
use std::fmt;
6+
7+
#[derive(Deserialize, Serialize)]
8+
struct ReplaceRuleYaml {
9+
name: String,
10+
pattern: String,
11+
repl: String,
12+
}
13+
14+
struct StringOrReplaceRulesVisitor;
15+
16+
impl<'de> Visitor<'de> for StringOrReplaceRulesVisitor {
17+
type Value = String;
18+
19+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
20+
formatter.write_str("a JSON string or YAML sequence of replace rules")
21+
}
22+
23+
// Handle existing JSON strings
24+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
25+
where
26+
E: serde::de::Error,
27+
{
28+
// Validate it's at least valid JSON
29+
let _: serde_json::Value =
30+
serde_json::from_str(value).map_err(|_| E::custom("Expected valid JSON string"))?;
31+
Ok(value.to_string())
32+
}
33+
34+
// Convert YAML sequences to JSON strings
35+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
36+
where
37+
A: SeqAccess<'de>,
38+
{
39+
let mut rules = Vec::new();
40+
while let Some(rule) = seq.next_element::<ReplaceRuleYaml>()? {
41+
rules.push(rule);
42+
}
43+
// Serialize to JSON string for compatibility with parse_rules_from_string
44+
serde_json::to_string(&rules).map_err(|e| {
45+
serde::de::Error::custom(format!("Failed to serialize rules to JSON: {e}"))
46+
})
47+
}
48+
}
49+
50+
pub fn deserialize_apm_replace_rules<'de, D>(
51+
deserializer: D,
52+
) -> Result<Option<Vec<ReplaceRule>>, D::Error>
53+
where
54+
D: Deserializer<'de>,
55+
{
56+
let json_string = deserializer.deserialize_any(StringOrReplaceRulesVisitor)?;
57+
58+
let rules = parse_rules_from_string(&json_string)
59+
.map_err(|e| serde::de::Error::custom(format!("Parse error: {e}")))?;
60+
61+
Ok(Some(rules))
62+
}

mod.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
pub mod apm_replace_rule;
12
pub mod flush_strategy;
23
pub mod log_level;
34
pub mod processing_rule;
45
pub mod service_mapping;
56
pub mod trace_propagation_style;
67

8+
use datadog_trace_obfuscation::replacer::ReplaceRule;
79
use datadog_trace_utils::config_utils::{trace_intake_url, trace_intake_url_prefixed};
810
use std::collections::HashMap;
911
use std::path::Path;
@@ -17,6 +19,7 @@ use serde_json::Value;
1719
use trace_propagation_style::{deserialize_trace_propagation_style, TracePropagationStyle};
1820

1921
use crate::config::{
22+
apm_replace_rule::deserialize_apm_replace_rules,
2023
flush_strategy::FlushStrategy,
2124
log_level::{deserialize_log_level, LogLevel},
2225
processing_rule::{deserialize_processing_rules, ProcessingRule},
@@ -57,6 +60,30 @@ pub struct YamlLogsConfig {
5760
processing_rules: Option<Vec<ProcessingRule>>,
5861
}
5962

63+
#[derive(Debug, PartialEq, Deserialize, Clone, Default)]
64+
#[serde(default)]
65+
#[allow(clippy::module_name_repetitions)]
66+
pub struct YamlApmConfig {
67+
#[serde(deserialize_with = "deserialize_apm_replace_rules")]
68+
replace_tags: Option<Vec<ReplaceRule>>,
69+
obfuscation: Option<ApmObfuscation>,
70+
}
71+
72+
#[derive(Debug, PartialEq, Deserialize, Clone, Copy, Default)]
73+
#[serde(default)]
74+
#[allow(clippy::module_name_repetitions)]
75+
pub struct ApmObfuscation {
76+
http: ApmHttpObfuscation,
77+
}
78+
79+
#[derive(Debug, PartialEq, Deserialize, Clone, Copy, Default)]
80+
#[serde(default)]
81+
#[allow(clippy::module_name_repetitions)]
82+
pub struct ApmHttpObfuscation {
83+
remove_query_string: bool,
84+
remove_paths_with_digits: bool,
85+
}
86+
6087
/// `YamlConfig` is a struct that represents some of the fields in the datadog.yaml file.
6188
///
6289
/// It is used to deserialize the datadog.yaml file into a struct that can be merged with the Config struct.
@@ -65,6 +92,7 @@ pub struct YamlLogsConfig {
6592
#[allow(clippy::module_name_repetitions)]
6693
pub struct YamlConfig {
6794
pub logs_config: YamlLogsConfig,
95+
pub apm_config: YamlApmConfig,
6896
pub proxy: YamlProxyConfig,
6997
}
7098

@@ -114,6 +142,10 @@ pub struct Config {
114142
pub trace_propagation_extract_first: bool,
115143
pub trace_propagation_http_baggage_enabled: bool,
116144
pub apm_config_apm_dd_url: String,
145+
#[serde(deserialize_with = "deserialize_apm_replace_rules")]
146+
pub apm_config_replace_tags: Option<Vec<ReplaceRule>>,
147+
pub apm_config_obfuscation_http_remove_query_string: bool,
148+
pub apm_config_obfuscation_http_remove_paths_with_digits: bool,
117149
// Metrics overrides
118150
pub dd_url: String,
119151
pub url: String,
@@ -156,6 +188,9 @@ impl Default for Config {
156188
trace_propagation_extract_first: false,
157189
trace_propagation_http_baggage_enabled: false,
158190
apm_config_apm_dd_url: String::default(),
191+
apm_config_replace_tags: None,
192+
apm_config_obfuscation_http_remove_query_string: false,
193+
apm_config_obfuscation_http_remove_paths_with_digits: false,
159194
dd_url: String::default(),
160195
url: String::default(),
161196
}
@@ -311,6 +346,26 @@ pub fn get_config(config_directory: &Path, region: &str) -> Result<Config, Confi
311346
trace_intake_url_prefixed(config.apm_config_apm_dd_url.as_str());
312347
}
313348

349+
if config.apm_config_replace_tags.is_none() {
350+
if let Some(rules) = yaml_config.apm_config.replace_tags {
351+
config.apm_config_replace_tags = Some(rules);
352+
}
353+
}
354+
355+
if !config.apm_config_obfuscation_http_remove_paths_with_digits {
356+
if let Some(obfuscation) = yaml_config.apm_config.obfuscation {
357+
config.apm_config_obfuscation_http_remove_paths_with_digits =
358+
obfuscation.http.remove_paths_with_digits;
359+
}
360+
}
361+
362+
if !config.apm_config_obfuscation_http_remove_query_string {
363+
if let Some(obfuscation) = yaml_config.apm_config.obfuscation {
364+
config.apm_config_obfuscation_http_remove_query_string =
365+
obfuscation.http.remove_query_string;
366+
}
367+
}
368+
314369
// Metrics are handled by dogstatsd in Main
315370
Ok(config)
316371
}
@@ -363,6 +418,8 @@ pub fn get_aws_partition_by_region(region: &str) -> String {
363418

364419
#[cfg(test)]
365420
pub mod tests {
421+
use datadog_trace_obfuscation::replacer::parse_rules_from_string;
422+
366423
use super::*;
367424

368425
use crate::config::flush_strategy::PeriodicStrategy;
@@ -825,6 +882,54 @@ pub mod tests {
825882
});
826883
}
827884

885+
#[test]
886+
fn test_parse_apm_replace_tags_from_yaml() {
887+
figment::Jail::expect_with(|jail| {
888+
jail.clear_env();
889+
jail.create_file(
890+
"datadog.yaml",
891+
r"
892+
site: datadoghq.com
893+
apm_config:
894+
replace_tags:
895+
- name: '*'
896+
pattern: 'foo'
897+
repl: 'REDACTED'
898+
",
899+
)?;
900+
let config = get_config(Path::new(""), MOCK_REGION).expect("should parse config");
901+
let rule = parse_rules_from_string(
902+
r#"[
903+
{"name": "*", "pattern": "foo", "repl": "REDACTED"}
904+
]"#,
905+
)
906+
.expect("can't parse rules");
907+
assert_eq!(config.apm_config_replace_tags, Some(rule),);
908+
Ok(())
909+
});
910+
}
911+
912+
#[test]
913+
fn test_parse_apm_http_obfuscation_from_yaml() {
914+
figment::Jail::expect_with(|jail| {
915+
jail.clear_env();
916+
jail.create_file(
917+
"datadog.yaml",
918+
r"
919+
site: datadoghq.com
920+
apm_config:
921+
obfuscation:
922+
http:
923+
remove_query_string: true
924+
remove_paths_with_digits: true
925+
",
926+
)?;
927+
let config = get_config(Path::new(""), MOCK_REGION).expect("should parse config");
928+
assert!(config.apm_config_obfuscation_http_remove_query_string,);
929+
assert!(config.apm_config_obfuscation_http_remove_paths_with_digits,);
930+
Ok(())
931+
});
932+
}
828933
#[test]
829934
fn test_parse_trace_propagation_style() {
830935
figment::Jail::expect_with(|jail| {

0 commit comments

Comments
 (0)