Skip to content

Commit 9a005ca

Browse files
committed
feat: Add stack trace support including precedence to
- INSTANA_CONFIG_PATH - in-code config - agent config Signed-off-by: Varsha GS <[email protected]>
1 parent 4dcd5d9 commit 9a005ca

File tree

1 file changed

+198
-3
lines changed

1 file changed

+198
-3
lines changed

src/instana/options.py

Lines changed: 198 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import logging
1818
import os
19-
from typing import Any, Dict, Sequence
19+
from typing import Any, Dict, Sequence, Tuple
2020

2121
from instana.configurator import config
2222
from instana.log import logger
@@ -25,6 +25,7 @@
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

211318
class 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

Comments
 (0)