Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e3cc2df
feat(span_stack): Add initial config structure
arjun-rajappa Jan 21, 2026
a2675af
feat(span_stack): read config from env vars
arjun-rajappa Jan 21, 2026
87a7189
feat(span_stack): retain previous default of 30 lines and append stac…
arjun-rajappa Jan 21, 2026
e057c46
feat(span_stack): add config to reflect config source
arjun-rajappa Jan 22, 2026
6150db2
feat(span_stack): add tests for different config scenarios
arjun-rajappa Jan 22, 2026
cc890c0
feat(span_stack): read config from yml configuration
arjun-rajappa Jan 23, 2026
6956693
feat(span_stack): add tests for yml configuration read
arjun-rajappa Jan 23, 2026
89f5f9a
feat(span_stack): fix rubucop linting failures
arjun-rajappa Jan 23, 2026
a8d9455
feat(span_stack): read config from the agent discovery
arjun-rajappa Jan 28, 2026
81dd7bf
feat(span_stack): read config from the agent discovery
arjun-rajappa Jan 28, 2026
909cc61
feat(span_stack): test config read from agent
arjun-rajappa Jan 29, 2026
0395ab8
feat(span_stack): read span filtering right after discovery
arjun-rajappa Jan 29, 2026
c808b03
feat(span_stack): test span filtering config right after discovery
arjun-rajappa Jan 29, 2026
bcaebcb
feat(span_stack): support different technology specific config
arjun-rajappa Feb 2, 2026
6c887e5
feat(span_stack): test technology specific configs
arjun-rajappa Feb 3, 2026
2a39b88
feat(span_stack): modify span stack t6o consider technology specific …
arjun-rajappa Feb 4, 2026
c458aa7
feat(span_stack): fix rubocop linting failures
arjun-rajappa Feb 5, 2026
1287b8b
chore(currency): update tests to support dalli 5.0.0
arjun-rajappa Feb 10, 2026
a6f8267
feat(span_stack): use new config structure for backtrace
arjun-rajappa Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/instana/backend/host_agent_activation_observer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def update(_time, _old_version, new_version)
wait_for_backend(discovery['pid'])
@logger.debug("Agent ready.")
@discovery.swap { discovery }

# Read configuration from agent right after discovery
::Instana.config.read_config_from_agent(discovery)
end

socket.close
Expand Down
9 changes: 9 additions & 0 deletions lib/instana/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ class << self
attr_reader :serverless
attr_accessor :tracer_provider

##
# span_filtering_config
#
# Returns the span filtering configuration instance
#
def span_filtering_config
::Instana::SpanFiltering.configuration
end

##
# setup
#
Expand Down
165 changes: 163 additions & 2 deletions lib/instana/config.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# (c) Copyright IBM Corp. 2021
# (c) Copyright Instana Inc. 2016

require 'yaml'

module Instana
class Config
def initialize(logger: ::Instana.logger, agent_host: ENV['INSTANA_AGENT_HOST'], agent_port: ENV['INSTANA_AGENT_PORT'])
Expand Down Expand Up @@ -43,8 +45,9 @@ def initialize(logger: ::Instana.logger, agent_host: ENV['INSTANA_AGENT_HOST'],
# In Ruby, backtrace collection is very expensive so it's
# (unfortunately) disabled by default. If you still want
# backtraces, it can be enabled with this config option.
# ::Instana.config[:collect_backtraces] = true
@config[:collect_backtraces] = false
# @config[:back_trace][:stack_trace_level] = all
# @config[:back_trace] = { stack_trace_level: nil }
read_span_stack_config

# By default, collected SQL will be sanitized to remove potentially sensitive bind params such as:
# > SELECT "blocks".* FROM "blocks" WHERE "blocks"."name" = "Mr. Smith"
Expand Down Expand Up @@ -86,6 +89,164 @@ def [](key)
def []=(key, value)
@config[key.to_sym] = value
end

# Read stack trace configuration from YAML file, environment variables, or use defaults
# Priority: YAML file > Environment variables > Agent discovery > Defaults
def read_span_stack_config
# Try to load from YAML file first
yaml_config = read_span_stack_config_from_yaml

if yaml_config
@config[:back_trace] = yaml_config[:global]
@config[:back_trace_technologies] = yaml_config[:technologies] || {}
else
# Fall back to environment variables or defaults
read_span_stack_config_from_env
@config[:back_trace_technologies] = {}
end
end

# Read configuration from agent discovery response
# This is called after agent discovery is complete
# @param discovery [Hash] The discovery response from the agent
def read_config_from_agent(discovery)
return unless discovery.is_a?(Hash) && discovery['tracing']

tracing_config = discovery['tracing']

# Read stack trace configuration from agent if not already set from YAML or env
read_span_stack_config_from_agent(tracing_config) if should_read_from_agent?(:back_trace)
# Read span filtering configuration from agent
::Instana.span_filtering_config&.read_config_from_agent(discovery)
rescue => e
::Instana.logger.warn("Failed to read configuration from agent: #{e.message}")
end

# Read stack trace configuration from agent discovery
# @param tracing_config [Hash] The tracing configuration from discovery
def read_span_stack_config_from_agent(tracing_config)
return unless tracing_config['global']

global_config = tracing_config['global']
stack_trace_level = global_config['stack-trace']
stack_trace_length = global_config['stack-trace-length']

# Only update if at least one value is present
if stack_trace_level || stack_trace_length
@config[:back_trace] = {
stack_trace_level: stack_trace_level || 'error',
stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30,
config_source: 'agent'
}
end

# Read technology-specific configurations
@config[:back_trace_technologies] = {}
tracing_config.each do |key, value|
next if key == 'global' || !value.is_a?(Hash)

tech_stack_trace = value['stack-trace']
tech_stack_trace_length = value['stack-trace-length']

next unless tech_stack_trace || tech_stack_trace_length

@config[:back_trace_technologies][key.to_sym] = {
stack_trace_level: tech_stack_trace,
stack_trace_length: tech_stack_trace_length ? tech_stack_trace_length.to_i : nil
}.compact
end
end

# Read stack trace configuration from YAML file
# Returns hash with :global and :technologies keys or nil if not found
def read_span_stack_config_from_yaml # rubocop:disable Metrics/CyclomaticComplexity
config_path = ENV['INSTANA_CONFIG_PATH']
return nil unless config_path && File.exist?(config_path)

begin
yaml_content = YAML.safe_load(File.read(config_path))

# Support both "tracing" and "com.instana.tracing" as top-level keys
tracing_config = yaml_content['tracing'] || yaml_content['com.instana.tracing']
return nil unless tracing_config

result = {}

# Look for global stack trace configuration
if tracing_config['global']
global_config = tracing_config['global']
stack_trace_level = global_config['stack-trace']
stack_trace_length = global_config['stack-trace-length']

if stack_trace_level || stack_trace_length
result[:global] = {
stack_trace_level: stack_trace_level || 'error',
stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30,
config_source: 'yaml'
}
end
end

# Look for technology-specific configurations
technologies = {}
tracing_config.each do |key, value|
next if key == 'global' || !value.is_a?(Hash)

tech_stack_trace = value['stack-trace']
tech_stack_trace_length = value['stack-trace-length']

next unless tech_stack_trace || tech_stack_trace_length

technologies[key.to_sym] = {
stack_trace_level: tech_stack_trace,
stack_trace_length: tech_stack_trace_length ? tech_stack_trace_length.to_i : nil
}.compact
end

result[:technologies] = technologies unless technologies.empty?

result.empty? ? nil : result
rescue => e
::Instana.logger.warn("Failed to load stack trace configuration from YAML: #{e.message}")
nil
end
end

# Read stack trace configuration from environment variables
def read_span_stack_config_from_env
stack_trace = ENV['INSTANA_STACK_TRACE']
stack_trace_length = ENV['INSTANA_STACK_TRACE_LENGTH']
config_source = stack_trace || stack_trace_length ? 'env' : 'default'
@config[:back_trace] = {
stack_trace_level: stack_trace || 'error',
stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30,
config_source: config_source
}
end

# Check if we should read configuration from agent
# Returns true if config was not set from YAML or environment variables
def should_read_from_agent?(config_key)
return true unless @config[config_key]

source = @config[config_key][:config_source]
source.nil? || source == 'default'
end

# Get stack trace configuration for a specific technology
# Falls back to global configuration if technology-specific config is not found
# @param technology [Symbol] The technology name (e.g., :excon, :kafka, :activerecord)
# @return [Hash] Configuration hash with :stack_trace_level and :stack_trace_length
def get_stack_trace_config(technology)
tech_config = @config[:back_trace_technologies]&.[](technology)
global_config = @config[:back_trace] || {}

{
stack_trace_level: tech_config&.[](:stack_trace_level) || global_config[:stack_trace_level] || 'error',
stack_trace_length: tech_config&.[](:stack_trace_length) || global_config[:stack_trace_length] || 30
}
end

end
end

Expand Down
54 changes: 9 additions & 45 deletions lib/instana/span_filtering/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,57 +27,21 @@ def initialize
def load_configuration
load_from_yaml
load_from_env_vars unless rules_loaded?
load_from_agent unless rules_loaded?
# Agent configuration will be loaded after discovery via read_config_from_agent
end

private

# Load configuration from agent discovery response
def load_from_agent
# Try to get discovery value immediately first
discovery = ::Instana.agent&.delegate&.send(:discovery_value)
if discovery && discovery.is_a?(Hash) && !discovery.empty?
process_discovery_config(discovery)
return
end
# Read configuration from agent discovery response
# This is called from Config#read_config_from_agent after discovery is complete
# @param discovery [Hash] The discovery response from the agent
def read_config_from_agent(discovery)
return if rules_loaded? # Don't override if already loaded from YAML or env

# If not available, set up a timer task to periodically check for discovery
setup_discovery_timer
process_discovery_config(discovery)
rescue => e
Instana.logger.warn("Failed to load span filtering configuration from agent: #{e.message}")
Instana.logger.warn("Failed to read span filtering configuration from agent: #{e.message}")
end

# Set up a timer task to periodically check for discovery
def setup_discovery_timer
# Don't create a timer task if we're in a test environment
return if ENV.key?('INSTANA_TEST')

# Create a timer task that checks for discovery every second
@discovery_timer = Concurrent::TimerTask.new(execution_interval: 1) do
check_discovery
end

# Start the timer task
@discovery_timer.execute
end

# Check if discovery is available and process it
def check_discovery
discovery = ::Instana.agent&.delegate.send(:discovery_value)
if discovery && discovery.is_a?(Hash) && !discovery.empty?
process_discovery_config(discovery)

# Shutdown the timer task after successful processing
@discovery_timer.shutdown if @discovery_timer

return true
end

false
rescue => e
Instana.logger.warn("Error checking discovery in timer task: #{e.message}")
false
end
private

# Process the discovery configuration
def process_discovery_config(discovery)
Expand Down
23 changes: 21 additions & 2 deletions lib/instana/trace/span.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,18 @@ def initialize(name, parent_ctx = nil, _context = nil, parent_span = nil, _kind
set_tags(attributes)
::Instana.processor.on_start(self)
# Attach a backtrace to all exit spans
add_stack if ::Instana.config[:collect_backtraces] && exit_span?
add_stack if should_collect_stack_trace?
end

# Adds a backtrace to this span
#
# @param limit [Integer] Limit the backtrace to the top <limit> frames
#
def add_stack(limit: 30, stack: Kernel.caller)
def add_stack(span_stack_config: nil, stack: Kernel.caller)
# Get technology-specific config if not provided
span_stack_config ||= current_span_stack_config

limit = span_stack_config[:stack_trace_length]
cleaner = ::Instana.config[:backtrace_cleaner]
stack = cleaner.call(stack) if cleaner

Expand Down Expand Up @@ -530,5 +534,20 @@ def add_event(_name, attributes: nil, timestamp: nil) # rubocop:disable Lint/Unu
#
# @return [void]
def status=(status); end

def should_collect_stack_trace?
return false unless exit_span?

config = current_span_stack_config
config[:stack_trace_level] == "all"
end

# Get the stack trace configuration for this span's technology
# Falls back to global configuration if technology-specific config is not found
# @return [Hash] Configuration hash with :stack_trace_level and :stack_trace_length
def current_span_stack_config
technology = @attributes[:n]
::Instana.config.get_stack_trace_config(technology)
end
end
end
Loading
Loading