diff --git a/src/instana/options.py b/src/instana/options.py index f63ed9c7..480f68fa 100644 --- a/src/instana/options.py +++ b/src/instana/options.py @@ -268,8 +268,11 @@ def set_span_filter_configurations(self) -> None: # The precedence is as follows: # environment variables > in-code configuration > # > agent config (configuration.yaml) > default value - if any(k.startswith("INSTANA_TRACING_FILTER_") for k in os.environ): - # Check for new span filtering env vars + if any( + k.startswith("INSTANA_TRACING_FILTER_") and os.environ[k] + for k in os.environ + ): + # Check for new span filtering env vars (only if at least one has a non-empty value) parsed_filter = parse_filter_rules_env_vars() if parsed_filter["exclude"] or parsed_filter["include"]: self.span_filters = parsed_filter @@ -393,8 +396,14 @@ def set_tracing(self, tracing: Dict[str, Any]) -> None: @param tracing: tracing configuration dictionary @return: None """ - if "filter" in tracing and not self.span_filters: - self.span_filters = parse_filter_rules(tracing["filter"]) + if "filter" in tracing and not self._has_high_priority_span_filter_source(): + parsed = parse_filter_rules(tracing["filter"]) + for policy in ("exclude", "include"): + rules = parsed.get(policy, []) + if rules: + if policy not in self.span_filters: + self.span_filters[policy] = [] + self.span_filters[policy].extend(rules) if "kafka" in tracing: if ( @@ -427,6 +436,21 @@ def set_tracing(self, tracing: Dict[str, Any]) -> None: # Handle stack trace configuration from agent config self.set_stack_trace_from_agent(tracing) + def _has_high_priority_span_filter_source(self) -> bool: + """Return True if a higher-priority span filter source (env var, YAML, or in-code config) + has already been configured, in which case the agent-provided filter should be ignored.""" + return ( + any( + k.startswith("INSTANA_TRACING_FILTER_") and os.environ[k] + for k in os.environ + ) + or "INSTANA_CONFIG_PATH" in os.environ + or ( + isinstance(config.get("tracing"), dict) + and "filter" in config["tracing"] + ) + ) + def _should_apply_agent_global_config(self) -> bool: """Check if agent global config should be applied (lowest priority).""" has_env_vars = ( diff --git a/tests/test_options.py b/tests/test_options.py index d0004e09..3beeecf6 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -689,8 +689,30 @@ def test_set_trace_configurations_by_agent_configuration(self) -> None: self.base_options = StandardOptions() self.base_options.set_tracing(test_tracing) - # set_tracing does not override span_filters when already set (has internal filters) - assert self.base_options.span_filters == {"exclude": INTERNAL_SPAN_FILTERS} + # Agent filter rules are appended after the internal filters (no high-priority source set). + agent_exclude = [ + { + "name": "service1", + "suppression": True, + "attributes": [ + {"key": "service", "values": ["service1"], "match_type": "strict"} + ], + }, + { + "name": "service2", + "suppression": True, + "attributes": [ + { + "key": "method", + "values": ["method1", "method2"], + "match_type": "strict", + } + ], + }, + ] + assert self.base_options.span_filters == { + "exclude": INTERNAL_SPAN_FILTERS + agent_exclude + } assert self.base_options.kafka_trace_correlation # Check disabled_spans list @@ -908,7 +930,28 @@ def test_set_tracing( } self.standart_options.set_tracing(test_tracing) - assert self.standart_options.span_filters == {"exclude": INTERNAL_SPAN_FILTERS} + # Agent filter rules are appended after the internal filters (no high-priority source set). + expected_exclude = INTERNAL_SPAN_FILTERS + [ + { + "name": "service1", + "suppression": True, + "attributes": [ + {"key": "service", "values": ["service1"], "match_type": "strict"} + ], + }, + { + "name": "service2", + "suppression": True, + "attributes": [ + { + "key": "method", + "values": ["method1", "method2"], + "match_type": "strict", + } + ], + }, + ] + assert self.standart_options.span_filters == {"exclude": expected_exclude} assert not self.standart_options.kafka_trace_correlation assert ( "Binary header format for Kafka is deprecated. Please use string header format." @@ -972,7 +1015,30 @@ def test_set_from(self) -> None: self.standart_options.secrets_matcher == test_res_data["secrets"]["matcher"] ) assert self.standart_options.secrets_list == test_res_data["secrets"]["list"] - assert self.standart_options.span_filters == {"exclude": INTERNAL_SPAN_FILTERS} + # Agent filter rules are appended after the internal filters. + agent_exclude = [ + { + "name": "service1", + "suppression": True, + "attributes": [ + {"key": "service", "values": ["service1"], "match_type": "strict"} + ], + }, + { + "name": "service2", + "suppression": True, + "attributes": [ + { + "key": "method", + "values": ["method1", "method2"], + "match_type": "strict", + } + ], + }, + ] + assert self.standart_options.span_filters == { + "exclude": INTERNAL_SPAN_FILTERS + agent_exclude + } test_res_data2 = { "extraHeaders": {"header1": "sample-match", "header2": ["sample", "list"]},