class NewRelic::Agent::AttributeFilter

Constants

DST_ALL
DST_BROWSER_MONITORING
DST_ERROR_COLLECTOR
DST_NONE
DST_SPAN_EVENTS
DST_TRANSACTION_EVENTS
DST_TRANSACTION_SEGMENTS
DST_TRANSACTION_TRACER

Attributes

rules[R]

Public Class Methods

new(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 78
def initialize(config)
  prep_enabled_destinations(config)
  prep_rules(config)

  # We're ok to cache high security for fast lookup because the attribute
  # filter is re-generated on any significant config change.
  @high_security = config[:high_security]

  setup_key_cache
  cache_prefix_denylist
end

Public Instance Methods

allows?(allowed_destinations, requested_destination) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 211
def allows?(allowed_destinations, requested_destination)
  allowed_destinations & requested_destination == requested_destination
end
allows_key?(key, destination) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 215
def allows_key?(key, destination)
  return false unless destination & @enabled_destinations == destination

  value = @key_cache[destination][key]

  if value.nil?
    allowed_destinations = apply(key, destination)
    @key_cache[destination][key] = allows?(allowed_destinations, destination)
  else
    value
  end
end
apply(attribute_name, default_destinations) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 192
def apply(attribute_name, default_destinations)
  return DST_NONE if @enabled_destinations == DST_NONE

  destinations = default_destinations
  attribute_name = attribute_name.to_s

  @rules.each do |rule|
    if rule.match?(attribute_name)
      if rule.is_include
        destinations |= rule.destinations
      else
        destinations &= rule.destinations
      end
    end
  end

  destinations & @enabled_destinations
end
build_rule(attribute_names, destinations, is_include) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 177
def build_rule(attribute_names, destinations, is_include)
  attribute_names.each do |attribute_name|
    rule = AttributeFilterRule.new(attribute_name, destinations, is_include)
    @rules << rule unless rule.empty?
  end
end
build_uri_rule(excluded_attributes) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 184
def build_uri_rule(excluded_attributes)
  uri_aliases = %w[uri url request_uri request.uri http.url]

  if (excluded_attributes & uri_aliases).size > 0
    build_rule(uri_aliases - excluded_attributes, DST_ALL, false)
  end
end
cache_prefix_denylist() click to toggle source

For attribute prefixes where we know the default destinations will always be DST_NONE, we can statically determine that any attribute starting with the prefix will not be allowed unless there’s an include rule that might match attributes starting with it.

This allows us to skip significant preprocessing work (hash/array flattening and type coercion) for HTTP request parameters and job arguments for Sidekiq and Resque in the common case, since none of these attributes are captured by default.

# File lib/new_relic/agent/attribute_filter.rb, line 242
def cache_prefix_denylist
  @prefix_denylist = {}
  @prefix_denylist[:'request.parameters'] = true unless might_allow_prefix_uncached?(:'request.parameters')
  @prefix_denylist[:'job.sidekiq.args'] = true unless might_allow_prefix_uncached?(:'job.sidekiq.args')
  @prefix_denylist[:'job.resque.args'] = true unless might_allow_prefix_uncached?(:'job.resque.args')
end
enabled_destinations_for_attributes(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 94
def enabled_destinations_for_attributes(config)
  destinations = DST_NONE
  destinations |= DST_TRANSACTION_TRACER if config[:'transaction_tracer.attributes.enabled']
  destinations |= DST_TRANSACTION_EVENTS if config[:'transaction_events.attributes.enabled']
  destinations |= DST_ERROR_COLLECTOR if config[:'error_collector.attributes.enabled']
  destinations |= DST_BROWSER_MONITORING if config[:'browser_monitoring.attributes.enabled']
  destinations |= DST_SPAN_EVENTS if config[:'span_events.attributes.enabled']
  destinations |= DST_TRANSACTION_SEGMENTS if config[:'transaction_segments.attributes.enabled']
  destinations
end
high_security?() click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 228
def high_security?
  @high_security
end
include_destinations_for_capture_params(capturing) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 169
def include_destinations_for_capture_params(capturing)
  if capturing
    DST_TRANSACTION_TRACER | DST_ERROR_COLLECTOR
  else
    DST_NONE
  end
end
might_allow_prefix?(prefix) click to toggle source

Note that the given prefix must be a Symbol

# File lib/new_relic/agent/attribute_filter.rb, line 250
def might_allow_prefix?(prefix)
  !@prefix_denylist.include?(prefix)
end
might_allow_prefix_uncached?(prefix) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 254
def might_allow_prefix_uncached?(prefix)
  prefix = prefix.to_s
  @rules.any? do |rule|
    if rule.is_include
      if rule.wildcard
        if rule.attribute_name.size > prefix.size
          rule.attribute_name.start_with?(prefix)
        else
          prefix.start_with?(rule.attribute_name)
        end
      else
        rule.attribute_name.start_with?(prefix)
      end
    end
  end
end
prep_attributes_exclude_rules(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 115
def prep_attributes_exclude_rules(config)
  build_rule(config[:'attributes.exclude'], DST_ALL, false)
  build_rule(config[:'transaction_tracer.attributes.exclude'], DST_TRANSACTION_TRACER, false)
  build_rule(config[:'transaction_events.attributes.exclude'], DST_TRANSACTION_EVENTS, false)
  build_rule(config[:'error_collector.attributes.exclude'], DST_ERROR_COLLECTOR, false)
  build_rule(config[:'browser_monitoring.attributes.exclude'], DST_BROWSER_MONITORING, false)
  build_rule(config[:'span_events.attributes.exclude'], DST_SPAN_EVENTS, false)
  build_rule(config[:'transaction_segments.attributes.exclude'], DST_TRANSACTION_SEGMENTS, false)
end
prep_attributes_include_rules(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 134
def prep_attributes_include_rules(config)
  build_rule(config[:'attributes.include'], DST_ALL, true)
  build_rule(config[:'transaction_tracer.attributes.include'], DST_TRANSACTION_TRACER, true)
  build_rule(config[:'transaction_events.attributes.include'], DST_TRANSACTION_EVENTS, true)
  build_rule(config[:'error_collector.attributes.include'], DST_ERROR_COLLECTOR, true)
  build_rule(config[:'browser_monitoring.attributes.include'], DST_BROWSER_MONITORING, true)
  build_rule(config[:'span_events.attributes.include'], DST_SPAN_EVENTS, true)
  build_rule(config[:'transaction_segments.attributes.include'], DST_TRANSACTION_SEGMENTS, true)
end
prep_capture_params_rules(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 125
def prep_capture_params_rules(config)
  build_rule(['request.parameters.*'], include_destinations_for_capture_params(config[:capture_params]), true)
end
prep_datastore_rules(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 129
def prep_datastore_rules(config)
  build_rule(%w[host port_path_or_id], DST_TRANSACTION_SEGMENTS, config[:'datastore_tracer.instance_reporting.enabled'])
  build_rule(['database_name'], DST_TRANSACTION_SEGMENTS, config[:'datastore_tracer.database_name_reporting.enabled'])
end
prep_enabled_destinations(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 90
def prep_enabled_destinations(config)
  @enabled_destinations = config[:'attributes.enabled'] ? enabled_destinations_for_attributes(config) : DST_NONE
end
prep_rules(config) click to toggle source
# File lib/new_relic/agent/attribute_filter.rb, line 105
def prep_rules(config)
  @rules = []
  prep_attributes_exclude_rules(config)
  prep_capture_params_rules(config)
  prep_datastore_rules(config)
  prep_attributes_include_rules(config)
  build_uri_rule(config[:'attributes.exclude'])
  @rules.sort!
end
setup_key_cache() click to toggle source

Note the key_cache is a global cache, accessible by multiple threads, but is intentionally left unsynchronized for liveness. Writes will always involve writing the same boolean value for each key, so there is no worry of one value clobbering another. For reads, if a value hasn’t been written to the cache yet, the worst that will happen is that it will run through the filter rules again. Both reads and writes will become eventually consistent.

# File lib/new_relic/agent/attribute_filter.rb, line 152
def setup_key_cache
  destinations = [
    DST_TRANSACTION_EVENTS,
    DST_TRANSACTION_TRACER,
    DST_ERROR_COLLECTOR,
    DST_BROWSER_MONITORING,
    DST_SPAN_EVENTS,
    DST_TRANSACTION_SEGMENTS,
    DST_ALL
  ]

  @key_cache = destinations.inject({}) do |memo, destination|
    memo[destination] = {}
    memo
  end
end