module ScoutApm::Instruments::ActiveRecordInstruments

Contains ActiveRecord instrument, aliasing +ActiveRecord::ConnectionAdapters::AbstractAdapter#log+ calls to trace calls to the database.

log instrument.

log is very close to where AR calls out to the database itself. We have access to the real SQL, and an AR generated “name” for the Query

Public Class Methods

prepended(instrumented_class) click to toggle source
# File lib/scout_apm/instruments/active_record.rb, line 169
def self.prepended(instrumented_class)
  ScoutApm::Agent.instance.context.logger.info "Instrumenting #{instrumented_class.inspect}"
end

Public Instance Methods

log(*args, &block) click to toggle source
Calls superclass method
# File lib/scout_apm/instruments/active_record.rb, line 173
def log(*args, &block)
  # Extract data from the arguments
  sql, name = args
  metric_name = Utils::ActiveRecordMetricName.new(sql, name)
  desc = SqlList.new(sql)

  # Get current ScoutApm context
  req = ScoutApm::RequestManager.lookup
  current_layer = req.current_layer

  # If we call #log, we have a real query to run, and we've already
  # gotten through the cache gatekeeper. Since we want to only trace real
  # queries, and not repeated identical queries that just hit cache, we
  # mark layer as ignorable initially in #find_by_sql, then only when we
  # know it's a real database call do we mark it back as usable.
  #
  # This flag is later used in SlowRequestConverter to skip adding ignorable layers
  current_layer.annotate_layer(:ignorable => false) if current_layer

  # Either: update the current layer and yield, don't start a new one.
  if current_layer && current_layer.type == "ActiveRecord"
    # TODO: Get rid of call .to_s, need to find this without forcing a previous run of the name logic
    if current_layer.name.to_s == Utils::ActiveRecordMetricName::DEFAULT_METRIC
      current_layer.name = metric_name
    end

    if current_layer.desc.nil?
      current_layer.desc = SqlList.new
    end
    current_layer.desc.merge(desc)

    super(*args, &block)

  # OR: Start a new layer, we didn't pick up instrumentation earlier in the stack.
  else
    layer = ScoutApm::Layer.new("ActiveRecord", metric_name)
    layer.desc = desc
    req.start_layer(layer)
    begin
      super(*args, &block)
    ensure
      req.stop_layer
    end
  end
end