1616
1717import logging
1818import os
19- from typing import Any , Dict , Sequence
19+ from typing import Any , Dict , Sequence , Tuple
2020
2121from instana .configurator import config
2222from instana .log import logger
2525 get_disable_trace_configurations_from_env ,
2626 get_disable_trace_configurations_from_local ,
2727 get_disable_trace_configurations_from_yaml ,
28+ get_stack_trace_config_from_yaml ,
2829 is_truthy ,
2930 parse_ignored_endpoints ,
3031 parse_ignored_endpoints_from_yaml ,
@@ -54,6 +55,10 @@ def __init__(self, **kwds: Dict[str, Any]) -> None:
5455 self .stack_trace_level = "all" # Options: "all", "error", "none"
5556 self .stack_trace_length = 30 # Default: 30, recommended range: 10-40
5657
58+ # Technology-specific stack trace overrides
59+ # Format: {"kafka": {"level": "all", "length": 25}, "redis": {"level": "error", "length": 20}}
60+ self .stack_trace_technology_config = {}
61+
5762 self .set_trace_configurations ()
5863
5964 # Defaults
@@ -129,7 +134,11 @@ def set_trace_configurations(self) -> None:
129134 self .set_stack_trace_configurations ()
130135
131136 def set_stack_trace_configurations (self ) -> None :
132- # Stack trace level configuration
137+ """
138+ Set stack trace configurations following precedence:
139+ environment variables > INSTANA_CONFIG_PATH > in-code config > agent config > defaults
140+ """
141+ # 1. Environment variables (highest priority)
133142 if "INSTANA_STACK_TRACE" in os .environ :
134143 level = os .environ ["INSTANA_STACK_TRACE" ].lower ()
135144 if level in ["all" , "error" , "none" ]:
@@ -139,7 +148,6 @@ def set_stack_trace_configurations(self) -> None:
139148 f"Invalid INSTANA_STACK_TRACE value: { level } . Must be 'all', 'error', or 'none'. Using default 'all'"
140149 )
141150
142- # Stack trace length configuration
143151 if "INSTANA_STACK_TRACE_LENGTH" in os .environ :
144152 try :
145153 length = int (os .environ ["INSTANA_STACK_TRACE_LENGTH" ])
@@ -153,6 +161,76 @@ def set_stack_trace_configurations(self) -> None:
153161 logger .warning (
154162 "Invalid INSTANA_STACK_TRACE_LENGTH value. Must be an integer. Using default 30"
155163 )
164+
165+ # 2. INSTANA_CONFIG_PATH (YAML file) - includes tech-specific overrides
166+ elif "INSTANA_CONFIG_PATH" in os .environ :
167+ yaml_level , yaml_length , yaml_tech_config = get_stack_trace_config_from_yaml ()
168+ if "INSTANA_STACK_TRACE" not in os .environ :
169+ self .stack_trace_level = yaml_level
170+ if "INSTANA_STACK_TRACE_LENGTH" not in os .environ :
171+ self .stack_trace_length = yaml_length
172+ # Technology-specific overrides from YAML
173+ self .stack_trace_technology_config .update (yaml_tech_config )
174+
175+ # 3. In-code (local) configuration - includes tech-specific overrides
176+ elif isinstance (config .get ("tracing" ), dict ) and "global" in config ["tracing" ]:
177+ global_config = config ["tracing" ]["global" ]
178+
179+ if "INSTANA_STACK_TRACE" not in os .environ and "stack_trace" in global_config :
180+ level = str (global_config ["stack_trace" ]).lower ()
181+ if level in ["all" , "error" , "none" ]:
182+ self .stack_trace_level = level
183+ else :
184+ logger .warning (
185+ f"Invalid stack_trace value in config: { level } . Must be 'all', 'error', or 'none'. Using default 'all'"
186+ )
187+
188+ if "INSTANA_STACK_TRACE_LENGTH" not in os .environ and "stack_trace_length" in global_config :
189+ try :
190+ length = int (global_config ["stack_trace_length" ])
191+ if length >= 1 :
192+ self .stack_trace_length = length
193+ else :
194+ logger .warning (
195+ "stack_trace_length must be positive. Using default 30"
196+ )
197+ except (ValueError , TypeError ):
198+ logger .warning (
199+ "Invalid stack_trace_length in config. Must be an integer. Using default 30"
200+ )
201+
202+ # Technology-specific overrides from in-code config
203+ for tech_name , tech_data in config ["tracing" ].items ():
204+ if tech_name == "global" or not isinstance (tech_data , dict ):
205+ continue
206+
207+ tech_stack_config = {}
208+
209+ if "stack_trace" in tech_data :
210+ tech_level = str (tech_data ["stack_trace" ]).lower ()
211+ if tech_level in ["all" , "error" , "none" ]:
212+ tech_stack_config ["level" ] = tech_level
213+ else :
214+ logger .warning (
215+ f"Invalid stack_trace value for { tech_name } : { tech_level } . Ignoring."
216+ )
217+
218+ if "stack_trace_length" in tech_data :
219+ try :
220+ tech_length = int (tech_data ["stack_trace_length" ])
221+ if tech_length >= 1 :
222+ tech_stack_config ["length" ] = tech_length
223+ else :
224+ logger .warning (
225+ f"stack_trace_length for { tech_name } must be positive. Ignoring."
226+ )
227+ except (ValueError , TypeError ):
228+ logger .warning (
229+ f"Invalid stack_trace_length for { tech_name } . Must be an integer. Ignoring."
230+ )
231+
232+ if tech_stack_config :
233+ self .stack_trace_technology_config [tech_name ] = tech_stack_config
156234
157235 def set_disable_trace_configurations (self ) -> None :
158236 disabled_spans = []
@@ -207,6 +285,35 @@ def is_span_disabled(self, category=None, span_type=None) -> bool:
207285 # Default: not disabled
208286 return False
209287
288+ def get_stack_trace_config (self , span_name : str ) -> Tuple [str , int ]:
289+ """
290+ Get stack trace configuration for a specific span type.
291+ Technology-specific configuration overrides global configuration.
292+
293+ Args:
294+ span_name: The name of the span (e.g., "kafka-producer", "redis", "mysql")
295+
296+ Returns:
297+ Tuple of (level, length) where:
298+ - level: "all", "error", or "none"
299+ - length: positive integer (1-40)
300+ """
301+ # Start with global defaults
302+ level = self .stack_trace_level
303+ length = self .stack_trace_length
304+
305+ # Check for technology-specific overrides
306+ # Extract base technology name from span name
307+ # Examples: "kafka-producer" -> "kafka", "mysql" -> "mysql"
308+ tech_name = span_name .split ("-" )[0 ] if "-" in span_name else span_name
309+
310+ if tech_name in self .stack_trace_technology_config :
311+ tech_config = self .stack_trace_technology_config [tech_name ]
312+ level = tech_config .get ("level" , level )
313+ length = tech_config .get ("length" , length )
314+
315+ return level , length
316+
210317
211318class StandardOptions (BaseOptions ):
212319 """The options class used when running directly on a host/node with an Instana agent"""
@@ -282,6 +389,94 @@ def set_tracing(self, tracing: Dict[str, Any]) -> None:
282389 # Handle span disabling configuration
283390 if "disable" in tracing :
284391 self .set_disable_tracing (tracing ["disable" ])
392+
393+ # Handle stack trace configuration from agent config
394+ self .set_stack_trace_from_agent (tracing )
395+
396+ def set_stack_trace_from_agent (self , tracing : Dict [str , Any ]) -> None :
397+ """
398+ Set stack trace configuration from agent config (configuration.yaml).
399+ Only applies if not already set by higher priority sources.
400+
401+ @param tracing: tracing configuration dictionary from agent
402+ """
403+ # Check if we should apply agent config (lowest priority)
404+ should_apply_agent_config = (
405+ "INSTANA_STACK_TRACE" not in os .environ
406+ and "INSTANA_STACK_TRACE_LENGTH" not in os .environ
407+ and "INSTANA_CONFIG_PATH" not in os .environ
408+ and not (
409+ isinstance (config .get ("tracing" ), dict )
410+ and "global" in config ["tracing" ]
411+ and ("stack_trace" in config ["tracing" ]["global" ] or "stack_trace_length" in config ["tracing" ]["global" ])
412+ )
413+ )
414+
415+ if should_apply_agent_config and "global" in tracing :
416+ global_config = tracing ["global" ]
417+
418+ # Set stack-trace level from agent config
419+ if "stack-trace" in global_config :
420+ level = str (global_config ["stack-trace" ]).lower ()
421+ if level in ["all" , "error" , "none" ]:
422+ self .stack_trace_level = level
423+ else :
424+ logger .warning (
425+ f"Invalid stack-trace value in agent config: { level } . Must be 'all', 'error', or 'none'. Using default 'all'"
426+ )
427+
428+ # Set stack-trace length from agent config
429+ if "stack-trace-length" in global_config :
430+ try :
431+ length = int (global_config ["stack-trace-length" ])
432+ if length >= 1 :
433+ self .stack_trace_length = length
434+ else :
435+ logger .warning (
436+ "stack-trace-length must be positive. Using default 30"
437+ )
438+ except (ValueError , TypeError ):
439+ logger .warning (
440+ "Invalid stack-trace-length in agent config. Must be an integer. Using default 30"
441+ )
442+
443+ # Technology-specific stack trace configuration from agent config
444+ # Only apply if not already set by higher priority sources (YAML or in-code config)
445+ # If stack_trace_technology_config is already populated, it means YAML or in-code config set it
446+ if not self .stack_trace_technology_config :
447+ # Apply technology-specific overrides from agent config
448+ # Example: kafka, redis, mysql, postgres, mongo, etc.
449+ for tech_name , tech_config in tracing .items ():
450+ if tech_name == "global" or not isinstance (tech_config , dict ):
451+ continue
452+
453+ tech_stack_config = {}
454+
455+ if "stack-trace" in tech_config :
456+ level = str (tech_config ["stack-trace" ]).lower ()
457+ if level in ["all" , "error" , "none" ]:
458+ tech_stack_config ["level" ] = level
459+ else :
460+ logger .warning (
461+ f"Invalid stack-trace value for { tech_name } : { level } . Ignoring."
462+ )
463+
464+ if "stack-trace-length" in tech_config :
465+ try :
466+ length = int (tech_config ["stack-trace-length" ])
467+ if length >= 1 :
468+ tech_stack_config ["length" ] = length
469+ else :
470+ logger .warning (
471+ f"stack-trace-length for { tech_name } must be positive. Ignoring."
472+ )
473+ except (ValueError , TypeError ):
474+ logger .warning (
475+ f"Invalid stack-trace-length for { tech_name } . Must be an integer. Ignoring."
476+ )
477+
478+ if tech_stack_config :
479+ self .stack_trace_technology_config [tech_name ] = tech_stack_config
285480
286481 def set_disable_tracing (self , tracing_config : Sequence [Dict [str , Any ]]) -> None :
287482 # The precedence is as follows:
0 commit comments