Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 49 additions & 0 deletions lib/instana/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@
# W3C Trace Context Support
@config[:w3c_trace_correlation] = ENV.fetch('INSTANA_DISABLE_W3C_TRACE_CORRELATION', nil).nil?

# Server-Timing response header injection (default: injected)
# Priority: YAML > env var > agent config > default (false)
read_disable_server_timing_config

# HTTP exit span 4xx error classification (opt-in, default: off)
# Priority: env vars > agent config > default (false / [])
read_http_exit_config
Expand Down Expand Up @@ -151,6 +155,8 @@
read_otlp_config_from_agent(tracing_config) if should_read_from_agent?(:otlp)
# Read HTTP exit 4xx classification from agent if env vars did not already set it
read_http_exit_classification_from_agent(tracing_config) unless env_http_exit_classification_set?
# Read Server-Timing disable flag from agent if not already set from YAML or env
read_disable_server_timing_from_agent(tracing_config) if @config[:disable_server_timing_source] == 'default'
# Read span filtering configuration from agent
::Instana.span_filtering_config&.read_config_from_agent(discovery)
rescue => e
Expand Down Expand Up @@ -268,6 +274,49 @@

private

# Read disable-server-timing from YAML (tracing.global) or INSTANA_TRACING_DISABLE_SERVER_TIMING.
# Agent value is applied later via read_config_from_agent if neither is set.
def read_disable_server_timing_config
@config[:disable_server_timing] = false
@config[:disable_server_timing_source] = 'default'

yaml_value = parse_disable_server_timing_from_yaml
unless yaml_value.nil?
@config[:disable_server_timing] = truthy?(yaml_value)
@config[:disable_server_timing_source] = 'yaml'
return
end

env_value = ENV.fetch('INSTANA_TRACING_DISABLE_SERVER_TIMING', nil)
return if env_value.nil? || env_value.strip.empty?

@config[:disable_server_timing] = truthy?(env_value.strip)
@config[:disable_server_timing_source] = 'env'
end

# @return [Object, nil] raw YAML value of tracing.global.disable-server-timing, or nil if not set
def parse_disable_server_timing_from_yaml
config_path = ENV.fetch('INSTANA_CONFIG_PATH', nil)
return nil unless config_path && File.exist?(config_path)

yaml_content = YAML.safe_load(File.read(config_path))
tracing_config = yaml_content[TRACING_KEY] || yaml_content[LEGACY_TRACING_KEY]
global_config = tracing_config && tracing_config['global']
global_config.is_a?(Hash) ? global_config['disable-server-timing'] : nil

Check failure on line 305 in lib/instana/config.rb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "disable-server-timing" 3 times.

See more on https://sonarcloud.io/project/issues?id=instana_ruby-sensor&issues=AaF2ghDUF0XFUMkHt4qu&open=AaF2ghDUF0XFUMkHt4qu&pullRequest=539
rescue => e
::Instana.logger.warn("Failed to load disable-server-timing configuration from YAML: #{e.message}")
nil
end

# @param tracing_config [Hash] the tracing sub-hash from the discovery payload
def read_disable_server_timing_from_agent(tracing_config)
global_config = tracing_config['global']
return unless global_config.is_a?(Hash) && !global_config['disable-server-timing'].nil?

@config[:disable_server_timing] = truthy?(global_config['disable-server-timing'])
@config[:disable_server_timing_source] = 'agent'
end

# Read HTTP exit 4xx error classification config (wrapper — same pattern as read_span_stack_config / read_otlp_config)
def read_http_exit_config
@config[:http_exit_classify_all_4xx_as_errors] = false
Expand Down
2 changes: 2 additions & 0 deletions lib/instana/instrumentation/rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def set_response_headers(headers, trace_context)
end

headers['Traceparent'] = trace_context.trace_parent_header
return if ::Instana.config[:disable_server_timing]

headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}"
end
end
Expand Down
96 changes: 96 additions & 0 deletions test/config_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1283,3 +1283,99 @@ def test_agent_config_ignores_invalid_codes
assert_equal [401], subject[:http_exit_classify_as_errors]
end
end

class DisableServerTimingConfigTest < Minitest::Test
YAML_FILE = 'test_disable_server_timing.yaml'.freeze

def logger
Logger.new('/dev/null')
end

def setup
ENV.delete('INSTANA_TRACING_DISABLE_SERVER_TIMING')
ENV.delete('INSTANA_CONFIG_PATH')
end

def teardown
ENV.delete('INSTANA_TRACING_DISABLE_SERVER_TIMING')
ENV.delete('INSTANA_CONFIG_PATH')
File.unlink(YAML_FILE) if File.exist?(YAML_FILE)
end

def write_yaml(top_key, value)
File.write(YAML_FILE, "#{top_key}:\n global:\n disable-server-timing: #{value}\n")
ENV['INSTANA_CONFIG_PATH'] = YAML_FILE
end

def agent_discovery(value)
{ 'tracing' => { 'global' => { 'disable-server-timing' => value } } }
end

def test_default_is_false
subject = Instana::Config.new(logger: logger)
assert_equal false, subject[:disable_server_timing]
end

def test_env_truthy_values
%w[true 1 TRUE yes].each do |value|
ENV['INSTANA_TRACING_DISABLE_SERVER_TIMING'] = value
assert_equal true, Instana::Config.new(logger: logger)[:disable_server_timing], "expected #{value} to be truthy"
end
end

def test_env_falsy_or_empty_values
['false', '0', ''].each do |value|
ENV['INSTANA_TRACING_DISABLE_SERVER_TIMING'] = value
assert_equal false, Instana::Config.new(logger: logger)[:disable_server_timing], "expected #{value.inspect} to be falsy"
end
end

def test_yaml_tracing_key
write_yaml('tracing', 'true')
assert_equal true, Instana::Config.new(logger: logger)[:disable_server_timing]
end

def test_yaml_legacy_key
write_yaml('com.instana.tracing', 'true')
assert_equal true, Instana::Config.new(logger: logger)[:disable_server_timing]
end

def test_yaml_takes_precedence_over_env
write_yaml('tracing', 'false')
ENV['INSTANA_TRACING_DISABLE_SERVER_TIMING'] = 'true'
assert_equal false, Instana::Config.new(logger: logger)[:disable_server_timing]
end

def test_invalid_yaml_falls_back_to_env
File.write(YAML_FILE, "tracing: [unclosed")
ENV['INSTANA_CONFIG_PATH'] = YAML_FILE
ENV['INSTANA_TRACING_DISABLE_SERVER_TIMING'] = 'true'
assert_equal true, Instana::Config.new(logger: logger)[:disable_server_timing]
end

def test_agent_sets_value_when_nothing_else_configured
subject = Instana::Config.new(logger: logger)
subject.read_config_from_agent(agent_discovery(true))
assert_equal true, subject[:disable_server_timing]
end

def test_env_takes_precedence_over_agent
ENV['INSTANA_TRACING_DISABLE_SERVER_TIMING'] = 'true'
subject = Instana::Config.new(logger: logger)
subject.read_config_from_agent(agent_discovery(false))
assert_equal true, subject[:disable_server_timing]
end

def test_yaml_takes_precedence_over_agent
write_yaml('tracing', 'true')
subject = Instana::Config.new(logger: logger)
subject.read_config_from_agent(agent_discovery(false))
assert_equal true, subject[:disable_server_timing]
end

def test_agent_without_key_keeps_default
subject = Instana::Config.new(logger: logger)
subject.read_config_from_agent('tracing' => { 'global' => { 'stack-trace' => 'all' } })
assert_equal false, subject[:disable_server_timing]
end
end
33 changes: 33 additions & 0 deletions test/instrumentation/rack_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ def call(_env)
end
end

class ServerTimingApp
def call(_env)
[200, { 'Server-Timing' => 'db;dur=53' }, ['Ok']]
end
end

class NoHeadersApp
def call(_env)
[501, nil, ['No']]
Expand All @@ -53,9 +59,36 @@ def app
map("/path_tpl") { run PathTemplateApp.new }
map("/error") { run ErrorApp.new }
map("/five_zero_one") { run FiveZeroOneApp.new }
map("/server_timing") { run ServerTimingApp.new }
end
end

def test_server_timing_not_added_when_disabled
clear_all!
::Instana.config[:disable_server_timing] = true

get '/mrlobster'
assert last_response.ok?

assert !last_response.headers.key?("Server-Timing")
assert last_response.headers.key?("X-Instana-T")
assert last_response.headers.key?("Traceparent")
ensure
::Instana.config[:disable_server_timing] = false
end

def test_existing_server_timing_preserved_when_disabled
clear_all!
::Instana.config[:disable_server_timing] = true

get '/server_timing'
assert last_response.ok?

assert_equal 'db;dur=53', last_response.headers["Server-Timing"]
ensure
::Instana.config[:disable_server_timing] = false
end

def test_basic_get
clear_all!
Instana.config[:back_trace][:stack_trace_level] = "all"
Expand Down
Loading