class Mmtrix::Agent::ErrorCollector

This class collects errors from the parent application, storing them until they are harvested and transmitted to the server

Constants

DEPRECATED_OPTIONS
DEPRECATED_OPTIONS_MSG
EMPTY_STRING
EXCEPTION_TAG_IVAR
MAX_ERROR_QUEUE_LENGTH

Maximum possible length of the queue - defaults to 20, may be made configurable in the future. This is a tradeoff between memory and data retention

Attributes

errors[RW]

Public Class Methods

ignore_error_filter() click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 78
def self.ignore_error_filter
  @ignore_filter
end
ignore_error_filter=(block) click to toggle source

We store the passed block in both an ivar on the class, and implicitly within the body of the ignore_filter_proc method intentionally here. The define_method trick is needed to get around the fact that users may call ‘return’ from within their filter blocks, which would otherwise result in a LocalJumpError.

The raw block is also stored in an instance variable so that we can return it later in its original form.

This is all done at the class level in order to avoid the case where the user sets up an ignore filter on one instance of the ErrorCollector, and then that instance subsequently gets discarded during agent startup. (For example, if the agent is initially disabled, and then gets enabled via a call to manual_start later on.)

# File lib/mmtrix/agent/error_collector.rb, line 68
def self.ignore_error_filter=(block)
  @ignore_filter = block
  if block
    define_method(:ignore_filter_proc, &block)
  elsif method_defined?(:ignore_filter_proc)
    undef :ignore_filter_proc
  end
  @ignore_filter
end
new() click to toggle source

Returns a new error collector

# File lib/mmtrix/agent/error_collector.rb, line 21
def initialize
  @errors = []

  # lookup of exception class names to ignore.  Hash for fast access
  @ignore = {}

  initialize_ignored_errors(Agent.config[:'error_collector.ignore_errors'])
  @lock = Mutex.new

  Agent.config.register_callback(:'error_collector.enabled') do |config_enabled|
    ::Mmtrix::Agent.logger.debug "Errors will #{config_enabled ? '' : 'not '}be sent to the Mmtrix service."
  end
  Agent.config.register_callback(:'error_collector.ignore_errors') do |ignore_errors|
    initialize_ignored_errors(ignore_errors)
  end
end

Public Instance Methods

add_to_error_queue(noticed_error) click to toggle source

Synchronizes adding an error to the error queue, and checks if the error queue is too long - if so, we drop the error on the floor after logging a warning.

# File lib/mmtrix/agent/error_collector.rb, line 202
def add_to_error_queue(noticed_error)
  @lock.synchronize do
    if !over_queue_limit?(noticed_error.message) && !@errors.include?(noticed_error)
      @errors << noticed_error
    end
  end
end
aggregated_metric_names(txn) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 144
def aggregated_metric_names(txn)
  metric_names = ["Errors/all"]
  return metric_names unless txn

  if txn.recording_web_transaction?
    metric_names << "Errors/allWeb"
  else
    metric_names << "Errors/allOther"
  end

  metric_names
end
blamed_metric_name(txn, options) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 136
def blamed_metric_name(txn, options)
  if options[:metric] && options[:metric] != ::Mmtrix::Agent::UNKNOWN_METRIC
    "Errors/#{options[:metric]}"
  else
    "Errors/#{txn.best_name}" if txn
  end
end
create_noticed_error(exception, options) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 227
def create_noticed_error(exception, options)
  error_metric = options.delete(:metric) || EMPTY_STRING

  noticed_error = Mmtrix::NoticedError.new(error_metric, exception)
  noticed_error.request_uri = options.delete(:uri) || EMPTY_STRING
  noticed_error.attributes  = options.delete(:attributes)

  noticed_error.file_name   = sense_method(exception, :file_name)
  noticed_error.line_number = sense_method(exception, :line_number)
  noticed_error.stack_trace = extract_stack_trace(exception)

  handle_deprecated_options(options)

  noticed_error.attributes_from_notice_error = options.delete(:custom_params) || {}

  # Any options that are passed to notice_error which aren't known keys
  # get treated as custom attributes, so merge them into that hash.
  noticed_error.attributes_from_notice_error.merge!(options)

  noticed_error
end
disabled?() click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 49
def disabled?
  !enabled?
end
enabled?() click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 45
def enabled?
  Agent.config[:'error_collector.enabled']
end
error_is_ignored?(error) click to toggle source

an error is ignored if it is nil or if it is filtered

# File lib/mmtrix/agent/error_collector.rb, line 104
def error_is_ignored?(error)
  error && filtered_error?(error)
rescue => e
  Mmtrix::Agent.logger.error("Error '#{error}' will NOT be ignored. Exception '#{e}' while determining whether to ignore or not.", e)
  false
end
exception_is_java_object?(exception) click to toggle source

Calling instance_variable_set on a wrapped Java object in JRuby will generate a warning unless that object’s class has already been marked as persistent, so we skip tagging of exception objects that are actually wrapped Java objects on JRuby.

# File lib/mmtrix/agent/error_collector.rb, line 117
def exception_is_java_object?(exception)
  Mmtrix::LanguageSupport.jruby? && exception.respond_to?(:java_class)
end
exception_tagged?(exception) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 121
def exception_tagged?(exception)
  return false if exception_is_java_object?(exception)
  exception.instance_variable_get(EXCEPTION_TAG_IVAR)
end
extract_stack_trace(exception) click to toggle source

extracts a stack trace from the exception for debugging purposes

# File lib/mmtrix/agent/error_collector.rb, line 186
def extract_stack_trace(exception)
  actual_exception = sense_method(exception, 'original_exception') || exception
  sense_method(actual_exception, 'backtrace') || '<no stack trace>'
end
filtered_by_error_filter?(error) click to toggle source

Checks the provided error against the error filter, if there is an error filter

# File lib/mmtrix/agent/error_collector.rb, line 93
def filtered_by_error_filter?(error)
  respond_to?(:ignore_filter_proc) && !ignore_filter_proc(error)
end
filtered_error?(error) click to toggle source

Checks the array of error names and the error filter against the provided error

# File lib/mmtrix/agent/error_collector.rb, line 99
def filtered_error?(error)
  @ignore[error.class.name] || filtered_by_error_filter?(error)
end
handle_deprecated_options(options) click to toggle source

Old options no longer used by notice_error can still be passed. If they are, they shouldn’t get merged into custom attributes. Delete and warn callers so they can fix their calls to notice_error.

# File lib/mmtrix/agent/error_collector.rb, line 255
def handle_deprecated_options(options)
  DEPRECATED_OPTIONS.each do |deprecated|
    if options.include?(deprecated)
      Mmtrix::Agent.logger.warn(DEPRECATED_OPTIONS_MSG % deprecated)
      options.delete(deprecated)
    end
  end
end
harvest!() click to toggle source

Get the errors currently queued up. Unsent errors are left over from a previous unsuccessful attempt to send them to the server.

# File lib/mmtrix/agent/error_collector.rb, line 300
def harvest!
  @lock.synchronize do
    errors = @errors
    @errors = []
    errors
  end
end
ignore(errors) click to toggle source

errors is an array of Exception Class Names

# File lib/mmtrix/agent/error_collector.rb, line 84
def ignore(errors)
  errors.each do |error|
    @ignore[error] = true
    ::Mmtrix::Agent.logger.debug("Ignoring errors of type '#{error}'")
  end
end
increment_error_count!(state, exception, options={}) click to toggle source

Increments a statistic that tracks total error rate

# File lib/mmtrix/agent/error_collector.rb, line 158
def increment_error_count!(state, exception, options={})
  txn = state.current_transaction

  metric_names  = aggregated_metric_names(txn)
  blamed_metric = blamed_metric_name(txn, options)
  metric_names << blamed_metric if blamed_metric

  stats_engine = Mmtrix::Agent.agent.stats_engine
  stats_engine.record_unscoped_metrics(state, metric_names) do |stats|
    stats.increment_count
  end
end
initialize_ignored_errors(ignore_errors) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 38
def initialize_ignored_errors(ignore_errors)
  @ignore.clear
  ignore_errors = ignore_errors.split(",") if ignore_errors.is_a? String
  ignore_errors.each { |error| error.strip! }
  ignore(ignore_errors)
end
merge!(errors) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 292
def merge!(errors)
  errors.each do |error|
    add_to_error_queue(error)
  end
end
notice_agent_error(exception) click to toggle source

*Use sparingly for difficult to track bugs.*

Track internal agent errors for communication back to Mmtrix. To use, make a specific subclass of Mmtrix::Agent::InternalAgentError, then pass an instance of it to this method when your problem occurs.

Limits are treated differently for these errors. We only gather one per class per harvest, disregarding (and not impacting) the app error queue limit.

# File lib/mmtrix/agent/error_collector.rb, line 273
def notice_agent_error(exception)
  return unless exception.class < Mmtrix::Agent::InternalAgentError

  # Log 'em all!
  Mmtrix::Agent.logger.info(exception)

  @lock.synchronize do
    # Already seen this class once? Bail!
    return if @errors.any? { |err| err.exception_class_name == exception.class.name }

    trace = exception.backtrace || caller.dup
    noticed_error = Mmtrix::NoticedError.new("Mmtrix/AgentError", exception)
    noticed_error.stack_trace = trace
    @errors << noticed_error
  end
rescue => e
  Mmtrix::Agent.logger.info("Unable to capture internal agent error due to an exception:", e)
end
notice_error(exception, options={}) click to toggle source

See Mmtrix::Agent.notice_error for options and commentary

# File lib/mmtrix/agent/error_collector.rb, line 211
def notice_error(exception, options={}) #THREAD_LOCAL_ACCESS
  return if skip_notice_error?(exception)

  tag_exception(exception)

  state = ::Mmtrix::Agent::TransactionState.tl_get
  increment_error_count!(state, exception, options)
  add_to_error_queue(create_noticed_error(exception, options))

  exception
rescue => e
  ::Mmtrix::Agent.logger.warn("Failure when capturing error '#{exception}':", e)
end
over_queue_limit?(message) click to toggle source

checks the size of the error queue to make sure we are under the maximum limit, and logs a warning if we are over the limit.

# File lib/mmtrix/agent/error_collector.rb, line 193
def over_queue_limit?(message)
  over_limit = (@errors.reject{|err| err.is_internal}.length >= MAX_ERROR_QUEUE_LENGTH)
  ::Mmtrix::Agent.logger.warn("The error reporting queue has reached #{MAX_ERROR_QUEUE_LENGTH}. The error detail for this and subsequent errors will not be transmitted to Mmtrix until the queued errors have been sent: #{message}") if over_limit
  over_limit
end
reset!() click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 308
def reset!
  @lock.synchronize do
    @errors = []
  end
end
sense_method(object, method) click to toggle source

calls a method on an object, if it responds to it - used for detection and soft fail-safe. Returns nil if the method does not exist

# File lib/mmtrix/agent/error_collector.rb, line 181
def sense_method(object, method)
  object.send(method) if object.respond_to?(method)
end
skip_notice_error?(exception) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 171
def skip_notice_error?(exception)
  disabled? ||
    error_is_ignored?(exception) ||
    exception.nil? ||
    exception_tagged?(exception)
end
tag_exception(exception) click to toggle source
# File lib/mmtrix/agent/error_collector.rb, line 126
def tag_exception(exception)
  return if exception_is_java_object?(exception)
  return if exception.frozen?
  begin
    exception.instance_variable_set(EXCEPTION_TAG_IVAR, true)
  rescue => e
    Mmtrix::Agent.logger.warn("Failed to tag exception: #{exception}: ", e)
  end
end