diff --git a/src/instana/options.py b/src/instana/options.py index d31bc456..a5651db1 100644 --- a/src/instana/options.py +++ b/src/instana/options.py @@ -16,7 +16,7 @@ import logging import os -from typing import Any, Dict, Sequence +from typing import Any, Dict, Sequence, Tuple from instana.configurator import config from instana.log import logger @@ -25,10 +25,14 @@ get_disable_trace_configurations_from_env, get_disable_trace_configurations_from_local, get_disable_trace_configurations_from_yaml, + get_stack_trace_config_from_yaml, is_truthy, parse_ignored_endpoints, parse_ignored_endpoints_from_yaml, parse_span_disabling, + parse_technology_stack_trace_config, + validate_stack_trace_length, + validate_stack_trace_level, ) from instana.util.runtime import determine_service_name @@ -54,6 +58,10 @@ def __init__(self, **kwds: Dict[str, Any]) -> None: self.stack_trace_level = "all" # Options: "all", "error", "none" self.stack_trace_length = 30 # Default: 30, recommended range: 10-40 + # Technology-specific stack trace overrides + # Format: {"kafka": {"level": "all", "length": 25}, "redis": {"level": "error", "length": 20}} + self.stack_trace_technology_config = {} + self.set_trace_configurations() # Defaults @@ -128,31 +136,73 @@ def set_trace_configurations(self) -> None: self.set_disable_trace_configurations() self.set_stack_trace_configurations() - def set_stack_trace_configurations(self) -> None: - # Stack trace level configuration + def _apply_env_stack_trace_config(self) -> None: + """Apply stack trace configuration from environment variables.""" if "INSTANA_STACK_TRACE" in os.environ: - level = os.environ["INSTANA_STACK_TRACE"].lower() - if level in ["all", "error", "none"]: - self.stack_trace_level = level - else: - logger.warning( - f"Invalid INSTANA_STACK_TRACE value: {level}. Must be 'all', 'error', or 'none'. Using default 'all'" - ) + if validated_level := validate_stack_trace_level( + os.environ["INSTANA_STACK_TRACE"], "from INSTANA_STACK_TRACE" + ): + self.stack_trace_level = validated_level - # Stack trace length configuration if "INSTANA_STACK_TRACE_LENGTH" in os.environ: - try: - length = int(os.environ["INSTANA_STACK_TRACE_LENGTH"]) - if length >= 1: - self.stack_trace_length = length - else: - logger.warning( - "INSTANA_STACK_TRACE_LENGTH must be positive. Using default 30" - ) - except ValueError: - logger.warning( - "Invalid INSTANA_STACK_TRACE_LENGTH value. Must be an integer. Using default 30" - ) + if validated_length := validate_stack_trace_length( + os.environ["INSTANA_STACK_TRACE_LENGTH"], "from INSTANA_STACK_TRACE_LENGTH" + ): + self.stack_trace_length = validated_length + + def _apply_yaml_stack_trace_config(self) -> None: + """Apply stack trace configuration from YAML file.""" + yaml_level, yaml_length, yaml_tech_config = get_stack_trace_config_from_yaml() + if "INSTANA_STACK_TRACE" not in os.environ: + self.stack_trace_level = yaml_level + if "INSTANA_STACK_TRACE_LENGTH" not in os.environ: + self.stack_trace_length = yaml_length + self.stack_trace_technology_config.update(yaml_tech_config) + + def _apply_in_code_stack_trace_config(self) -> None: + """Apply stack trace configuration from in-code config.""" + if not isinstance(config.get("tracing"), dict) or "global" not in config["tracing"]: + return + + global_config = config["tracing"]["global"] + + if "INSTANA_STACK_TRACE" not in os.environ and "stack_trace" in global_config: + if validated_level := validate_stack_trace_level(global_config["stack_trace"], "from in-code config"): + self.stack_trace_level = validated_level + + if "INSTANA_STACK_TRACE_LENGTH" not in os.environ and "stack_trace_length" in global_config: + if validated_length := validate_stack_trace_length(global_config["stack_trace_length"], "from in-code config"): + self.stack_trace_length = validated_length + + # Technology-specific overrides from in-code config + for tech_name, tech_data in config["tracing"].items(): + if tech_name == "global" or not isinstance(tech_data, dict): + continue + + tech_stack_config = parse_technology_stack_trace_config( + tech_data, + level_key="stack_trace", + length_key="stack_trace_length", + tech_name=tech_name + ) + + if tech_stack_config: + self.stack_trace_technology_config[tech_name] = tech_stack_config + + def set_stack_trace_configurations(self) -> None: + """ + Set stack trace configurations following precedence: + environment variables > INSTANA_CONFIG_PATH > in-code config > agent config > defaults + """ + # 1. Environment variables (highest priority) + self._apply_env_stack_trace_config() + + # 2. INSTANA_CONFIG_PATH (YAML file) - includes tech-specific overrides + if "INSTANA_CONFIG_PATH" in os.environ: + self._apply_yaml_stack_trace_config() + # 3. In-code (local) configuration - includes tech-specific overrides + elif isinstance(config.get("tracing"), dict): + self._apply_in_code_stack_trace_config() def set_disable_trace_configurations(self) -> None: disabled_spans = [] @@ -207,6 +257,35 @@ def is_span_disabled(self, category=None, span_type=None) -> bool: # Default: not disabled return False + def get_stack_trace_config(self, span_name: str) -> Tuple[str, int]: + """ + Get stack trace configuration for a specific span type. + Technology-specific configuration overrides global configuration. + + Args: + span_name: The name of the span (e.g., "kafka-producer", "redis", "mysql") + + Returns: + Tuple of (level, length) where: + - level: "all", "error", or "none" + - length: positive integer (1-40) + """ + # Start with global defaults + level = self.stack_trace_level + length = self.stack_trace_length + + # Check for technology-specific overrides + # Extract base technology name from span name + # Examples: "kafka-producer" -> "kafka", "mysql" -> "mysql" + tech_name = span_name.split("-")[0] if "-" in span_name else span_name + + if tech_name in self.stack_trace_technology_config: + tech_config = self.stack_trace_technology_config[tech_name] + level = tech_config.get("level", level) + length = tech_config.get("length", length) + + return level, length + class StandardOptions(BaseOptions): """The options class used when running directly on a host/node with an Instana agent""" @@ -282,6 +361,65 @@ def set_tracing(self, tracing: Dict[str, Any]) -> None: # Handle span disabling configuration if "disable" in tracing: self.set_disable_tracing(tracing["disable"]) + + # Handle stack trace configuration from agent config + self.set_stack_trace_from_agent(tracing) + + def _should_apply_agent_global_config(self) -> bool: + """Check if agent global config should be applied (lowest priority).""" + has_env_vars = ( + "INSTANA_STACK_TRACE" in os.environ + or "INSTANA_STACK_TRACE_LENGTH" in os.environ + ) + has_yaml_config = "INSTANA_CONFIG_PATH" in os.environ + has_in_code_config = ( + isinstance(config.get("tracing"), dict) + and "global" in config["tracing"] + and ("stack_trace" in config["tracing"]["global"] + or "stack_trace_length" in config["tracing"]["global"]) + ) + return not (has_env_vars or has_yaml_config or has_in_code_config) + + def _apply_agent_global_stack_trace_config(self, global_config: Dict[str, Any]) -> None: + """Apply global stack trace configuration from agent config.""" + if "stack-trace" in global_config: + if validated_level := validate_stack_trace_level(global_config["stack-trace"], "in agent config"): + self.stack_trace_level = validated_level + + if "stack-trace-length" in global_config: + if validated_length := validate_stack_trace_length(global_config["stack-trace-length"], "in agent config"): + self.stack_trace_length = validated_length + + def _apply_agent_tech_stack_trace_config(self, tracing: Dict[str, Any]) -> None: + """Apply technology-specific stack trace configuration from agent config.""" + for tech_name, tech_config in tracing.items(): + if tech_name == "global" or not isinstance(tech_config, dict): + continue + + tech_stack_config = parse_technology_stack_trace_config( + tech_config, + level_key="stack-trace", + length_key="stack-trace-length", + tech_name=tech_name + ) + + if tech_stack_config: + self.stack_trace_technology_config[tech_name] = tech_stack_config + + def set_stack_trace_from_agent(self, tracing: Dict[str, Any]) -> None: + """ + Set stack trace configuration from agent config (configuration.yaml). + Only applies if not already set by higher priority sources. + + @param tracing: tracing configuration dictionary from agent + """ + # Apply global config if no higher priority source exists + if self._should_apply_agent_global_config() and "global" in tracing: + self._apply_agent_global_stack_trace_config(tracing["global"]) + + # Apply technology-specific config if not already set by YAML or in-code config + if not self.stack_trace_technology_config: + self._apply_agent_tech_stack_trace_config(tracing) def set_disable_tracing(self, tracing_config: Sequence[Dict[str, Any]]) -> None: # The precedence is as follows: diff --git a/src/instana/span/stack_trace.py b/src/instana/span/stack_trace.py index c3016fbf..ab2a0c59 100644 --- a/src/instana/span/stack_trace.py +++ b/src/instana/span/stack_trace.py @@ -136,21 +136,23 @@ def add_stack_trace_if_needed(span: "InstanaSpan") -> None: Add stack trace to span based on configuration before span ends. This function checks if the span is an EXIT span and if so, captures - a stack trace based on the configured level and limit. + a stack trace based on the configured level and limit. It supports + technology-specific configuration overrides via get_stack_trace_config(). Args: span: The InstanaSpan to potentially add stack trace to """ if span.name in EXIT_SPANS: - # Get configuration from agent options + # Get configuration from agent options (with technology-specific overrides) options = span._span_processor.agent.options + level, limit = options.get_stack_trace_config(span.name) # Check if span is errored is_errored = span.attributes.get("ec", 0) > 0 # Capture stack trace using add_stack function span.stack = add_stack( - level=options.stack_trace_level, - limit=options.stack_trace_length, + level=level, + limit=limit, is_errored=is_errored ) diff --git a/src/instana/util/config.py b/src/instana/util/config.py index 6cc1e109..a4bbe7a6 100644 --- a/src/instana/util/config.py +++ b/src/instana/util/config.py @@ -2,12 +2,17 @@ import itertools import os -from typing import Any, Dict, List, Sequence, Tuple, Union +from typing import Any, Dict, List, Sequence, Tuple, Union, Optional from instana.configurator import config from instana.log import logger from instana.util.config_reader import ConfigReader +# Constants +DEPRECATED_CONFIG_KEY_WARNING = ( + 'Please use "tracing" instead of "com.instana.tracing" for local configuration file.' +) + # List of supported span categories (technology or protocol) SPAN_CATEGORIES = [ "logging", @@ -167,9 +172,7 @@ def parse_ignored_endpoints_from_yaml(file_path: str) -> List[str]: if "tracing" in config_reader.data: ignore_endpoints_dict = config_reader.data["tracing"].get("ignore-endpoints") elif "com.instana.tracing" in config_reader.data: - logger.warning( - 'Please use "tracing" instead of "com.instana.tracing" for local configuration file.' - ) + logger.warning(DEPRECATED_CONFIG_KEY_WARNING) ignore_endpoints_dict = config_reader.data["com.instana.tracing"].get( "ignore-endpoints" ) @@ -295,21 +298,35 @@ def get_disable_trace_configurations_from_env() -> Tuple[List[str], List[str]]: return [], [] +def get_tracing_root_key(config_data: Dict[str, Any]) -> Optional[str]: + """ + Get the root key for tracing configuration from config data. + Handles both 'tracing' and deprecated 'com.instana.tracing' keys. + + Args: + config_data: Configuration data dictionary + + Returns: + Root key string or None if not found + """ + if "tracing" in config_data: + return "tracing" + elif "com.instana.tracing" in config_data: + logger.warning(DEPRECATED_CONFIG_KEY_WARNING) + return "com.instana.tracing" + return None + + def get_disable_trace_configurations_from_yaml() -> Tuple[List[str], List[str]]: config_reader = ConfigReader(os.environ.get("INSTANA_CONFIG_PATH", "")) - - if "tracing" in config_reader.data: - root_key = "tracing" - elif "com.instana.tracing" in config_reader.data: - logger.warning( - 'Please use "tracing" instead of "com.instana.tracing" for local configuration file.' - ) - root_key = "com.instana.tracing" - else: + + root_key = get_tracing_root_key(config_reader.data) + if not root_key: return [], [] - tracing_disable_config = config_reader.data[root_key].get("disable", "") - return parse_span_disabling(tracing_disable_config) + if tracing_disable_config := config_reader.data[root_key].get("disable", None): + return parse_span_disabling(tracing_disable_config) + return [], [] def get_disable_trace_configurations_from_local() -> Tuple[List[str], List[str]]: @@ -319,4 +336,174 @@ def get_disable_trace_configurations_from_local() -> Tuple[List[str], List[str]] return [], [] +def validate_stack_trace_level(level_value: Any, context: str = "") -> Optional[str]: + """ + Validate stack trace level value. + + Args: + level_value: The level value to validate + context: Context string for error messages (e.g., "for kafka", "in agent config") + + Returns: + Validated level string ("all", "error", or "none"), or None if invalid + """ + level = str(level_value).lower() + if level in ["all", "error", "none"]: + return level + + context_msg = f" {context}" if context else "" + logger.warning( + f"Invalid stack-trace value{context_msg}: {level}. Must be 'all', 'error', or 'none'. Using default 'all'." + ) + return None + + +def validate_stack_trace_length(length_value: Any, context: str = "") -> Optional[int]: + """ + Validate stack trace length value. + + Args: + length_value: The length value to validate + context: Context string for error messages (e.g., "for kafka", "in agent config") + + Returns: + Validated length integer (>= 1), or None if invalid + """ + try: + length = int(length_value) + if length >= 1: + return length + + context_msg = f" {context}" if context else "" + logger.warning( + f"stack-trace-length{context_msg} must be positive. Using default 30." + ) + return None + except (ValueError, TypeError): + context_msg = f" {context}" if context else "" + logger.warning( + f"Invalid stack-trace-length{context_msg}. Must be an integer. Using default 30." + ) + return None + + +def parse_technology_stack_trace_config( + tech_data: Dict[str, Any], + level_key: str = "stack-trace", + length_key: str = "stack-trace-length", + tech_name: str = "", +) -> Dict[str, Union[str, int]]: + """ + Parse technology-specific stack trace configuration from a dictionary. + + Args: + tech_data: Dictionary containing stack trace configuration + level_key: Key name for level configuration (e.g., "stack-trace" or "stack_trace") + length_key: Key name for length configuration (e.g., "stack-trace-length" or "stack_trace_length") + tech_name: Technology name for error messages (e.g., "kafka", "redis") + + Returns: + Dictionary with "level" and/or "length" keys, or empty dict if no valid config + """ + tech_stack_config = {} + context = f"for {tech_name}" if tech_name else "" + + if level_key in tech_data: + if validated_level := validate_stack_trace_level(tech_data[level_key], context): + tech_stack_config["level"] = validated_level + + if length_key in tech_data: + if validated_length := validate_stack_trace_length(tech_data[length_key], context): + tech_stack_config["length"] = validated_length + + return tech_stack_config + + +def parse_global_stack_trace_config(global_config: Dict[str, Any]) -> Tuple[str, int]: + """ + Parse global stack trace configuration from a config dictionary. + + Args: + global_config: Global configuration dictionary + + Returns: + Tuple of (level, length) with defaults if not found + """ + level = "all" + length = 30 + + if "stack-trace" in global_config: + if validated_level := validate_stack_trace_level(global_config["stack-trace"], "in YAML config"): + level = validated_level + + if "stack-trace-length" in global_config: + if validated_length := validate_stack_trace_length(global_config["stack-trace-length"], "in YAML config"): + length = validated_length + + return level, length + + +def parse_tech_specific_stack_trace_configs( + tracing_data: Dict[str, Any] +) -> Dict[str, Dict[str, Union[str, int]]]: + """ + Parse technology-specific stack trace configurations from tracing data. + + Args: + tracing_data: Tracing configuration dictionary + + Returns: + Dictionary of technology-specific overrides + """ + tech_config = {} + + for tech_name, tech_data in tracing_data.items(): + if tech_name == "global" or not isinstance(tech_data, dict): + continue + + tech_stack_config = parse_technology_stack_trace_config( + tech_data, + level_key="stack-trace", + length_key="stack-trace-length", + tech_name=tech_name + ) + + if tech_stack_config: + tech_config[tech_name] = tech_stack_config + + return tech_config + + +def get_stack_trace_config_from_yaml() -> Tuple[str, int, Dict[str, Dict[str, Union[str, int]]]]: + """ + Get stack trace configuration from YAML file specified by INSTANA_CONFIG_PATH. + + Returns: + Tuple of (level, length, tech_config) where: + - level: "all", "error", or "none" + - length: positive integer + - tech_config: Dict of technology-specific overrides + Format: {"kafka": {"level": "all", "length": 35}, "redis": {"level": "none"}} + """ + config_reader = ConfigReader(os.environ.get("INSTANA_CONFIG_PATH", "")) + + level = "all" + length = 30 + tech_config = {} + + root_key = get_tracing_root_key(config_reader.data) + if not root_key: + return level, length, tech_config + + tracing_data = config_reader.data[root_key] + + # Read global configuration + if "global" in tracing_data: + level, length = parse_global_stack_trace_config(tracing_data["global"]) + + # Read technology-specific overrides + tech_config = parse_tech_specific_stack_trace_configs(tracing_data) + + return level, length, tech_config + # Made with Bob diff --git a/tests/test_options.py b/tests/test_options.py index a532787b..37c667f8 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -653,6 +653,7 @@ def test_stack_trace_defaults(self) -> None: assert self.options.stack_trace_level == "all" assert self.options.stack_trace_length == 30 + assert self.options.stack_trace_technology_config == {} @pytest.mark.parametrize( "level_value,expected_level", @@ -684,7 +685,7 @@ def test_stack_trace_level_env_var_invalid( self.options = BaseOptions() assert self.options.stack_trace_level == "all" # Falls back to default assert any( - "Invalid INSTANA_STACK_TRACE value" in message + "Invalid stack-trace value from INSTANA_STACK_TRACE" in message for message in caplog.messages ) @@ -692,7 +693,7 @@ def test_stack_trace_level_env_var_invalid( "length_value,expected_length", [ ("25", 25), - ("60", 60), # Not capped here, capped when _add_stack() is called + ("60", 60), # Not capped here, capped when add_stack() is called ], ) def test_stack_trace_length_env_var( @@ -711,7 +712,7 @@ def test_stack_trace_length_env_var( [ ("0", "must be positive"), ("-5", "must be positive"), - ("invalid", "Invalid INSTANA_STACK_TRACE_LENGTH"), + ("invalid", "Invalid stack-trace-length from INSTANA_STACK_TRACE_LENGTH"), ], ) def test_stack_trace_length_env_var_invalid( @@ -739,3 +740,253 @@ def test_stack_trace_both_env_vars(self) -> None: self.options = BaseOptions() assert self.options.stack_trace_level == "error" assert self.options.stack_trace_length == 15 + + def test_stack_trace_in_code_config(self) -> None: + """Test in-code configuration for stack trace.""" + config["tracing"] = { + "global": { + "stack_trace": "error", + "stack_trace_length": 20 + } + } + self.options = BaseOptions() + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 20 + + def test_stack_trace_agent_config(self) -> None: + """Test agent configuration for stack trace.""" + self.options = StandardOptions() + + test_tracing = { + "global": { + "stack-trace": "error", + "stack-trace-length": 15 + } + } + self.options.set_tracing(test_tracing) + + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 15 + + def test_stack_trace_precedence_env_over_in_code(self) -> None: + """Test environment variables take precedence over in-code config.""" + config["tracing"] = { + "global": { + "stack_trace": "all", + "stack_trace_length": 10 + } + } + + with patch.dict( + os.environ, + { + "INSTANA_STACK_TRACE": "error", + "INSTANA_STACK_TRACE_LENGTH": "25", + }, + ): + self.options = BaseOptions() + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 25 + + def test_stack_trace_precedence_in_code_over_agent(self) -> None: + """Test in-code config takes precedence over agent config.""" + config["tracing"] = { + "global": { + "stack_trace": "error", + "stack_trace_length": 20 + } + } + + self.options = StandardOptions() + + test_tracing = { + "global": { + "stack-trace": "all", + "stack-trace-length": 10 + } + } + self.options.set_tracing(test_tracing) + + # In-code config should win + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 20 + + def test_stack_trace_technology_specific_override(self) -> None: + """Test technology-specific stack trace configuration.""" + self.options = StandardOptions() + + test_tracing = { + "global": { + "stack-trace": "error", + "stack-trace-length": 25 + }, + "kafka": { + "stack-trace": "all", + "stack-trace-length": 35 + }, + "redis": { + "stack-trace": "none" + } + } + self.options.set_tracing(test_tracing) + + # Global config + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 25 + + # Kafka-specific override + level, length = self.options.get_stack_trace_config("kafka-producer") + assert level == "all" + assert length == 35 + + # Redis-specific override (inherits length from global) + level, length = self.options.get_stack_trace_config("redis") + assert level == "none" + assert length == 25 + + # Non-overridden span uses global + level, length = self.options.get_stack_trace_config("mysql") + assert level == "error" + assert length == 25 + + def test_get_stack_trace_config_with_hyphenated_span_name(self) -> None: + """Test get_stack_trace_config extracts technology name correctly.""" + self.options = StandardOptions() + self.options.stack_trace_technology_config = { + "kafka": {"level": "all", "length": 35} + } + + # Should match "kafka" from "kafka-producer" + level, length = self.options.get_stack_trace_config("kafka-producer") + assert level == "all" + assert length == 35 + + # Should match "kafka" from "kafka-consumer" + level, length = self.options.get_stack_trace_config("kafka-consumer") + assert level == "all" + assert length == 35 + + def test_stack_trace_yaml_config_basic(self) -> None: + """Test YAML configuration for stack trace (basic format).""" + with patch.dict( + os.environ, + {"INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_1.yaml"}, + ): + self.options = BaseOptions() + assert self.options.stack_trace_level == "all" + assert self.options.stack_trace_length == 15 + + def test_stack_trace_yaml_config_with_prefix( + self, + caplog: pytest.LogCaptureFixture, + ) -> None: + """Test YAML configuration with com.instana prefix.""" + caplog.set_level(logging.WARNING, logger="instana") + with patch.dict( + os.environ, + {"INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_2.yaml"}, + ): + self.options = BaseOptions() + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 20 + + assert ( + 'Please use "tracing" instead of "com.instana.tracing" for local configuration file.' + in caplog.messages + ) + + def test_stack_trace_yaml_config_disabled(self) -> None: + """Test YAML configuration with stack trace disabled.""" + with patch.dict( + os.environ, + {"INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_3.yaml"}, + ): + self.options = BaseOptions() + assert self.options.stack_trace_level == "none" + assert self.options.stack_trace_length == 5 + + def test_stack_trace_yaml_config_invalid( + self, + caplog: pytest.LogCaptureFixture, + ) -> None: + """Test YAML configuration with invalid values.""" + caplog.set_level(logging.WARNING, logger="instana") + with patch.dict( + os.environ, + {"INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_4.yaml"}, + ): + self.options = BaseOptions() + # Should fall back to defaults + assert self.options.stack_trace_level == "all" + assert self.options.stack_trace_length == 30 + assert any( + "Invalid stack-trace value" in message + for message in caplog.messages + ) + assert any( + "must be positive" in message + for message in caplog.messages + ) + + def test_stack_trace_yaml_config_partial(self) -> None: + """Test YAML configuration with only stack-trace (no length).""" + with patch.dict( + os.environ, + {"INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_5.yaml"}, + ): + self.options = BaseOptions() + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 30 # Default + + def test_stack_trace_precedence_env_over_yaml(self) -> None: + """Test environment variables take precedence over YAML config.""" + with patch.dict( + os.environ, + { + "INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_1.yaml", + "INSTANA_STACK_TRACE": "error", + "INSTANA_STACK_TRACE_LENGTH": "25", + }, + ): + self.options = BaseOptions() + # Env vars should override YAML + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 25 + + def test_stack_trace_precedence_yaml_over_in_code(self) -> None: + """Test YAML config takes precedence over in-code config.""" + config["tracing"] = { + "global": { + "stack_trace": "error", + "stack_trace_length": 10 + } + } + + with patch.dict( + os.environ, + {"INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_1.yaml"}, + ): + self.options = BaseOptions() + # YAML should override in-code config + assert self.options.stack_trace_level == "all" + assert self.options.stack_trace_length == 15 + + def test_stack_trace_precedence_yaml_over_agent(self) -> None: + """Test YAML config takes precedence over agent config.""" + with patch.dict( + os.environ, + {"INSTANA_CONFIG_PATH": "tests/util/test_stack_trace_config_2.yaml"}, + ): + self.options = StandardOptions() + + test_tracing = { + "global": { + "stack-trace": "all", + "stack-trace-length": 30 + } + } + self.options.set_tracing(test_tracing) + + # YAML should override agent config + assert self.options.stack_trace_level == "error" + assert self.options.stack_trace_length == 20 diff --git a/tests/util/test_stack_trace_config_1.yaml b/tests/util/test_stack_trace_config_1.yaml new file mode 100644 index 00000000..4c87ee09 --- /dev/null +++ b/tests/util/test_stack_trace_config_1.yaml @@ -0,0 +1,7 @@ +# (c) Copyright IBM Corp. 2025 + +# Test configuration file for stack trace - basic global configuration +tracing: + global: + stack-trace: all + stack-trace-length: 15 diff --git a/tests/util/test_stack_trace_config_2.yaml b/tests/util/test_stack_trace_config_2.yaml new file mode 100644 index 00000000..34fa7c1c --- /dev/null +++ b/tests/util/test_stack_trace_config_2.yaml @@ -0,0 +1,7 @@ +# (c) Copyright IBM Corp. 2025 + +# Test configuration file for stack trace - with com.instana prefix +com.instana.tracing: + global: + stack-trace: error + stack-trace-length: 20 diff --git a/tests/util/test_stack_trace_config_3.yaml b/tests/util/test_stack_trace_config_3.yaml new file mode 100644 index 00000000..5ac971f5 --- /dev/null +++ b/tests/util/test_stack_trace_config_3.yaml @@ -0,0 +1,7 @@ +# (c) Copyright IBM Corp. 2025 + +# Test configuration file for stack trace - disabled configuration +tracing: + global: + stack-trace: none + stack-trace-length: 5 diff --git a/tests/util/test_stack_trace_config_4.yaml b/tests/util/test_stack_trace_config_4.yaml new file mode 100644 index 00000000..a1cf1d6b --- /dev/null +++ b/tests/util/test_stack_trace_config_4.yaml @@ -0,0 +1,7 @@ +# (c) Copyright IBM Corp. 2025 + +# Test configuration file for stack trace - invalid values for testing validation +tracing: + global: + stack-trace: invalid-value + stack-trace-length: -10 diff --git a/tests/util/test_stack_trace_config_5.yaml b/tests/util/test_stack_trace_config_5.yaml new file mode 100644 index 00000000..0527feb3 --- /dev/null +++ b/tests/util/test_stack_trace_config_5.yaml @@ -0,0 +1,6 @@ +# (c) Copyright IBM Corp. 2025 + +# Test configuration file for stack trace - only stack-trace without length +tracing: + global: + stack-trace: error