class NewRelic::Agent::Configuration::Manager
Constants
- DEPENDENCY_DETECTION_VALUES
- MALFORMED_LABELS_WARNING
- MAX_LABEL_COUNT
- MAX_LABEL_LENGTH
- PARSING_LABELS_FAILURE
Public Class Methods
# File lib/new_relic/agent/configuration/manager.rb, line 34 def initialize reset_to_defaults @callbacks = Hash.new { |hash, key| hash[key] = [] } @lock = Mutex.new end
Public Instance Methods
Defining these explicitly saves object allocations that we incur if we use Forwardable and def_delegators.
# File lib/new_relic/agent/configuration/manager.rb, line 22 def [](key) @cache[key] end
# File lib/new_relic/agent/configuration/manager.rb, line 40 def add_config_for_testing(source, level = 0) raise 'Invalid config type for testing' unless [Hash, DottedHash].include?(source.class) invoke_callbacks(:add, source) @configs_for_testing << [source.freeze, level] reset_cache log_config(:add, source) end
# File lib/new_relic/agent/configuration/manager.rb, line 258 def apply_mask(hash) MASK_DEFAULTS \ .select { |_, proc| proc.call } \ .each { |key, _| hash.delete(key) } hash end
# File lib/new_relic/agent/configuration/manager.rb, line 151 def apply_transformations(key, value) if transform = transform_from_default(key) begin transform.call(value) rescue => e NewRelic::Agent.logger.error("Error applying transformation for #{key}, pre-transform value was: #{value}.", e) raise e end else value end end
# File lib/new_relic/agent/configuration/manager.rb, line 303 def break_label_string_into_pairs(labels) stripped_labels = labels.strip.sub(/^;*/, '').sub(/;*$/, '') stripped_labels.split(';').map do |pair| pair.split(':').map(&:strip) end end
# File lib/new_relic/agent/configuration/manager.rb, line 447 def config_classes_for_testing config_stack.map(&:class) end
# File lib/new_relic/agent/configuration/manager.rb, line 189 def default_source NewRelic::Agent::Configuration::DefaultSource end
# File lib/new_relic/agent/configuration/manager.rb, line 432 def delete_all_configs_for_testing @security_policy_source = nil @high_security_source = nil @environment_source = nil @server_source = nil @manual_source = nil @yaml_source = nil @default_source = nil @configs_for_testing = [] end
# File lib/new_relic/agent/configuration/manager.rb, line 164 def enforce_allowlist(key, value) return unless allowlist = default_source.allowlist_for(key) return if allowlist.include?(value) default = default_source.default_for(key) NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'" default end
# File lib/new_relic/agent/configuration/manager.rb, line 173 def enforce_boolean(key, value) type = default_source.value_from_defaults(key, :type) return unless type == Boolean bool_value = default_source.boolean_for(key, value) return bool_value unless bool_value.nil? default = default_source.default_for(key) NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'" default end
# File lib/new_relic/agent/configuration/manager.rb, line 140 def evaluate_and_apply_transformations(key, value) evaluated = evaluate_procs(value) default = enforce_allowlist(key, evaluated) return default if default boolean = enforce_boolean(key, value) evaluated = boolean if [true, false].include?(boolean) apply_transformations(key, evaluated) end
# File lib/new_relic/agent/configuration/manager.rb, line 132 def evaluate_procs(value) if value.respond_to?(:call) instance_eval(&value) else value end end
# File lib/new_relic/agent/configuration/manager.rb, line 114 def fetch(key) config_stack.each do |config| next unless config accessor = key.to_sym if config.has_key?(accessor) begin return evaluate_and_apply_transformations(accessor, config[accessor]) rescue next end end end nil end
# File lib/new_relic/agent/configuration/manager.rb, line 238 def finished_configuring? !@server_source.nil? end
# File lib/new_relic/agent/configuration/manager.rb, line 242 def flattened config_stack.reverse.inject({}) do |flat, layer| thawed_layer = layer.to_hash.dup thawed_layer.each do |k, v| begin thawed_layer[k] = instance_eval(&v) if v.respond_to?(:call) rescue => e NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} - when accessing config key #{k}") thawed_layer[k] = nil end thawed_layer.delete(:config) end flat.merge(thawed_layer.to_hash) end end
# File lib/new_relic/agent/configuration/manager.rb, line 26 def has_key?(key) @cache.has_key?(key) end
# File lib/new_relic/agent/configuration/manager.rb, line 198 def invoke_callbacks(direction, source) return unless source source.keys.each do |key| begin # we need to evaluate and apply transformations for the value to deal with procs as values # this is usually done by the fetch method when accessing config, however the callbacks bypass that evaluated_cache = evaluate_and_apply_transformations(key, @cache[key]) evaluated_source = evaluate_and_apply_transformations(key, source[key]) rescue next end if evaluated_cache != evaluated_source @callbacks[key].each do |proc| if direction == :add proc.call(evaluated_source) else proc.call(evaluated_cache) end end end end end
# File lib/new_relic/agent/configuration/manager.rb, line 30 def keys @cache.keys end
# File lib/new_relic/agent/configuration/manager.rb, line 361 def limit_number_of_labels(pairs) if pairs.length > MAX_LABEL_COUNT NewRelic::Agent.logger.warn("Too many labels defined. Only taking first #{MAX_LABEL_COUNT}") pairs[0...64] else pairs end end
# File lib/new_relic/agent/configuration/manager.rb, line 421 def log_config(direction, source) # Just generating this log message (specifically calling `flattened`) # is expensive enough that we don't want to do it unless we're # actually going to be logging the message based on our current log # level, so use a `do` block. NewRelic::Agent.logger.debug do hash = flattened.delete_if { |k, _h| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) } "Updating config (#{direction}) from #{source.class}. Results: #{hash.inspect}" end end
# File lib/new_relic/agent/configuration/manager.rb, line 328 def make_label_hash(pairs, labels = nil) # This can accept a hash, so force it down to an array of pairs first pairs = Array(pairs) unless valid_label_pairs?(pairs) NewRelic::Agent.logger.warn("#{MALFORMED_LABELS_WARNING}: #{labels || pairs}") return NewRelic::EMPTY_ARRAY end pairs = limit_number_of_labels(pairs) pairs = remove_duplicates(pairs) pairs.map do |key, value| { 'label_type' => truncate(key), 'label_value' => truncate(value.to_s, key) } end end
# File lib/new_relic/agent/configuration/manager.rb, line 417 def new_cache @cache = Hash.new { |hash, key| hash[key] = self.fetch(key) } end
This event is intended to be fired once during the entire lifespan of an agent run, after the server source has been applied for the first time. This should indicate that all configuration has been applied, and the main functions of the agent are safe to start.
# File lib/new_relic/agent/configuration/manager.rb, line 234 def notify_finished_configuring NewRelic::Agent.instance.events.notify(:initial_configuration_complete) end
This event is intended to be fired every time the server source is applied. This happens after the agent’s initial connect, and again on every forced reconnect.
# File lib/new_relic/agent/configuration/manager.rb, line 226 def notify_server_source_added NewRelic::Agent.instance.events.notify(:server_source_configuration_added) end
# File lib/new_relic/agent/configuration/manager.rb, line 443 def num_configs_for_testing config_stack.size end
# File lib/new_relic/agent/configuration/manager.rb, line 376 def parse_labels_from_dictionary make_label_hash(NewRelic::Agent.config[:labels]) end
# File lib/new_relic/agent/configuration/manager.rb, line 297 def parse_labels_from_string labels = NewRelic::Agent.config[:labels] label_pairs = break_label_string_into_pairs(labels) make_label_hash(label_pairs, labels) end
# File lib/new_relic/agent/configuration/manager.rb, line 285 def parsed_labels case NewRelic::Agent.config[:labels] when String parse_labels_from_string else parse_labels_from_dictionary end rescue => e NewRelic::Agent.logger.error(PARSING_LABELS_FAILURE, e) NewRelic::EMPTY_ARRAY end
# File lib/new_relic/agent/configuration/manager.rb, line 193 def register_callback(key, &proc) @callbacks[key] << proc yield(@cache[key]) end
# File lib/new_relic/agent/configuration/manager.rb, line 63 def remove_config(source) case source when SecurityPolicySource then @security_policy_source = nil when HighSecuritySource then @high_security_source = nil when EnvironmentSource then @environment_source = nil when ServerSource then @server_source = nil when ManualSource then @manual_source = nil when YamlSource then @yaml_source = nil when DefaultSource then @default_source = nil else @configs_for_testing.delete_if { |src, lvl| src == source } end reset_cache invoke_callbacks(:remove, source) log_config(:remove, source) end
# File lib/new_relic/agent/configuration/manager.rb, line 49 def remove_config_type(sym) source = case sym when :security_policy then @security_policy_source when :high_security then @high_security_source when :environment then @environment_source when :server then @server_source when :manual then @manual_source when :yaml then @yaml_source when :default then @default_source end remove_config(source) end
We only take the last value provided for a given label type key
# File lib/new_relic/agent/configuration/manager.rb, line 371 def remove_duplicates(pairs) grouped_by_type = pairs.group_by(&:first) grouped_by_type.values.map(&:last) end
# File lib/new_relic/agent/configuration/manager.rb, line 81 def replace_or_add_config(source) source.freeze was_finished = finished_configuring? invoke_callbacks(:add, source) case source when SecurityPolicySource then @security_policy_source = source when HighSecuritySource then @high_security_source = source when EnvironmentSource then @environment_source = source when ServerSource then @server_source = source when ManualSource then @manual_source = source when YamlSource then @yaml_source = source when DefaultSource then @default_source = source else NewRelic::Agent.logger.warn("Invalid config format; config will be ignored: #{source}") end reset_cache log_config(:add, source) notify_server_source_added if ServerSource === source notify_finished_configuring if !was_finished && finished_configuring? end
reset the configuration hash, but do not replace previously auto determined dependency detection values with nil or ‘auto’
# File lib/new_relic/agent/configuration/manager.rb, line 397 def reset_cache return new_cache unless defined?(@cache) && @cache # Modifying the @cache hash under JRuby - even with a `synchronize do` # block and a `Hash#dup` operation - has been known to cause issues # with JRuby for concurrent access of the hash while it is being # modified. The hash really only needs to be modified for the benefit # of the security agent, so if JRuby is in play and the security agent # is not, don't attempt to modify the hash at all and return early. return new_cache if NewRelic::LanguageSupport.jruby? && !Agent.config[:'security.agent.enabled'] @lock.synchronize do preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) } new_cache preserved.each { |k, v| @cache[k] = v } end @cache end
Generally only useful during initial construction and tests
# File lib/new_relic/agent/configuration/manager.rb, line 381 def reset_to_defaults @security_policy_source = nil @high_security_source = nil @environment_source = EnvironmentSource.new @server_source = nil @manual_source = nil @yaml_source = nil @default_source = DefaultSource.new @configs_for_testing = [] reset_cache end
# File lib/new_relic/agent/configuration/manager.rb, line 106 def source(key) config_stack.each do |config| if config.respond_to?(key.to_sym) || config.has_key?(key.to_sym) return config end end end
# File lib/new_relic/agent/configuration/manager.rb, line 265 def to_collector_hash DottedHash.new(apply_mask(flattened)).to_hash.delete_if do |k, _v| default = DEFAULTS[k] if default default[:exclude_from_reported_settings] else # In our tests, we add totally bogus configs, because testing. # In those cases, there will be no default. So we'll just let # them through. false end end end
# File lib/new_relic/agent/configuration/manager.rb, line 185 def transform_from_default(key) default_source.transform_for(key) end
# File lib/new_relic/agent/configuration/manager.rb, line 347 def truncate(text, key = nil) if text.length > MAX_LABEL_LENGTH if key msg = "The value for the label '#{key}' is longer than the allowed #{MAX_LABEL_LENGTH} and will be truncated. Value = '#{text}'" else msg = "Label name longer than the allowed #{MAX_LABEL_LENGTH} will be truncated. Name = '#{text}'" end NewRelic::Agent.logger.warn(msg) text[0..MAX_LABEL_LENGTH - 1] else text end end
# File lib/new_relic/agent/configuration/manager.rb, line 318 def valid_label_item?(item) case item when String then !item.empty? when Numeric then true when true then true when false then true else false end end
# File lib/new_relic/agent/configuration/manager.rb, line 310 def valid_label_pairs?(label_pairs) label_pairs.all? do |pair| pair.length == 2 && valid_label_item?(pair.first) && valid_label_item?(pair.last) end end
Private Instance Methods
# File lib/new_relic/agent/configuration/manager.rb, line 453 def config_stack stack = [@security_policy_source, @high_security_source, @environment_source, @server_source, @manual_source, @yaml_source, @default_source] stack.compact! @configs_for_testing.each do |config, at_start| if at_start stack.insert(0, config) else stack.push(config) end end stack end