class Mmtrix::Agent::Transaction::Attributes

Constants

CAN_BYTESLICE
COUNT_LIMIT
EMPTY_HASH
KEY_LIMIT
VALUE_LIMIT

Public Class Methods

new(filter) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 19
def initialize(filter)
  @filter = filter

  @custom_attributes = {}
  @agent_attributes = {}
  @intrinsic_attributes = {}

  @custom_destinations = {}
  @agent_destinations = {}
end

Public Instance Methods

add_agent_attribute(key, value, default_destinations) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 30
def add_agent_attribute(key, value, default_destinations)
  destinations = @filter.apply(key, default_destinations)
  return if destinations == AttributeFilter::DST_NONE

  @agent_destinations[key] = destinations
  add(@agent_attributes, key, value)
end
add_agent_attribute_with_key_check(key, value, default_destinations) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 38
def add_agent_attribute_with_key_check(key, value, default_destinations)
  if exceeds_bytesize_limit? key, KEY_LIMIT
    Mmtrix::Agent.logger.debug("Agent attribute #{key} was dropped for exceeding key length limit #{KEY_LIMIT}")
    return
  end

  add_agent_attribute(key, value, default_destinations)
end
add_intrinsic_attribute(key, value) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 47
def add_intrinsic_attribute(key, value)
  add(@intrinsic_attributes, key, value)
end
agent_attributes_for(destination) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 71
def agent_attributes_for(destination)
  for_destination(@agent_attributes, @agent_destinations, destination)
end
custom_attributes_for(destination) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 67
def custom_attributes_for(destination)
  for_destination(@custom_attributes, @custom_destinations, destination)
end
intrinsic_attributes_for(destination) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 75
def intrinsic_attributes_for(destination)
  if destination == Mmtrix::Agent::AttributeFilter::DST_TRANSACTION_TRACER ||
     destination == Mmtrix::Agent::AttributeFilter::DST_ERROR_COLLECTOR
    @intrinsic_attributes
  else
    EMPTY_HASH
  end
end
merge_custom_attributes(other) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 60
def merge_custom_attributes(other)
  return if other.empty?
  AttributeProcessing.flatten_and_coerce(other) do |k, v|
    add_custom_attribute(k, v)
  end
end
merge_untrusted_agent_attributes(attributes, prefix, default_destinations) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 51
def merge_untrusted_agent_attributes(attributes, prefix, default_destinations)
  return if @filter.high_security?
  return if !@filter.might_allow_prefix?(prefix)

  AttributeProcessing.flatten_and_coerce(attributes, prefix) do |k, v|
    add_agent_attribute_with_key_check(k, v, AttributeFilter::DST_NONE)
  end
end

Private Instance Methods

add(attributes, key, value) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 112
def add(attributes, key, value)
  return unless value

  if exceeds_bytesize_limit?(value, VALUE_LIMIT)
    value = slice(value)
  end

  attributes[key] = value
end
add_custom_attribute(key, value) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 86
def add_custom_attribute(key, value)
  if @custom_attributes.size >= COUNT_LIMIT
    unless @already_warned_count_limit
      Mmtrix::Agent.logger.warn("Custom attributes count exceeded limit of #{COUNT_LIMIT}. Any additional custom attributes during this transaction will be dropped.")
      @already_warned_count_limit = true
    end
    return
  end

  if @filter.high_security?
    Mmtrix::Agent.logger.debug("Unable to add custom attribute #{key} while in high security mode.")
    return
  end

  if exceeds_bytesize_limit?(key, KEY_LIMIT)
    Mmtrix::Agent.logger.warn("Custom attribute key '#{key}' was longer than limit of #{KEY_LIMIT} bytes. This attribute will be dropped.")
    return
  end

  destinations = @filter.apply(key, AttributeFilter::DST_ALL)
  return if destinations == AttributeFilter::DST_NONE

  @custom_destinations[key] = destinations
  add(@custom_attributes, key, value)
end
exceeds_bytesize_limit?(value, limit) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 136
def exceeds_bytesize_limit?(value, limit)
  if value.respond_to?(:bytesize)
    value.bytesize > limit
  elsif value.is_a?(Symbol)
    value.to_s.bytesize > limit
  else
    false
  end
end
for_destination(attributes, calculated_destinations, destination) click to toggle source
# File lib/mmtrix/agent/transaction/attributes.rb, line 122
def for_destination(attributes, calculated_destinations, destination)
  # Avoid allocating anything if there are no attrs at all
  return EMPTY_HASH if attributes.empty?

  return attributes.dup if destination == Mmtrix::Agent::AttributeFilter::DST_DEVELOPER_MODE

  attributes.inject({}) do |memo, (key, value)|
    if @filter.allows?(calculated_destinations[key], destination)
      memo[key] = value
    end
    memo
  end
end
slice(incoming) click to toggle source

Take one byte past our limit. Why? This lets us unconditionally chop! the end. It’ll either remove the one-character-too-many we have, or peel off the partial, mangled character left by the byteslice.

# File lib/mmtrix/agent/transaction/attributes.rb, line 149
def slice(incoming)
  if CAN_BYTESLICE
    result = incoming.to_s.byteslice(0, VALUE_LIMIT + 1)
  else
    # < 1.9.3 doesn't have byteslice, so we take off bytes instead.
    result = incoming.to_s.bytes.take(VALUE_LIMIT + 1).pack("C*")
  end

  result.chop!
  result
end