module NewRelic::Agent::Instrumentation::ControllerInstrumentation

NewRelic instrumentation for controller actions and tasks

This module can also be used to capture performance information for background tasks and other non-web transactions, including detailed transaction traces and traced errors.

For details on how to instrument background tasks see {ClassMethods#add_transaction_tracer} and {#perform_action_with_newrelic_trace}

@api public

Constants

NR_DEFAULT_OPTIONS
NR_DO_NOT_TRACE_KEY
NR_IGNORE_APDEX_KEY
NR_IGNORE_ENDUSER_KEY

Public Instance Methods

perform_action_with_newrelic_trace(*args) { || ... } click to toggle source

Yield to the given block with NewRelic tracing. Used by default instrumentation on controller actions in Rails. But it can also be used in custom instrumentation of controller methods and background tasks.

This is the method invoked by instrumentation added by the ClassMethods#add_transaction_tracer.

Here’s a more verbose version of the example shown in ClassMethods#add_transaction_tracer using this method instead of add_transaction_tracer.

Below is a controller with an invoke_operation action which dispatches to more specific operation methods based on a parameter (very dangerous, btw!). With this instrumentation, the invoke_operation action is ignored but the operation methods show up in New Relic as if they were first class controller actions

MyController < ActionController::Base
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
  # dispatch the given op to the method given by the service parameter.
  def invoke_operation
    op = params['operation']
    perform_action_with_newrelic_trace(:name => op) do
      send op, params['message']
    end
  end
  # Ignore the invoker to avoid double counting
  newrelic_ignore :only => 'invoke_operation'
end

When invoking this method explicitly as in the example above, pass in a block to measure with some combination of options:

  • :category => :controller indicates that this is a controller action and will appear with all the other actions. This is the default.

  • :category => :task indicates that this is a background task and will show up in New Relic with other background tasks instead of in the controllers list

  • :category => :middleware if you are instrumenting a rack middleware call. The :name is optional, useful if you have more than one potential transaction in the call.

  • :category => :uri indicates that this is a web transaction whose name is a normalized URI, where ‘normalized’ means the URI does not have any elements with data in them such as in many REST URIs.

  • :name => action_name is used to specify the action name used as part of the metric name

  • :params => {...} to provide information about the context of the call, used in transaction trace display, for example: :params => { :account => @account.name, :file => file.name } These are treated similarly to request parameters in web transactions.

Seldomly used options:

  • :class_name => Class.name is used to override the name of the class when used inside the metric name. Default is the current class.

  • :path => metric_path is deprecated in the public API. It allows you to set the entire metric after the category part. Overrides all the other options.

  • :request => Rack::Request#new(env) is used to pass in a request object that may respond to path and referer.

@api public

# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 354
def perform_action_with_newrelic_trace(*args, &block) # THREAD_LOCAL_ACCESS
  NewRelic::Agent.record_api_supportability_metric(:perform_action_with_newrelic_trace)
  state = NewRelic::Agent::Tracer.state
  request = newrelic_request(args)
  queue_start_time = detect_queue_start_time(request)

  skip_tracing = do_not_trace? || !state.is_execution_traced?

  if skip_tracing
    state.current_transaction&.ignore!
    NewRelic::Agent.disable_all_tracing { return yield }
  end

  # This method has traditionally taken a variable number of arguments, but the
  # only one that is expected / used is a single options hash.  We are preserving
  # the *args method signature to ensure backwards compatibility.

  trace_options = args.last.is_a?(Hash) ? args.last : NR_DEFAULT_OPTIONS
  category = trace_options[:category] || :controller
  txn_options = create_transaction_options(trace_options, category, state, queue_start_time)

  begin
    finishable = Tracer.start_transaction_or_segment(
      name: txn_options[:transaction_name],
      category: category,
      options: txn_options
    )

    begin
      yield
    rescue => e
      NewRelic::Agent.notice_error(e)
      raise
    end
  ensure
    # the following line needs else branch coverage
    finishable.finish if finishable # rubocop:disable Style/SafeNavigation
  end
end

Protected Instance Methods

do_not_trace?() click to toggle source

overridable method to determine whether to trace an action or not - you may override this in your controller and supply your own logic for ignoring transactions.

# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 423
def do_not_trace?
  _is_filtered?(NR_DO_NOT_TRACE_KEY)
end
ignore_apdex?() click to toggle source

overridable method to determine whether to trace an action for purposes of apdex measurement - you can use this to ignore things like api calls or other fast non-user-facing actions

# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 431
def ignore_apdex?
  _is_filtered?(NR_IGNORE_APDEX_KEY)
end
ignore_enduser?() click to toggle source
# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 435
def ignore_enduser?
  _is_filtered?(NR_IGNORE_ENDUSER_KEY)
end
newrelic_request(args) click to toggle source
# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 396
def newrelic_request(args)
  opts = args.first
  # passed as a parameter to add_transaction_tracer
  if opts.respond_to?(:keys) && opts.respond_to?(:[]) && opts[:request]
    opts[:request]
  # in a Rails app
  elsif self.respond_to?(:request)
    self.request rescue nil
  end
end
newrelic_request_headers(request) click to toggle source
# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 410
def newrelic_request_headers(request)
  if request
    if request.respond_to?(:headers)
      request.headers
    elsif request.respond_to?(:env)
      request.env
    end
  end
end
newrelic_response_code() click to toggle source

Should be implemented in the dispatcher class

# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 408
def newrelic_response_code; end

Private Instance Methods

_is_filtered?(key) click to toggle source

Filter out a request if it matches one of our parameters for ignoring it - the key is either NR_DO_NOT_TRACE_KEY or NR_IGNORE_APDEX_KEY

# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 459
def _is_filtered?(key)
  name = if respond_to?(:action_name)
    action_name
  else
    :'[action_name_missing]'
  end

  NewRelic::Agent::Instrumentation::IgnoreActions.is_filtered?(
    key,
    self.class,
    name
  )
end
create_transaction_options(trace_options, category, state, queue_start_time) click to toggle source
# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 441
def create_transaction_options(trace_options, category, state, queue_start_time)
  txn_options = {}
  txn_options[:request] = trace_options[:request]
  txn_options[:request] ||= request if respond_to?(:request) rescue nil
  # params should have been filtered before calling perform_action_with_newrelic_trace
  txn_options[:filtered_params] = trace_options[:params]
  txn_options[:transaction_name] = TransactionNamer.name_for(nil, self, category, trace_options)
  txn_options[:apdex_start_time] = queue_start_time
  txn_options[:ignore_apdex] = ignore_apdex?
  txn_options[:ignore_enduser] = ignore_enduser?
  NewRelic::Agent::MethodTracerHelpers::SOURCE_CODE_INFORMATION_PARAMETERS.each do |parameter|
    txn_options[parameter] = trace_options[parameter]
  end
  txn_options
end
detect_queue_start_time(request) click to toggle source
# File lib/new_relic/agent/instrumentation/controller_instrumentation.rb, line 473
def detect_queue_start_time(request)
  headers = newrelic_request_headers(request)

  QueueTime.parse_frontend_timestamp(headers) if headers
end