Module: ScoutApm::Instruments::ActiveRecordAliasMethodInstruments

Defined in:
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

Note, if you change this instrumentation, you also need to change ActiveRecordInstruments.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(instrumented_class) ⇒ Object



178
179
180
181
182
183
184
185
186
# File 'lib/scout_apm/instruments/active_record.rb', line 178

def self.included(instrumented_class)
  ScoutApm::Agent.instance.context.logger.info "Instrumenting #{instrumented_class.inspect}"
  instrumented_class.class_eval do
    unless instrumented_class.method_defined?(:log_without_scout_instruments)
      alias_method :log_without_scout_instruments, :log
      alias_method :log, :log_with_scout_instruments
    end
  end
end

Instance Method Details

#log_with_scout_instruments(*args, &block) ⇒ Object



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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/scout_apm/instruments/active_record.rb', line 188

def log_with_scout_instruments(*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)

    log_without_scout_instruments(*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
      log_without_scout_instruments(*args, &block)
    ensure
      req.stop_layer
    end
  end
end