From e3cc2dfb4625f0b5727be170ffebb10bf6097bcc Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Wed, 21 Jan 2026 16:11:45 +0530 Subject: [PATCH 01/19] feat(span_stack): Add initial config structure Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 4 ++-- lib/instana/trace/span.rb | 7 ++++++- test/instrumentation/rack_test.rb | 8 ++++---- test/trace/span_test.rb | 8 ++++---- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index 6521a8a4..1439912f 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -43,8 +43,8 @@ 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 } # By default, collected SQL will be sanitized to remove potentially sensitive bind params such as: # > SELECT "blocks".* FROM "blocks" WHERE "blocks"."name" = "Mr. Smith" diff --git a/lib/instana/trace/span.rb b/lib/instana/trace/span.rb index c8d64ddf..1e44f0d1 100644 --- a/lib/instana/trace/span.rb +++ b/lib/instana/trace/span.rb @@ -70,7 +70,7 @@ 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 @@ -78,6 +78,7 @@ def initialize(name, parent_ctx = nil, _context = nil, parent_span = nil, _kind # @param limit [Integer] Limit the backtrace to the top frames # def add_stack(limit: 30, stack: Kernel.caller) + limit ||= ::Instana.config[:back_traces][:stack_trace_length] || 30 cleaner = ::Instana.config[:backtrace_cleaner] stack = cleaner.call(stack) if cleaner @@ -530,5 +531,9 @@ def add_event(_name, attributes: nil, timestamp: nil) # rubocop:disable Lint/Unu # # @return [void] def status=(status); end + + def should_collect_stack_trace? + ::Instana.config[:back_trace][:stack_trace_level] + end end end diff --git a/test/instrumentation/rack_test.rb b/test/instrumentation/rack_test.rb index adc68fd2..46c57767 100644 --- a/test/instrumentation/rack_test.rb +++ b/test/instrumentation/rack_test.rb @@ -58,7 +58,7 @@ def app def test_basic_get clear_all! - ::Instana.config[:collect_backtraces] = true + Instana.config[:back_trace][:stack_trace_level] = "all" get '/mrlobster' assert last_response.ok? @@ -94,7 +94,7 @@ def test_basic_get assert !rack_span.key?(:stack) # Restore to default - ::Instana.config[:collect_backtraces] = false + ::Instana.config[:back_trace] = false end def test_basic_get_with_custom_service_name @@ -258,7 +258,7 @@ def test_that_url_params_not_logged def test_custom_headers_capture clear_all! - ::Instana.config[:collect_backtraces] = true + ::Instana.config[:back_trace] = true ::Instana.agent.define_singleton_method(:extra_headers) { %w(X-Capture-This X-Capture-That) } get '/mrlobster', {}, { "HTTP_X_CAPTURE_THIS" => "ThereYouGo" } @@ -278,7 +278,7 @@ def test_custom_headers_capture assert !rack_span.key?(:stack) # Restore to default - ::Instana.config[:collect_backtraces] = false + ::Instana.config[:back_trace] = false ::Instana.agent.singleton_class.send(:remove_method, :extra_headers) end diff --git a/test/trace/span_test.rb b/test/trace/span_test.rb index e3d21593..5cbc3ad0 100644 --- a/test/trace/span_test.rb +++ b/test/trace/span_test.rb @@ -56,15 +56,15 @@ def test_span_from_contetx_invalid end def test_span_collect_backtraces - Instana.config[:collect_backtraces] = true + Instana.config[:back_trace][:stack_trace_level] = "all" span = Instana::Span.new(:excon) assert span[:stack] ensure - Instana.config[:collect_backtraces] = false + Instana.config[:back_trace][:stack_trace_level] = nil end def test_span_backtrace_cleaner - Instana.config[:collect_backtraces] = true + ::Instana.config[:back_trace][:stack_trace_level] = "all" Instana.config[:backtrace_cleaner] = ->(trace) { trace.filter { |line| line.include?("lib/instana") } } span = Instana::Span.new(:excon) @@ -72,7 +72,7 @@ def test_span_backtrace_cleaner assert_equal 1, span[:stack].size ensure Instana.config[:backtrace_cleaner] = nil - Instana.config[:collect_backtraces] = false + Instana.config[:back_trace][:stack_trace_level] = nil end def test_span_stack_over_limit From a2675afb41069728f140a61c4b413d8a9a493e93 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Wed, 21 Jan 2026 17:36:12 +0530 Subject: [PATCH 02/19] feat(span_stack): read config from env vars Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 10 ++++++ test/config_test.rb | 83 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index 1439912f..39566947 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -86,6 +86,16 @@ def [](key) def []=(key, value) @config[key.to_sym] = value end + + def read_span_stack_config_from_env + stack_trace = ENV['INSTANA_STACK_TRACE'] + stack_trace_length = ENV['INSTANA_STACK_TRACE_LENGTH'] + + @config[:back_trace] = { + stack_trace_level: stack_trace, + stack_trace_length: stack_trace_length ? stack_trace_length.to_i : nil + } + end end end diff --git a/test/config_test.rb b/test/config_test.rb index 5fcc85b1..686f3ff8 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -31,4 +31,87 @@ def test_custom_agent_port subject = Instana::Config.new(logger: Logger.new('/dev/null'), agent_port: 'abc') assert_equal 'abc', subject[:agent_port] end + + def test_read_span_stack_config_from_env_with_both_values + ENV['INSTANA_STACK_TRACE'] = 'all' + ENV['INSTANA_STACK_TRACE_LENGTH'] = '40' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_span_stack_config_from_env + + assert_equal 'all', subject[:back_trace][:stack_trace_level] + assert_equal 40, subject[:back_trace][:stack_trace_length] + ensure + ENV.delete('INSTANA_STACK_TRACE') + ENV.delete('INSTANA_STACK_TRACE_LENGTH') + end + + def test_read_span_stack_config_from_env_with_error_level + ENV['INSTANA_STACK_TRACE'] = 'error' + ENV['INSTANA_STACK_TRACE_LENGTH'] = '30' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_span_stack_config_from_env + + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 30, subject[:back_trace][:stack_trace_length] + ensure + ENV.delete('INSTANA_STACK_TRACE') + ENV.delete('INSTANA_STACK_TRACE_LENGTH') + end + + def test_read_span_stack_config_from_env_with_none_level + ENV['INSTANA_STACK_TRACE'] = 'none' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_span_stack_config_from_env + + assert_equal 'none', subject[:back_trace][:stack_trace_level] + assert_nil subject[:back_trace][:stack_trace_length] + ensure + ENV.delete('INSTANA_STACK_TRACE') + end + + def test_read_span_stack_config_from_env_with_only_stack_trace_length + ENV['INSTANA_STACK_TRACE_LENGTH'] = '20' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_span_stack_config_from_env + + assert_nil subject[:back_trace][:stack_trace_level] + assert_equal 20, subject[:back_trace][:stack_trace_length] + ensure + ENV.delete('INSTANA_STACK_TRACE_LENGTH') + end + + def test_read_span_stack_config_from_env_with_no_env_vars + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_span_stack_config_from_env + + assert_nil subject[:back_trace][:stack_trace_level] + assert_nil subject[:back_trace][:stack_trace_length] + end + + def test_read_span_stack_config_from_env_converts_length_to_integer + ENV['INSTANA_STACK_TRACE_LENGTH'] = '25' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_span_stack_config_from_env + + assert_equal 25, subject[:back_trace][:stack_trace_length] + assert_instance_of Integer, subject[:back_trace][:stack_trace_length] + ensure + ENV.delete('INSTANA_STACK_TRACE_LENGTH') + end + + def test_read_span_stack_config_from_env_with_zero_length + ENV['INSTANA_STACK_TRACE_LENGTH'] = '0' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_span_stack_config_from_env + + assert_equal 0, subject[:back_trace][:stack_trace_length] + ensure + ENV.delete('INSTANA_STACK_TRACE_LENGTH') + end end From 87a71899f3dbfd8e865d85aff05c6dd051b0310b Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Wed, 21 Jan 2026 19:58:41 +0530 Subject: [PATCH 03/19] feat(span_stack): retain previous default of 30 lines and append stacks of errored span Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 7 ++++--- lib/instana/trace/span.rb | 6 +++--- test/config_test.rb | 8 ++++---- test/trace/span_test.rb | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index 39566947..53d461d3 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -44,7 +44,8 @@ def initialize(logger: ::Instana.logger, agent_host: ENV['INSTANA_AGENT_HOST'], # (unfortunately) disabled by default. If you still want # backtraces, it can be enabled with this config option. # @config[:back_trace][:stack_trace_level] = all - @config[:back_trace] = { stack_trace_level: nil } + # @config[:back_trace] = { stack_trace_level: nil } + read_span_stack_config_from_env # By default, collected SQL will be sanitized to remove potentially sensitive bind params such as: # > SELECT "blocks".* FROM "blocks" WHERE "blocks"."name" = "Mr. Smith" @@ -92,8 +93,8 @@ def read_span_stack_config_from_env stack_trace_length = ENV['INSTANA_STACK_TRACE_LENGTH'] @config[:back_trace] = { - stack_trace_level: stack_trace, - stack_trace_length: stack_trace_length ? stack_trace_length.to_i : nil + stack_trace_level: stack_trace || "error", + stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30 } end end diff --git a/lib/instana/trace/span.rb b/lib/instana/trace/span.rb index 1e44f0d1..bab67ca6 100644 --- a/lib/instana/trace/span.rb +++ b/lib/instana/trace/span.rb @@ -77,8 +77,8 @@ def initialize(name, parent_ctx = nil, _context = nil, parent_span = nil, _kind # # @param limit [Integer] Limit the backtrace to the top frames # - def add_stack(limit: 30, stack: Kernel.caller) - limit ||= ::Instana.config[:back_traces][:stack_trace_length] || 30 + def add_stack(span_stack_config: ::Instana.config[:back_trace], stack: Kernel.caller) + limit = span_stack_config.dig(:stack_trace_length) cleaner = ::Instana.config[:backtrace_cleaner] stack = cleaner.call(stack) if cleaner @@ -533,7 +533,7 @@ def add_event(_name, attributes: nil, timestamp: nil) # rubocop:disable Lint/Unu def status=(status); end def should_collect_stack_trace? - ::Instana.config[:back_trace][:stack_trace_level] + ::Instana.config[:back_trace] && ::Instana.config[:back_trace][:stack_trace_level] == "all" && exit_span? end end end diff --git a/test/config_test.rb b/test/config_test.rb index 686f3ff8..42bb685a 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -67,7 +67,7 @@ def test_read_span_stack_config_from_env_with_none_level subject.read_span_stack_config_from_env assert_equal 'none', subject[:back_trace][:stack_trace_level] - assert_nil subject[:back_trace][:stack_trace_length] + assert_equal 40, subject[:back_trace][:stack_trace_length] ensure ENV.delete('INSTANA_STACK_TRACE') end @@ -78,7 +78,7 @@ def test_read_span_stack_config_from_env_with_only_stack_trace_length subject = Instana::Config.new(logger: Logger.new('/dev/null')) subject.read_span_stack_config_from_env - assert_nil subject[:back_trace][:stack_trace_level] + assert_equal "error", subject[:back_trace][:stack_trace_level] assert_equal 20, subject[:back_trace][:stack_trace_length] ensure ENV.delete('INSTANA_STACK_TRACE_LENGTH') @@ -88,8 +88,8 @@ def test_read_span_stack_config_from_env_with_no_env_vars subject = Instana::Config.new(logger: Logger.new('/dev/null')) subject.read_span_stack_config_from_env - assert_nil subject[:back_trace][:stack_trace_level] - assert_nil subject[:back_trace][:stack_trace_length] + assert_equal "error", subject[:back_trace][:stack_trace_level] + assert_equal 30, subject[:back_trace][:stack_trace_length] end def test_read_span_stack_config_from_env_converts_length_to_integer diff --git a/test/trace/span_test.rb b/test/trace/span_test.rb index 5cbc3ad0..0df0c64b 100644 --- a/test/trace/span_test.rb +++ b/test/trace/span_test.rb @@ -84,7 +84,7 @@ def inner(depth = 50, &blk) # rubocop:disable Lint/NestedMethodDefinition inner do span = Instana::Span.new(:excon) - span.add_stack(limit: 500) + span.add_stack(span_stack_config: { stack_trace_length: 500}) assert_equal 40, span[:stack].length end end From e057c46e35f83f2bc4e9b322abd4c905aba27608 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Thu, 22 Jan 2026 11:24:15 +0530 Subject: [PATCH 04/19] feat(span_stack): add config to reflect config source Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 7 ++++--- lib/instana/trace/span.rb | 2 +- test/config_test.rb | 9 ++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index 53d461d3..5e5740da 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -91,10 +91,11 @@ def []=(key, value) 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 + stack_trace_level: stack_trace || 'error', + stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30, + config_source: config_source } end end diff --git a/lib/instana/trace/span.rb b/lib/instana/trace/span.rb index bab67ca6..693e8346 100644 --- a/lib/instana/trace/span.rb +++ b/lib/instana/trace/span.rb @@ -78,7 +78,7 @@ def initialize(name, parent_ctx = nil, _context = nil, parent_span = nil, _kind # @param limit [Integer] Limit the backtrace to the top frames # def add_stack(span_stack_config: ::Instana.config[:back_trace], stack: Kernel.caller) - limit = span_stack_config.dig(:stack_trace_length) + limit = span_stack_config[:stack_trace_length] cleaner = ::Instana.config[:backtrace_cleaner] stack = cleaner.call(stack) if cleaner diff --git a/test/config_test.rb b/test/config_test.rb index 42bb685a..c26801bd 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -41,6 +41,7 @@ def test_read_span_stack_config_from_env_with_both_values assert_equal 'all', subject[:back_trace][:stack_trace_level] assert_equal 40, subject[:back_trace][:stack_trace_length] + assert_equal 'env', subject[:back_trace][:config_source] ensure ENV.delete('INSTANA_STACK_TRACE') ENV.delete('INSTANA_STACK_TRACE_LENGTH') @@ -55,6 +56,7 @@ def test_read_span_stack_config_from_env_with_error_level assert_equal 'error', subject[:back_trace][:stack_trace_level] assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'env', subject[:back_trace][:config_source] ensure ENV.delete('INSTANA_STACK_TRACE') ENV.delete('INSTANA_STACK_TRACE_LENGTH') @@ -67,7 +69,8 @@ def test_read_span_stack_config_from_env_with_none_level subject.read_span_stack_config_from_env assert_equal 'none', subject[:back_trace][:stack_trace_level] - assert_equal 40, subject[:back_trace][:stack_trace_length] + assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'env', subject[:back_trace][:config_source] ensure ENV.delete('INSTANA_STACK_TRACE') end @@ -80,6 +83,7 @@ def test_read_span_stack_config_from_env_with_only_stack_trace_length assert_equal "error", subject[:back_trace][:stack_trace_level] assert_equal 20, subject[:back_trace][:stack_trace_length] + assert_equal 'env', subject[:back_trace][:config_source] ensure ENV.delete('INSTANA_STACK_TRACE_LENGTH') end @@ -90,6 +94,7 @@ def test_read_span_stack_config_from_env_with_no_env_vars assert_equal "error", subject[:back_trace][:stack_trace_level] assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'default', subject[:back_trace][:config_source] end def test_read_span_stack_config_from_env_converts_length_to_integer @@ -100,6 +105,7 @@ def test_read_span_stack_config_from_env_converts_length_to_integer assert_equal 25, subject[:back_trace][:stack_trace_length] assert_instance_of Integer, subject[:back_trace][:stack_trace_length] + assert_equal 'env', subject[:back_trace][:config_source] ensure ENV.delete('INSTANA_STACK_TRACE_LENGTH') end @@ -111,6 +117,7 @@ def test_read_span_stack_config_from_env_with_zero_length subject.read_span_stack_config_from_env assert_equal 0, subject[:back_trace][:stack_trace_length] + assert_equal 'env', subject[:back_trace][:config_source] ensure ENV.delete('INSTANA_STACK_TRACE_LENGTH') end From 6150db22d8b95794ff570d409f20c3c39c7e02c4 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Thu, 22 Jan 2026 12:45:06 +0530 Subject: [PATCH 05/19] feat(span_stack): add tests for different config scenarios Signed-off-by: Arjun Rajappa --- test/trace/span_test.rb | 226 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/test/trace/span_test.rb b/test/trace/span_test.rb index 0df0c64b..a13ced2e 100644 --- a/test/trace/span_test.rb +++ b/test/trace/span_test.rb @@ -176,4 +176,230 @@ def test_no_custom_service_name_set span = Instana::Span.new(:excon) assert_nil(span[:data][:service]) end + + # Tests for stack_trace_level configuration + + def test_stack_trace_level_all_collects_for_all_spans + Instana.config[:back_trace][:stack_trace_level] = "all" + span = Instana::Span.new(:excon) + + assert span[:stack], "Stack trace should be collected for all spans when level is 'all'" + assert span[:stack].is_a?(Array), "Stack trace should be an array" + assert span[:stack].length > 0, "Stack trace should not be empty" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + end + + def test_stack_trace_level_error_does_not_collect_for_normal_spans + Instana.config[:back_trace][:stack_trace_level] = "error" + span = Instana::Span.new(:excon) + + assert_nil span[:stack], "Stack trace should not be collected for normal spans when level is 'error'" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + end + + def test_stack_trace_level_error_collects_for_erroneous_spans + Instana.config[:back_trace][:stack_trace_level] = "error" + span = Instana::Span.new(:excon) + + # Span should not have stack trace at creation with level 'error' + assert_nil span[:stack], "Stack trace should not be collected at span creation when level is 'error'" + + # Record an exception to make it an erroneous span + # Need to raise the exception to populate its backtrace + begin + raise StandardError, "Test error" + rescue StandardError => error + span.record_exception(error) + end + + # Stack trace from the exception backtrace should be collected + assert span[:stack], "Stack trace from exception should be collected" + assert span[:stack].is_a?(Array), "Stack trace should be an array" + assert span[:stack].length > 0, "Stack trace should not be empty" + assert_equal 1, span[:ec], "Error count should be 1" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + end + + def test_stack_trace_level_none_does_not_collect_for_normal_spans + Instana.config[:back_trace][:stack_trace_level] = "none" + span = Instana::Span.new(:excon) + + assert_nil span[:stack], "Stack trace should not be collected when level is 'none'" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + end + + def test_stack_trace_level_none_does_not_collect_for_erroneous_spans + Instana.config[:back_trace][:stack_trace_level] = "none" + span = Instana::Span.new(:excon) + + # Span should not have stack trace at creation with level 'none' + assert_nil span[:stack], "Stack trace should not be collected at span creation when level is 'none'" + + # Record an exception - need to raise it to populate backtrace + begin + raise StandardError, "Test error" + rescue StandardError => error + span.record_exception(error) + end + + # Note: record_exception always collects the exception's backtrace regardless of stack_trace_level + # This is by design - the stack_trace_level only controls automatic collection at span creation + assert span[:stack], "Stack trace from exception backtrace is always collected by record_exception" + assert_equal 1, span[:ec], "Error count should be 1" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + end + + # Tests for stack_trace_length configuration + + def test_stack_trace_length_limits_frames + Instana.config[:back_trace][:stack_trace_level] = "all" + Instana.config[:back_trace][:stack_trace_length] = 5 + + span = Instana::Span.new(:excon) + + assert span[:stack], "Stack trace should be collected" + assert span[:stack].length <= 5, "Stack trace should be limited to 5 frames" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + Instana.config[:back_trace][:stack_trace_length] = 30 + end + + def test_stack_trace_length_with_different_values + Instana.config[:back_trace][:stack_trace_level] = "all" + + # Test with length 10 + Instana.config[:back_trace][:stack_trace_length] = 10 + span1 = Instana::Span.new(:excon) + assert span1[:stack].length <= 10, "Stack trace should be limited to 10 frames" + + # Test with length 20 + Instana.config[:back_trace][:stack_trace_length] = 20 + span2 = Instana::Span.new(:excon) + assert span2[:stack].length <= 20, "Stack trace should be limited to 20 frames" + + # Test with length 1 + Instana.config[:back_trace][:stack_trace_length] = 1 + span3 = Instana::Span.new(:excon) + assert span3[:stack].length <= 1, "Stack trace should be limited to 1 frame" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + Instana.config[:back_trace][:stack_trace_length] = 30 + end + + def test_stack_trace_length_zero_collects_no_frames + Instana.config[:back_trace][:stack_trace_level] = "all" + Instana.config[:back_trace][:stack_trace_length] = 0 + + span = Instana::Span.new(:excon) + + # With length 0, stack should either be nil or empty array + assert(span[:stack].nil? || span[:stack].empty?, "Stack trace should be empty with length 0") + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + Instana.config[:back_trace][:stack_trace_length] = 30 + end + + # Combined tests for stack_trace_level and stack_trace_length + + def test_stack_trace_all_with_custom_length + Instana.config[:back_trace][:stack_trace_level] = "all" + Instana.config[:back_trace][:stack_trace_length] = 15 + + span = Instana::Span.new(:excon) + + assert span[:stack], "Stack trace should be collected with level 'all'" + assert span[:stack].length <= 15, "Stack trace should respect custom length of 15" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + Instana.config[:back_trace][:stack_trace_length] = 30 + end + + def test_stack_trace_error_with_custom_length_on_error + Instana.config[:back_trace][:stack_trace_level] = "error" + Instana.config[:back_trace][:stack_trace_length] = 8 + + span = Instana::Span.new(:excon) + + # No stack at creation with level 'error' + assert_nil span[:stack], "Stack trace should not be collected at span creation when level is 'error'" + + # Raise exception to populate backtrace + begin + raise StandardError, "Test error" + rescue StandardError => error + span.record_exception(error) + end + + # Stack trace from exception should be collected and respect length limit + assert span[:stack], "Stack trace from exception should be collected" + assert span[:stack].length <= 8, "Stack trace should respect custom length of 8" + assert_equal 1, span[:ec], "Error count should be 1" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + Instana.config[:back_trace][:stack_trace_length] = 30 + end + + def test_stack_trace_none_ignores_length_setting + Instana.config[:back_trace][:stack_trace_level] = "none" + Instana.config[:back_trace][:stack_trace_length] = 100 + + span = Instana::Span.new(:excon) + + assert_nil span[:stack], "Stack trace should not be collected when level is 'none', regardless of length" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + Instana.config[:back_trace][:stack_trace_length] = 30 + end + + # Test for non-exit spans + + def test_stack_trace_not_collected_for_non_exit_spans + Instana.config[:back_trace][:stack_trace_level] = "all" + + # Create a non-exit span (sdk/custom span) + span = Instana::Span.new(:sdk) + + # Non-exit spans should not collect stack traces automatically + assert_nil span[:stack], "Stack trace should not be collected for non-exit spans" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + end + + def test_stack_trace_for_multiple_errors_with_error_level + Instana.config[:back_trace][:stack_trace_level] = "error" + + span = Instana::Span.new(:excon) + + # No stack at creation with level 'error' + assert_nil span[:stack], "Stack trace should not be collected at span creation when level is 'error'" + + # First error - raise to populate backtrace + begin + raise StandardError, "First error" + rescue StandardError => error1 + span.record_exception(error1) + end + + assert span[:stack], "Stack trace from first exception should be collected" + first_stack = span[:stack].dup + + # Second error - raise to populate backtrace + begin + raise StandardError, "Second error" + rescue StandardError => error2 + span.record_exception(error2) + end + + assert span[:stack], "Stack trace from second exception should be collected" + assert_equal 2, span[:ec], "Error count should be 2" + # The stack from the second error should replace the first + refute_equal first_stack, span[:stack], "Stack trace should be updated with second error" + ensure + Instana.config[:back_trace][:stack_trace_level] = "error" + end end From cc890c051d5010f748226e99c21f7da315f7f5e7 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Fri, 23 Jan 2026 11:33:07 +0530 Subject: [PATCH 06/19] feat(span_stack): read config from yml configuration Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 55 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index 5e5740da..8350a525 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -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']) @@ -45,7 +47,7 @@ def initialize(logger: ::Instana.logger, agent_host: ENV['INSTANA_AGENT_HOST'], # backtraces, it can be enabled with this config option. # @config[:back_trace][:stack_trace_level] = all # @config[:back_trace] = { stack_trace_level: nil } - read_span_stack_config_from_env + 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" @@ -88,6 +90,57 @@ 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 > 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 + else + # Fall back to environment variables or defaults + read_span_stack_config_from_env + end + end + + # Read stack trace configuration from YAML file + # Returns hash with config or nil if not found + def read_span_stack_config_from_yaml + 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 + + # Look for global stack trace configuration + global_config = tracing_config['global'] + return nil unless global_config + + stack_trace_level = global_config['stack-trace'] + stack_trace_length = global_config['stack-trace-length'] + + # Only return config if at least one value is present + if stack_trace_level || stack_trace_length + { + stack_trace_level: stack_trace_level || 'error', + stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30, + config_source: 'yaml' + } + else + nil + end + 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'] From 6956693d9547cf9d2180bf0c5d60ca3d278792b7 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Fri, 23 Jan 2026 18:12:52 +0530 Subject: [PATCH 07/19] feat(span_stack): add tests for yml configuration read Signed-off-by: Arjun Rajappa --- test/config_test.rb | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/test/config_test.rb b/test/config_test.rb index c26801bd..2a283663 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -121,4 +121,172 @@ def test_read_span_stack_config_from_env_with_zero_length ensure ENV.delete('INSTANA_STACK_TRACE_LENGTH') end + + # Tests for YAML configuration reading + + def test_read_span_stack_config_from_yaml_with_both_values + yaml_content = <<~YAML + tracing: + global: + stack-trace: all + stack-trace-length: 25 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + assert_equal 'all', subject[:back_trace][:stack_trace_level] + assert_equal 25, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_span_stack_config_from_yaml_with_error_level + yaml_content = <<~YAML + tracing: + global: + stack-trace: error + stack-trace-length: 15 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 15, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_span_stack_config_from_yaml_with_none_level + yaml_content = <<~YAML + tracing: + global: + stack-trace: none + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + assert_equal 'none', subject[:back_trace][:stack_trace_level] + assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_span_stack_config_from_yaml_with_only_length + yaml_content = <<~YAML + tracing: + global: + stack-trace-length: 10 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 10, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_span_stack_config_from_yaml_with_com_instana_tracing + yaml_content = <<~YAML + com.instana.tracing: + global: + stack-trace: all + stack-trace-length: 20 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + assert_equal 'all', subject[:back_trace][:stack_trace_level] + assert_equal 20, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_span_stack_config_yaml_missing_global_section + yaml_content = <<~YAML + tracing: + filter: + deactivate: false + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Should fall back to defaults + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'default', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_span_stack_config_yaml_takes_precedence_over_env + yaml_content = <<~YAML + tracing: + global: + stack-trace: all + stack-trace-length: 50 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + ENV['INSTANA_STACK_TRACE'] = 'none' + ENV['INSTANA_STACK_TRACE_LENGTH'] = '5' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # YAML should take precedence + assert_equal 'all', subject[:back_trace][:stack_trace_level] + assert_equal 50, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + ENV.delete('INSTANA_STACK_TRACE') + ENV.delete('INSTANA_STACK_TRACE_LENGTH') + end + + def test_read_span_stack_config_invalid_yaml_falls_back_to_env + File.write('test_stack_config.yaml', "invalid: yaml: content: - [") + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + ENV['INSTANA_STACK_TRACE'] = 'all' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Should fall back to env vars + assert_equal 'all', subject[:back_trace][:stack_trace_level] + assert_equal 'env', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + ENV.delete('INSTANA_STACK_TRACE') + end end From 89f5f9a5eb76bd07a879abffc51b15c471faeb50 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Fri, 23 Jan 2026 19:08:17 +0530 Subject: [PATCH 08/19] feat(span_stack): fix rubucop linting failures Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 2 -- test/trace/span_test.rb | 26 +++++++++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index 8350a525..f1d70a55 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -131,8 +131,6 @@ def read_span_stack_config_from_yaml stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30, config_source: 'yaml' } - else - nil end rescue => e ::Instana.logger.warn("Failed to load stack trace configuration from YAML: #{e.message}") diff --git a/test/trace/span_test.rb b/test/trace/span_test.rb index a13ced2e..48b6fc62 100644 --- a/test/trace/span_test.rb +++ b/test/trace/span_test.rb @@ -185,7 +185,7 @@ def test_stack_trace_level_all_collects_for_all_spans assert span[:stack], "Stack trace should be collected for all spans when level is 'all'" assert span[:stack].is_a?(Array), "Stack trace should be an array" - assert span[:stack].length > 0, "Stack trace should not be empty" + assert span[:stack].length.positive?, "Stack trace should not be empty" ensure Instana.config[:back_trace][:stack_trace_level] = "error" end @@ -210,14 +210,14 @@ def test_stack_trace_level_error_collects_for_erroneous_spans # Need to raise the exception to populate its backtrace begin raise StandardError, "Test error" - rescue StandardError => error - span.record_exception(error) + rescue StandardError => e + span.record_exception(e) end # Stack trace from the exception backtrace should be collected assert span[:stack], "Stack trace from exception should be collected" assert span[:stack].is_a?(Array), "Stack trace should be an array" - assert span[:stack].length > 0, "Stack trace should not be empty" + assert span[:stack].length.positive?, "Stack trace should not be empty" assert_equal 1, span[:ec], "Error count should be 1" ensure Instana.config[:back_trace][:stack_trace_level] = "error" @@ -242,11 +242,11 @@ def test_stack_trace_level_none_does_not_collect_for_erroneous_spans # Record an exception - need to raise it to populate backtrace begin raise StandardError, "Test error" - rescue StandardError => error - span.record_exception(error) + rescue StandardError => e + span.record_exception(e) end - # Note: record_exception always collects the exception's backtrace regardless of stack_trace_level + # NOTE: record_exception always collects the exception's backtrace regardless of stack_trace_level # This is by design - the stack_trace_level only controls automatic collection at span creation assert span[:stack], "Stack trace from exception backtrace is always collected by record_exception" assert_equal 1, span[:ec], "Error count should be 1" @@ -331,8 +331,8 @@ def test_stack_trace_error_with_custom_length_on_error # Raise exception to populate backtrace begin raise StandardError, "Test error" - rescue StandardError => error - span.record_exception(error) + rescue StandardError => e + span.record_exception(e) end # Stack trace from exception should be collected and respect length limit @@ -381,8 +381,8 @@ def test_stack_trace_for_multiple_errors_with_error_level # First error - raise to populate backtrace begin raise StandardError, "First error" - rescue StandardError => error1 - span.record_exception(error1) + rescue StandardError => e + span.record_exception(e) end assert span[:stack], "Stack trace from first exception should be collected" @@ -391,8 +391,8 @@ def test_stack_trace_for_multiple_errors_with_error_level # Second error - raise to populate backtrace begin raise StandardError, "Second error" - rescue StandardError => error2 - span.record_exception(error2) + rescue StandardError => e + span.record_exception(e) end assert span[:stack], "Stack trace from second exception should be collected" From a8d9455b6288e842bdf534c057cf1a226c78e507 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Wed, 28 Jan 2026 13:49:26 +0530 Subject: [PATCH 09/19] feat(span_stack): read config from the agent discovery Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 45 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index f1d70a55..e92d37b9 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -91,7 +91,7 @@ def []=(key, value) end # Read stack trace configuration from YAML file, environment variables, or use defaults - # Priority: YAML file > Environment variables > 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 @@ -104,6 +104,39 @@ def read_span_stack_config 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) + 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 + end + # Read stack trace configuration from YAML file # Returns hash with config or nil if not found def read_span_stack_config_from_yaml @@ -149,6 +182,16 @@ def read_span_stack_config_from_env 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 + end end From 81dd7bff52e7fab71eb9dbd3da3ea920ddc7f5a0 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Wed, 28 Jan 2026 14:34:30 +0530 Subject: [PATCH 10/19] feat(span_stack): read config from the agent discovery Signed-off-by: Arjun Rajappa --- lib/instana/backend/host_agent_activation_observer.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/instana/backend/host_agent_activation_observer.rb b/lib/instana/backend/host_agent_activation_observer.rb index e1bd46ef..56b2157e 100644 --- a/lib/instana/backend/host_agent_activation_observer.rb +++ b/lib/instana/backend/host_agent_activation_observer.rb @@ -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 From 909cc61e32659ecc3662c45dafe93ac1fcb339fb Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Thu, 29 Jan 2026 13:21:11 +0530 Subject: [PATCH 11/19] feat(span_stack): test config read from agent Signed-off-by: Arjun Rajappa --- test/config_test.rb | 203 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/test/config_test.rb b/test/config_test.rb index 2a283663..311ecf4e 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -289,4 +289,207 @@ def test_read_span_stack_config_invalid_yaml_falls_back_to_env ENV.delete('INSTANA_CONFIG_PATH') ENV.delete('INSTANA_STACK_TRACE') end + + # Tests for reading configuration from agent discovery + + def test_read_config_from_agent_with_stack_trace_config + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace' => 'all', + 'stack-trace-length' => 100 + } + } + } + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_config_from_agent(discovery) + + assert_equal 'all', subject[:back_trace][:stack_trace_level] + assert_equal 100, subject[:back_trace][:stack_trace_length] + assert_equal 'agent', subject[:back_trace][:config_source] + end + + def test_read_config_from_agent_with_only_stack_trace_level + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace' => 'none' + } + } + } + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_config_from_agent(discovery) + + assert_equal 'none', subject[:back_trace][:stack_trace_level] + assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'agent', subject[:back_trace][:config_source] + end + + def test_read_config_from_agent_with_only_stack_trace_length + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace-length' => 75 + } + } + } + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_config_from_agent(discovery) + + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 75, subject[:back_trace][:stack_trace_length] + assert_equal 'agent', subject[:back_trace][:config_source] + end + + def test_read_config_from_agent_without_tracing_config + discovery = { + 'pid' => 12345, + 'agentUuid' => 'test-uuid' + } + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + original_config = subject[:back_trace].dup + subject.read_config_from_agent(discovery) + + # Config should remain unchanged + assert_equal original_config, subject[:back_trace] + end + + def test_read_config_from_agent_with_empty_discovery + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + original_config = subject[:back_trace].dup + subject.read_config_from_agent({}) + + # Config should remain unchanged + assert_equal original_config, subject[:back_trace] + end + + def test_read_config_from_agent_with_nil_discovery + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + original_config = subject[:back_trace].dup + subject.read_config_from_agent(nil) + + # Config should remain unchanged + assert_equal original_config, subject[:back_trace] + end + + # Tests for configuration priority: YAML > Env > Agent > Default + + def test_priority_yaml_over_agent + yaml_content = <<~YAML + tracing: + global: + stack-trace: none + stack-trace-length: 10 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Try to override with agent config + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace' => 'all', + 'stack-trace-length' => 100 + } + } + } + subject.read_config_from_agent(discovery) + + # YAML should take precedence + assert_equal 'none', subject[:back_trace][:stack_trace_level] + assert_equal 10, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_priority_env_over_agent + ENV['INSTANA_STACK_TRACE'] = 'error' + ENV['INSTANA_STACK_TRACE_LENGTH'] = '20' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Try to override with agent config + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace' => 'all', + 'stack-trace-length' => 100 + } + } + } + subject.read_config_from_agent(discovery) + + # Env should take precedence + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 20, subject[:back_trace][:stack_trace_length] + assert_equal 'env', subject[:back_trace][:config_source] + ensure + ENV.delete('INSTANA_STACK_TRACE') + ENV.delete('INSTANA_STACK_TRACE_LENGTH') + end + + def test_priority_agent_over_default + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Verify default config + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'default', subject[:back_trace][:config_source] + + # Override with agent config + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace' => 'all', + 'stack-trace-length' => 50 + } + } + } + subject.read_config_from_agent(discovery) + + # Agent should override default + assert_equal 'all', subject[:back_trace][:stack_trace_level] + assert_equal 50, subject[:back_trace][:stack_trace_length] + assert_equal 'agent', subject[:back_trace][:config_source] + end + + def test_should_read_from_agent_returns_true_for_default_config + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + assert subject.send(:should_read_from_agent?, :back_trace) + end + + def test_should_read_from_agent_returns_false_for_yaml_config + yaml_content = <<~YAML + tracing: + global: + stack-trace: all + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + refute subject.send(:should_read_from_agent?, :back_trace) + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_should_read_from_agent_returns_false_for_env_config + ENV['INSTANA_STACK_TRACE'] = 'all' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + refute subject.send(:should_read_from_agent?, :back_trace) + ensure + ENV.delete('INSTANA_STACK_TRACE') + end end From 0395ab833857fa3685fa96eb16253f79f04f0ee4 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Thu, 29 Jan 2026 16:54:40 +0530 Subject: [PATCH 12/19] feat(span_stack): read span filtering right after discovery Signed-off-by: Arjun Rajappa --- lib/instana/base.rb | 9 ++++ lib/instana/config.rb | 2 + lib/instana/span_filtering/configuration.rb | 54 ++++----------------- 3 files changed, 20 insertions(+), 45 deletions(-) diff --git a/lib/instana/base.rb b/lib/instana/base.rb index 983ed97b..a1a9da3b 100644 --- a/lib/instana/base.rb +++ b/lib/instana/base.rb @@ -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 # diff --git a/lib/instana/config.rb b/lib/instana/config.rb index e92d37b9..2948f883 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -114,6 +114,8 @@ def read_config_from_agent(discovery) # 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 diff --git a/lib/instana/span_filtering/configuration.rb b/lib/instana/span_filtering/configuration.rb index 642a922c..a1b300ed 100644 --- a/lib/instana/span_filtering/configuration.rb +++ b/lib/instana/span_filtering/configuration.rb @@ -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) From c808b03b22368327c2d565cdac9a6966a566ee2b Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Thu, 29 Jan 2026 21:44:24 +0530 Subject: [PATCH 13/19] feat(span_stack): test span filtering config right after discovery Signed-off-by: Arjun Rajappa --- test/span_filtering/configuration_test.rb | 159 +++++----------------- 1 file changed, 32 insertions(+), 127 deletions(-) diff --git a/test/span_filtering/configuration_test.rb b/test/span_filtering/configuration_test.rb index cbda4fe2..4b465618 100644 --- a/test/span_filtering/configuration_test.rb +++ b/test/span_filtering/configuration_test.rb @@ -202,7 +202,7 @@ def test_load_from_both_yaml_and_env_vars end def test_load_from_agent_discovery - # Create a mock agent with discovery value + # Create a discovery value discovery_value = { 'tracing' => { 'filter' => { @@ -235,17 +235,12 @@ def test_load_from_agent_discovery } } - # Create a mock agent - mock_agent = Minitest::Mock.new - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, discovery_value) - - # Replace the global agent with our mock - ::Instana.instance_variable_set(:@agent, mock_agent) - - # Create a new configuration that should load from our mock agent + # Create a new configuration config = Instana::SpanFiltering::Configuration.new + # Simulate loading from agent after discovery + config.read_config_from_agent(discovery_value) + # Verify the configuration was loaded correctly assert_equal 1, config.include_rules.size assert_equal 'include-http', config.include_rules.first.name @@ -253,12 +248,10 @@ def test_load_from_agent_discovery assert_equal 1, config.exclude_rules.size assert_equal 'exclude-redis', config.exclude_rules.first.name assert config.exclude_rules.first.suppression - - mock_agent.verify end def test_load_from_agent_with_deactivation - # Create a mock agent with discovery value that has deactivation flag + # Create a discovery value that has deactivation flag discovery_value = { 'tracing' => { 'filter' => { @@ -267,43 +260,29 @@ def test_load_from_agent_with_deactivation } } - # Create a mock agent - mock_agent = Minitest::Mock.new - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, discovery_value) - - # Replace the global agent with our mock - ::Instana.instance_variable_set(:@agent, mock_agent) - - # Create a new configuration that should load from our mock agent + # Create a new configuration config = Instana::SpanFiltering::Configuration.new + # Simulate loading from agent after discovery + config.read_config_from_agent(discovery_value) + # Verify the configuration was loaded correctly assert config.deactivated assert_empty config.include_rules assert_empty config.exclude_rules - - mock_agent.verify end def test_load_from_agent_with_empty_discovery - # Create a mock agent with empty discovery value - mock_agent = Minitest::Mock.new - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, {}) - - # Replace the global agent with our mock - ::Instana.instance_variable_set(:@agent, mock_agent) - - # Create a new configuration that should try to load from our mock agent + # Create a new configuration config = Instana::SpanFiltering::Configuration.new + # Simulate loading from agent with empty discovery + config.read_config_from_agent({}) + # Verify the configuration was not loaded (empty) refute config.deactivated assert_empty config.include_rules assert_empty config.exclude_rules - - mock_agent.verify end def test_load_from_agent_with_nil_agent @@ -339,46 +318,9 @@ def mock_agent.discovery_value assert_empty config.exclude_rules end - def test_load_from_agent_with_timer_task # rubocop:disable Metrics/MethodLength - # Save original INSTANA_TEST value - original_test_env = ENV['INSTANA_TEST'] - ENV.delete('INSTANA_TEST') # Temporarily remove INSTANA_TEST to allow timer task creation - - # Mock the Concurrent::TimerTask class - original_timer_task = Concurrent::TimerTask - Concurrent.send(:remove_const, :TimerTask) - - # Create a custom timer task class that immediately executes the block - Concurrent.const_set(:TimerTask, Class.new do - def initialize(*args, &block) - @block = block - @running = false - @args = args - end - - def execute - @running = true - # Immediately execute the block when execute is called - @block.call - true - end - - def shutdown - @running = false - end - - def running? - @running - end - end) - - # Create a mock agent with nil discovery initially, then with real discovery later - mock_agent = Minitest::Mock.new - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, nil) - - # We need to set up the mock to return real discovery value on second call - # This will be called by the timer task + def test_load_from_agent_with_timer_task + # This test is no longer relevant as we removed the timer task dependency + # Configuration is now loaded via read_config_from_agent after discovery discovery_value = { 'tracing' => { 'filter' => { @@ -398,34 +340,15 @@ def running? } } - # Set up the mock to return real discovery value on second call - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, discovery_value) - - # Replace the global agent with our mock - ::Instana.instance_variable_set(:@agent, mock_agent) - - # Create a new configuration that should set up a timer task + # Create a new configuration config = Instana::SpanFiltering::Configuration.new - # Verify the configuration was loaded by the timer task + # Simulate loading from agent after discovery (replaces timer task behavior) + config.read_config_from_agent(discovery_value) + + # Verify the configuration was loaded assert_equal 1, config.include_rules.size assert_equal 'include-http', config.include_rules.first.name - - mock_agent.verify - ensure - # Restore the original TimerTask class - if Concurrent.const_defined?(:TimerTask) - Concurrent.send(:remove_const, :TimerTask) - Concurrent.const_set(:TimerTask, original_timer_task) - end - - # Restore original INSTANA_TEST value - if original_test_env - ENV['INSTANA_TEST'] = original_test_env - else - ENV.delete('INSTANA_TEST') - end end end @@ -550,63 +473,45 @@ def test_redis_disabled_via_env_var_multiple end def test_redis_disabled_via_agent_discovery_string_format - # Create a mock agent with discovery value using string format + # Create a discovery value using string format discovery_value = { 'tracing' => { - 'disable' => [{'redis' => true}] + 'disable' => ['redis'] } } - mock_agent = Minitest::Mock.new - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, discovery_value) - - ::Instana.instance_variable_set(:@agent, mock_agent) - - Instana::SpanFiltering::Configuration.new + config = Instana::SpanFiltering::Configuration.new + config.read_config_from_agent(discovery_value) refute ::Instana.config[:redis][:enabled], "Redis should be disabled via agent discovery string format" - mock_agent.verify end def test_redis_disabled_via_agent_discovery_hash_format - # Create a mock agent with discovery value using hash format + # Create a discovery value using hash format discovery_value = { 'tracing' => { 'disable' => [{'redis' => true}] } } - mock_agent = Minitest::Mock.new - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, discovery_value) - - ::Instana.instance_variable_set(:@agent, mock_agent) - - Instana::SpanFiltering::Configuration.new + config = Instana::SpanFiltering::Configuration.new + config.read_config_from_agent(discovery_value) refute ::Instana.config[:redis][:enabled], "Redis should be disabled via agent discovery hash format" - mock_agent.verify end def test_redis_disabled_via_agent_discovery_databases - # Create a mock agent with discovery value disabling databases category + # Create a discovery value disabling databases category discovery_value = { 'tracing' => { 'disable' => [{'databases' => true}] } } - mock_agent = Minitest::Mock.new - mock_agent.expect(:delegate, mock_agent) - mock_agent.expect(:discovery_value, discovery_value) - - ::Instana.instance_variable_set(:@agent, mock_agent) - - Instana::SpanFiltering::Configuration.new + config = Instana::SpanFiltering::Configuration.new + config.read_config_from_agent(discovery_value) refute ::Instana.config[:redis][:enabled], "Redis should be disabled when databases category is disabled via agent discovery" - mock_agent.verify end def test_yaml_config_takes_precedence_over_agent_discovery From bcaebcbb105e72244eb9358c88713330e7685669 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Mon, 2 Feb 2026 10:31:32 +0530 Subject: [PATCH 14/19] feat(span_stack): support different technology specific config Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 83 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index 2948f883..d1fc3b8d 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -97,10 +97,12 @@ def read_span_stack_config yaml_config = read_span_stack_config_from_yaml if yaml_config - @config[:back_trace] = 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 @@ -137,10 +139,26 @@ def read_span_stack_config_from_agent(tracing_config) 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'] + + if 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 end # Read stack trace configuration from YAML file - # Returns hash with config or nil if not found + # Returns hash with :global and :technologies keys or nil if not found def read_span_stack_config_from_yaml config_path = ENV['INSTANA_CONFIG_PATH'] return nil unless config_path && File.exist?(config_path) @@ -152,21 +170,42 @@ def read_span_stack_config_from_yaml tracing_config = yaml_content['tracing'] || yaml_content['com.instana.tracing'] return nil unless tracing_config + result = {} + # Look for global stack trace configuration - global_config = tracing_config['global'] - return nil unless global_config - - stack_trace_level = global_config['stack-trace'] - stack_trace_length = global_config['stack-trace-length'] - - # Only return config if at least one value is present - if stack_trace_level || stack_trace_length - { - stack_trace_level: stack_trace_level || 'error', - stack_trace_length: stack_trace_length ? stack_trace_length.to_i : 30, - config_source: 'yaml' - } + 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'] + + if 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 + 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 @@ -194,6 +233,20 @@ def should_read_from_agent?(config_key) 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 From 6c887e5f9e0ca657dfac2325800d8f8293bb87f2 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Tue, 3 Feb 2026 12:21:32 +0530 Subject: [PATCH 15/19] feat(span_stack): test technology specific configs Signed-off-by: Arjun Rajappa --- test/config_test.rb | 187 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/test/config_test.rb b/test/config_test.rb index 311ecf4e..15720e95 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -492,4 +492,191 @@ def test_should_read_from_agent_returns_false_for_env_config ensure ENV.delete('INSTANA_STACK_TRACE') end + + # Tests for technology-specific stack trace configuration + + def test_read_span_stack_config_from_yaml_with_technology_specific_config + yaml_content = <<~YAML + com.instana.tracing: + global: + stack-trace: error + stack-trace-length: 25 + + kafka: + stack-trace: all + + redis: + stack-trace: all + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Check global config + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 25, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + + # Check technology-specific configs + assert_equal 'all', subject[:back_trace_technologies][:kafka][:stack_trace_level] + assert_equal 'all', subject[:back_trace_technologies][:redis][:stack_trace_level] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_span_stack_config_from_yaml_with_technology_specific_length + yaml_content = <<~YAML + tracing: + global: + stack-trace: error + stack-trace-length: 30 + + kafka: + stack-trace: all + stack-trace-length: 50 + + redis: + stack-trace-length: 10 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Check kafka config + kafka_config = subject[:back_trace_technologies][:kafka] + assert_equal 'all', kafka_config[:stack_trace_level] + assert_equal 50, kafka_config[:stack_trace_length] + + # Check redis config (only length specified) + redis_config = subject[:back_trace_technologies][:redis] + assert_nil redis_config[:stack_trace_level] + assert_equal 10, redis_config[:stack_trace_length] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_get_stack_trace_config_for_technology + yaml_content = <<~YAML + tracing: + global: + stack-trace: error + stack-trace-length: 30 + + kafka: + stack-trace: all + stack-trace-length: 50 + + redis: + stack-trace: all + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Test kafka - should use technology-specific config + kafka_config = subject.get_stack_trace_config(:kafka) + assert_equal 'all', kafka_config[:stack_trace_level] + assert_equal 50, kafka_config[:stack_trace_length] + + # Test redis - should use technology-specific level, global length + redis_config = subject.get_stack_trace_config(:redis) + assert_equal 'all', redis_config[:stack_trace_level] + assert_equal 30, redis_config[:stack_trace_length] + + # Test excon - should fall back to global config + excon_config = subject.get_stack_trace_config(:excon) + assert_equal 'error', excon_config[:stack_trace_level] + assert_equal 30, excon_config[:stack_trace_length] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end + + def test_read_config_from_agent_with_technology_specific_config + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace' => 'error', + 'stack-trace-length' => 30 + }, + 'kafka' => { + 'stack-trace' => 'all', + 'stack-trace-length' => 100 + }, + 'redis' => { + 'stack-trace' => 'all' + } + } + } + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + subject.read_config_from_agent(discovery) + + # Check global config + assert_equal 'error', subject[:back_trace][:stack_trace_level] + assert_equal 30, subject[:back_trace][:stack_trace_length] + assert_equal 'agent', subject[:back_trace][:config_source] + + # Check technology-specific configs + kafka_config = subject[:back_trace_technologies][:kafka] + assert_equal 'all', kafka_config[:stack_trace_level] + assert_equal 100, kafka_config[:stack_trace_length] + + redis_config = subject[:back_trace_technologies][:redis] + assert_equal 'all', redis_config[:stack_trace_level] + assert_nil redis_config[:stack_trace_length] + end + + def test_yaml_technology_config_not_overridden_by_agent + yaml_content = <<~YAML + tracing: + global: + stack-trace: none + stack-trace-length: 10 + + kafka: + stack-trace: error + stack-trace-length: 20 + YAML + + File.write('test_stack_config.yaml', yaml_content) + ENV['INSTANA_CONFIG_PATH'] = 'test_stack_config.yaml' + + subject = Instana::Config.new(logger: Logger.new('/dev/null')) + + # Try to override with agent config + discovery = { + 'tracing' => { + 'global' => { + 'stack-trace' => 'all', + 'stack-trace-length' => 100 + }, + 'kafka' => { + 'stack-trace' => 'all', + 'stack-trace-length' => 200 + } + } + } + subject.read_config_from_agent(discovery) + + # YAML should take precedence for both global and technology-specific + assert_equal 'none', subject[:back_trace][:stack_trace_level] + assert_equal 10, subject[:back_trace][:stack_trace_length] + assert_equal 'yaml', subject[:back_trace][:config_source] + + kafka_config = subject[:back_trace_technologies][:kafka] + assert_equal 'error', kafka_config[:stack_trace_level] + assert_equal 20, kafka_config[:stack_trace_length] + ensure + File.unlink('test_stack_config.yaml') if File.exist?('test_stack_config.yaml') + ENV.delete('INSTANA_CONFIG_PATH') + end end From 2a39b882e6a17a99895d67ca4990c387459f6f82 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Wed, 4 Feb 2026 15:46:41 +0530 Subject: [PATCH 16/19] feat(span_stack): modify span stack t6o consider technology specific config Signed-off-by: Arjun Rajappa --- lib/instana/trace/span.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/instana/trace/span.rb b/lib/instana/trace/span.rb index 693e8346..9289d111 100644 --- a/lib/instana/trace/span.rb +++ b/lib/instana/trace/span.rb @@ -77,7 +77,10 @@ def initialize(name, parent_ctx = nil, _context = nil, parent_span = nil, _kind # # @param limit [Integer] Limit the backtrace to the top frames # - def add_stack(span_stack_config: ::Instana.config[:back_trace], stack: Kernel.caller) + def add_stack(span_stack_config: nil, stack: Kernel.caller) + # Get technology-specific config if not provided + span_stack_config ||= get_span_stack_config + limit = span_stack_config[:stack_trace_length] cleaner = ::Instana.config[:backtrace_cleaner] stack = cleaner.call(stack) if cleaner @@ -533,7 +536,18 @@ def add_event(_name, attributes: nil, timestamp: nil) # rubocop:disable Lint/Unu def status=(status); end def should_collect_stack_trace? - ::Instana.config[:back_trace] && ::Instana.config[:back_trace][:stack_trace_level] == "all" && exit_span? + return false unless exit_span? + + config = get_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 get_span_stack_config + technology = @attributes[:n] + ::Instana.config.get_stack_trace_config(technology) end end end From c458aa7893d39dc2f16217157129e212eb929cd4 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Thu, 5 Feb 2026 12:32:45 +0530 Subject: [PATCH 17/19] feat(span_stack): fix rubocop linting failures Signed-off-by: Arjun Rajappa --- lib/instana/config.rb | 26 +++++++++++++------------- lib/instana/trace/span.rb | 6 +++--- test/config_test.rb | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/instana/config.rb b/lib/instana/config.rb index d1fc3b8d..8d41c2f2 100644 --- a/lib/instana/config.rb +++ b/lib/instana/config.rb @@ -148,18 +148,18 @@ def read_span_stack_config_from_agent(tracing_config) tech_stack_trace = value['stack-trace'] tech_stack_trace_length = value['stack-trace-length'] - if 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 + 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 + 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) @@ -195,12 +195,12 @@ def read_span_stack_config_from_yaml tech_stack_trace = value['stack-trace'] tech_stack_trace_length = value['stack-trace-length'] - if 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 + 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? diff --git a/lib/instana/trace/span.rb b/lib/instana/trace/span.rb index 9289d111..c856f250 100644 --- a/lib/instana/trace/span.rb +++ b/lib/instana/trace/span.rb @@ -79,7 +79,7 @@ def initialize(name, parent_ctx = nil, _context = nil, parent_span = nil, _kind # def add_stack(span_stack_config: nil, stack: Kernel.caller) # Get technology-specific config if not provided - span_stack_config ||= get_span_stack_config + span_stack_config ||= current_span_stack_config limit = span_stack_config[:stack_trace_length] cleaner = ::Instana.config[:backtrace_cleaner] @@ -538,14 +538,14 @@ def status=(status); end def should_collect_stack_trace? return false unless exit_span? - config = get_span_stack_config + 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 get_span_stack_config + def current_span_stack_config technology = @attributes[:n] ::Instana.config.get_stack_trace_config(technology) end diff --git a/test/config_test.rb b/test/config_test.rb index 15720e95..c04b650a 100644 --- a/test/config_test.rb +++ b/test/config_test.rb @@ -3,7 +3,7 @@ require 'test_helper' -class ConfigTest < Minitest::Test +class ConfigTest < Minitest::Test # rubocop:disable Metrics/ClassLength def test_that_config_exists refute_nil ::Instana.config assert_instance_of(::Instana::Config, ::Instana.config) From 1287b8b2397baec99e012c39987e19fac477db40 Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Tue, 10 Feb 2026 11:14:06 +0530 Subject: [PATCH 18/19] chore(currency): update tests to support dalli 5.0.0 Signed-off-by: Arjun Rajappa --- test/instrumentation/dalli_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/instrumentation/dalli_test.rb b/test/instrumentation/dalli_test.rb index 6658038b..9f39fe2c 100644 --- a/test/instrumentation/dalli_test.rb +++ b/test/instrumentation/dalli_test.rb @@ -188,7 +188,7 @@ def test_incr clear_all! result = nil - @dc.set(:counter, 0, nil, :raw => true) + @dc.set(:counter, '0', nil, :raw => true) ::Instana.tracer.in_span(:dalli_test) do result = @dc.incr(:counter, 1, nil, 0) end @@ -222,7 +222,7 @@ def test_decr clear_all! result = nil - @dc.set(:counter, 0, nil, :raw => true) + @dc.set(:counter, '0', nil, :raw => true) ::Instana.tracer.in_span(:dalli_test) do result = @dc.decr(:counter, 1, nil, 0) end From a6f8267d01e9673a3ae53422972d625c585b202f Mon Sep 17 00:00:00 2001 From: Arjun Rajappa Date: Tue, 10 Feb 2026 12:39:36 +0530 Subject: [PATCH 19/19] feat(span_stack): use new config structure for backtrace Signed-off-by: Arjun Rajappa --- test/instrumentation/rack_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/instrumentation/rack_test.rb b/test/instrumentation/rack_test.rb index 46c57767..47b4ae10 100644 --- a/test/instrumentation/rack_test.rb +++ b/test/instrumentation/rack_test.rb @@ -94,7 +94,7 @@ def test_basic_get assert !rack_span.key?(:stack) # Restore to default - ::Instana.config[:back_trace] = false + ::Instana.config[:back_trace][:stack_trace_level] = 'none' end def test_basic_get_with_custom_service_name @@ -258,7 +258,7 @@ def test_that_url_params_not_logged def test_custom_headers_capture clear_all! - ::Instana.config[:back_trace] = true + ::Instana.config[:back_trace][:stack_trace_level] = 'all' ::Instana.agent.define_singleton_method(:extra_headers) { %w(X-Capture-This X-Capture-That) } get '/mrlobster', {}, { "HTTP_X_CAPTURE_THIS" => "ThereYouGo" } @@ -278,7 +278,7 @@ def test_custom_headers_capture assert !rack_span.key?(:stack) # Restore to default - ::Instana.config[:back_trace] = false + ::Instana.config[:back_trace][:stack_trace_level] = 'none' ::Instana.agent.singleton_class.send(:remove_method, :extra_headers) end