Module: ScoutApm::Instruments::ActiveRecordInstruments

Defined in:
lib/scout_apm/instruments/active_record.rb,
lib/scout_apm/instruments/active_record.rb

Overview

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

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.prepended(instrumented_class) ⇒ Object



169
170
171
# 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

Instance Method Details

#log(*args, &block) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# 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