# encoding: utf-8 require 'ting_yun/agent' require 'ting_yun/agent/transaction/transaction_state' require 'ting_yun/agent/collector/error_collector/noticed_error' require 'ting_yun/agent/collector/error_collector/error_trace_array' module TingYun module Agent module Collector class ErrorCollector ERRORS_ACTION = "Errors/Count/".freeze ERRORS_ALL = "Errors/Count/All".freeze ERRORS_ALL_WEB = "Errors/Count/AllWeb".freeze ERRORS_ALL_BACK_GROUND = "Errors/Count/AllBackground".freeze # Maximum possible length of the queue - defaults to 20, may be MAX_ERROR_QUEUE_LENGTH = 20 unless defined? MAX_ERROR_QUEUE_LENGTH #tag the exception,avoid the same exception record multiple times in the middlwars and other point module Tag EXCEPTION_TAG_IVAR = :'@__ty_seen_exception' unless defined? EXCEPTION_TAG_IVAR def tag_exception(exception) begin exception.instance_variable_set(EXCEPTION_TAG_IVAR, true) rescue => e TingYun::Agent.logger.warn("Failed to tag exception: #{exception}: ", e) end end def exception_tagged?(exception) exception.instance_variable_defined?(EXCEPTION_TAG_IVAR) end end include Tag module Metric def aggregated_metric_names(txn) metric_names = [ERRORS_ALL] return metric_names unless txn if txn.recording_web_transaction? metric_names << ERRORS_ALL_WEB else metric_names << ERRORS_ALL_BACK_GROUND end metric_names end def action_metric_name(txn) "#{ERRORS_ACTION}#{txn.best_name}" if txn end end include Metric attr_reader :error_trace_array, :external_error_array def initialize @error_trace_array = ::TingYun::Agent::Collector::ErrorTraceArray.new(MAX_ERROR_QUEUE_LENGTH) @external_error_array = ::TingYun::Agent::Collector::ErrorTraceArray.new(MAX_ERROR_QUEUE_LENGTH) end # See TingYun::Agent.notice_error for options and commentary def notice_error(exception, options={}) tag_exception(exception) state = ::TingYun::Agent::TransactionState.tl_get increment_error_count!(state, exception, options) noticed_error = create_noticed_error(exception, options) if noticed_error.is_external_error external_error_array.add_to_error_queue(noticed_error) else error_trace_array.add_to_error_queue(noticed_error) end rescue => e ::TingYun::Agent.logger.warn("Failure when capturing error '#{exception}':", e) nil end # Increments a statistic that tracks total error rate def increment_error_count!(state, exception, options={}) txn = state.current_transaction metric_names = aggregated_metric_names(txn) action_metric = action_metric_name(txn) metric_names << action_metric if action_metric stats_engine = TingYun::Agent.agent.stats_engine stats_engine.record_unscoped_metrics(state, metric_names) do |stats| stats.increment_count end end EMPTY_STRING = ''.freeze def create_noticed_error(exception, options) error_metric = options.delete(:metric_name) || EMPTY_STRING noticed_error = TingYun::Agent::Collector::NoticedError.new(error_metric, exception) noticed_error.request_uri = options.delete(:uri) || EMPTY_STRING noticed_error.request_port = options.delete(:port) 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) 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 def skip_notice_error?(exception) exception_tagged?(exception) end # 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 def sense_method(object, method) object.send(method) if object.respond_to?(method) end # extracts a stack trace from the exception for debugging purposes def extract_stack_trace(exception) actual_exception = sense_method(exception, 'original_exception') || exception sense_method(actual_exception, 'backtrace') || '' end # *Use sparingly for difficult to track bugs.* # # Track internal agent errors for communication back to TingYun # To use, make a specific subclass of TingYun::Support::Exception::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. def notice_agent_error(exception) error_trace_array.notice_agent_error(exception) end def reset! @error_trace_array.reset! @external_error_array.reset! nil end end end end end