Skip to content

Commit 38e8e60

Browse files
authored
Ignoring Unwanted Resources in APM (#794)
## Task https://datadoghq.atlassian.net/browse/SVLS-6846 ## Overview We want to allow users to set filter tags which drops traces with root spans that match specified span tags. Specifically, users can set `DD_APM_FILTER_TAGS_REQUIRE` or `DD_APM_FILTER_TAGS_REJECT`. More info [here](https://docs.datadoghq.com/tracing/guide/ignoring_apm_resources/?tab=datadogyaml#trace-agent-configuration-options). ## Testing Deployed changes to Lambda. Invoked Lambda directly and through API Gateway to check with different root spans. Set the tags to either be REQUIRE or REJECT with value `name:aws.lambda`. Confirmed in logs and UI that we were dropping spans.
1 parent 81ca772 commit 38e8e60

File tree

4 files changed

+697
-3
lines changed

4 files changed

+697
-3
lines changed

bottlecap/src/config/env.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use crate::{
1010
Config, ConfigError, ConfigSource,
1111
additional_endpoints::deserialize_additional_endpoints,
1212
apm_replace_rule::deserialize_apm_replace_rules,
13-
deserialize_array_from_comma_separated_string, deserialize_key_value_pairs,
14-
deserialize_optional_bool_from_anything, deserialize_optional_duration_from_microseconds,
13+
deserialize_apm_filter_tags, deserialize_array_from_comma_separated_string,
14+
deserialize_key_value_pairs, deserialize_optional_bool_from_anything,
15+
deserialize_optional_duration_from_microseconds,
1516
deserialize_optional_duration_from_seconds, deserialize_string_or_int,
1617
flush_strategy::FlushStrategy,
1718
log_level::LogLevel,
@@ -180,6 +181,34 @@ pub struct EnvConfig {
180181
/// <https://docs.datadoghq.com/agent/configuration/dual-shipping/?tab=helm#environment-variable-configuration-1>
181182
#[serde(deserialize_with = "deserialize_additional_endpoints")]
182183
pub apm_additional_endpoints: HashMap<String, Vec<String>>,
184+
/// @env `DD_APM_FILTER_TAGS_REQUIRE`
185+
///
186+
/// Space-separated list of key:value tag pairs that spans must match to be kept.
187+
/// Only spans matching at least one of these tags will be sent to Datadog.
188+
/// Example: "env:production service:api-gateway"
189+
#[serde(deserialize_with = "deserialize_apm_filter_tags")]
190+
pub apm_filter_tags_require: Option<Vec<String>>,
191+
/// @env `DD_APM_FILTER_TAGS_REJECT`
192+
///
193+
/// Space-separated list of key:value tag pairs that will cause spans to be filtered out.
194+
/// Spans matching any of these tags will be dropped.
195+
/// Example: "env:development debug:true name:health.check"
196+
#[serde(deserialize_with = "deserialize_apm_filter_tags")]
197+
pub apm_filter_tags_reject: Option<Vec<String>>,
198+
/// @env `DD_APM_FILTER_TAGS_REGEX_REQUIRE`
199+
///
200+
/// Space-separated list of key:value tag pairs with regex values that spans must match to be kept.
201+
/// Only spans matching at least one of these regex patterns will be sent to Datadog.
202+
/// Example: "env:^prod.*$ service:^api-.*$"
203+
#[serde(deserialize_with = "deserialize_apm_filter_tags")]
204+
pub apm_filter_tags_regex_require: Option<Vec<String>>,
205+
/// @env `DD_APM_FILTER_TAGS_REGEX_REJECT`
206+
///
207+
/// Space-separated list of key:value tag pairs with regex values that will cause spans to be filtered out.
208+
/// Spans matching any of these regex patterns will be dropped.
209+
/// Example: "env:^test.*$ debug:^true$"
210+
#[serde(deserialize_with = "deserialize_apm_filter_tags")]
211+
pub apm_filter_tags_regex_reject: Option<Vec<String>>,
183212
/// @env `DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED`
184213
///
185214
/// Enable the new AWS-resource naming logic in the tracer.
@@ -388,6 +417,10 @@ fn merge_config(config: &mut Config, env_config: &EnvConfig) {
388417
merge_option_to_value!(config, env_config, apm_config_compression_level);
389418
merge_vec!(config, env_config, apm_features);
390419
merge_hashmap!(config, env_config, apm_additional_endpoints);
420+
merge_option!(config, env_config, apm_filter_tags_require);
421+
merge_option!(config, env_config, apm_filter_tags_reject);
422+
merge_option!(config, env_config, apm_filter_tags_regex_require);
423+
merge_option!(config, env_config, apm_filter_tags_regex_reject);
391424
merge_option_to_value!(config, env_config, trace_aws_service_representation_enabled);
392425

393426
// Trace Propagation
@@ -588,6 +621,16 @@ mod tests {
588621
"enable_otlp_compute_top_level_by_span_kind,enable_stats_by_span_kind",
589622
);
590623
jail.set_env("DD_APM_ADDITIONAL_ENDPOINTS", "{\"https://trace.agent.datadoghq.com\": [\"apikey2\", \"apikey3\"], \"https://trace.agent.datadoghq.eu\": [\"apikey4\"]}");
624+
jail.set_env("DD_APM_FILTER_TAGS_REQUIRE", "env:production service:api");
625+
jail.set_env("DD_APM_FILTER_TAGS_REJECT", "debug:true env:test");
626+
jail.set_env(
627+
"DD_APM_FILTER_TAGS_REGEX_REQUIRE",
628+
"env:^test.*$ debug:^true$",
629+
);
630+
jail.set_env(
631+
"DD_APM_FILTER_TAGS_REGEX_REJECT",
632+
"env:^test.*$ debug:^true$",
633+
);
591634

592635
// Trace Propagation
593636
jail.set_env("DD_TRACE_PROPAGATION_STYLE", "datadog");
@@ -744,6 +787,22 @@ mod tests {
744787
vec!["apikey4".to_string()],
745788
),
746789
]),
790+
apm_filter_tags_require: Some(vec![
791+
"env:production".to_string(),
792+
"service:api".to_string(),
793+
]),
794+
apm_filter_tags_reject: Some(vec![
795+
"debug:true".to_string(),
796+
"env:test".to_string(),
797+
]),
798+
apm_filter_tags_regex_require: Some(vec![
799+
"env:^test.*$".to_string(),
800+
"debug:^true$".to_string(),
801+
]),
802+
apm_filter_tags_regex_reject: Some(vec![
803+
"env:^test.*$".to_string(),
804+
"debug:^true$".to_string(),
805+
]),
747806
trace_propagation_style: vec![TracePropagationStyle::Datadog],
748807
trace_propagation_style_extract: vec![TracePropagationStyle::B3],
749808
trace_propagation_extract_first: true,

bottlecap/src/config/mod.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ pub struct Config {
279279
pub apm_config_compression_level: i32,
280280
pub apm_features: Vec<String>,
281281
pub apm_additional_endpoints: HashMap<String, Vec<String>>,
282+
pub apm_filter_tags_require: Option<Vec<String>>,
283+
pub apm_filter_tags_reject: Option<Vec<String>>,
284+
pub apm_filter_tags_regex_require: Option<Vec<String>>,
285+
pub apm_filter_tags_regex_reject: Option<Vec<String>>,
282286
//
283287
// Trace Propagation
284288
pub trace_propagation_style: Vec<TracePropagationStyle>,
@@ -380,6 +384,10 @@ impl Default for Config {
380384
apm_config_compression_level: 6,
381385
apm_features: vec![],
382386
apm_additional_endpoints: HashMap::new(),
387+
apm_filter_tags_require: None,
388+
apm_filter_tags_reject: None,
389+
apm_filter_tags_regex_require: None,
390+
apm_filter_tags_regex_reject: None,
383391
trace_aws_service_representation_enabled: true,
384392
trace_propagation_style: vec![
385393
TracePropagationStyle::Datadog,
@@ -623,6 +631,53 @@ where
623631
Ok(map)
624632
}
625633

634+
/// Deserialize APM filter tags from space-separated "key:value" pairs, also support key-only tags
635+
pub fn deserialize_apm_filter_tags<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
636+
where
637+
D: Deserializer<'de>,
638+
{
639+
let opt: Option<String> = Option::deserialize(deserializer)?;
640+
641+
match opt {
642+
None => Ok(None),
643+
Some(s) if s.trim().is_empty() => Ok(None),
644+
Some(s) => {
645+
let tags: Vec<String> = s
646+
.split_whitespace()
647+
.filter_map(|pair| {
648+
let parts: Vec<&str> = pair.splitn(2, ':').collect();
649+
if parts.len() == 2 {
650+
let key = parts[0].trim();
651+
let value = parts[1].trim();
652+
if key.is_empty() {
653+
None
654+
} else if value.is_empty() {
655+
Some(key.to_string())
656+
} else {
657+
Some(format!("{key}:{value}"))
658+
}
659+
} else if parts.len() == 1 {
660+
let key = parts[0].trim();
661+
if key.is_empty() {
662+
None
663+
} else {
664+
Some(key.to_string())
665+
}
666+
} else {
667+
None
668+
}
669+
})
670+
.collect();
671+
672+
if tags.is_empty() {
673+
Ok(None)
674+
} else {
675+
Ok(Some(tags))
676+
}
677+
}
678+
}
679+
}
680+
626681
pub fn deserialize_optional_duration_from_microseconds<'de, D: Deserializer<'de>>(
627682
deserializer: D,
628683
) -> Result<Option<Duration>, D::Error> {

bottlecap/src/config/yaml.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,10 @@ extension_version: "compatibility"
915915
api_security_sample_delay: Duration::from_secs(60),
916916

917917
extension_version: Some("compatibility".to_string()),
918+
apm_filter_tags_require: None,
919+
apm_filter_tags_reject: None,
920+
apm_filter_tags_regex_require: None,
921+
apm_filter_tags_regex_reject: None,
918922
};
919923

920924
// Assert that

0 commit comments

Comments
 (0)