Class: Bigcommerce::Prometheus::Integrations::ActiveRecordSql

Inherits:
Object
  • Object
show all
Defined in:
lib/bigcommerce/prometheus/integrations/active_record.rb

Overview

Subscribes to ActiveSupport sql.active_record notifications and pushes a per-operation SQL query duration histogram to the Prometheus exporter.

Constant Summary collapse

IGNORED_NAMES =
%w[SCHEMA CACHE].freeze
TYPE =
'active_record_sql'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client:) ⇒ ActiveRecordSql

Returns a new instance of ActiveRecordSql.



75
76
77
# File 'lib/bigcommerce/prometheus/integrations/active_record.rb', line 75

def initialize(client:)
  @client = client
end

Class Method Details

.active_record_loaded?Boolean

Returns whether ActiveRecord is loaded in the current process.

Returns:

  • (Boolean)

    whether ActiveRecord is loaded in the current process.



43
44
45
# File 'lib/bigcommerce/prometheus/integrations/active_record.rb', line 43

def self.active_record_loaded?
  defined?(::ActiveRecord) ? true : false
end

.register_type_collector(server, process_name: nil) ⇒ Object

Wrapper for instrumentor wiring: registers the AR SQL type collector on the given server, gated on the active_record_enabled config flag, and swallows errors so an additive feature failure cannot take down core instrumentation.



50
51
52
53
54
55
56
# File 'lib/bigcommerce/prometheus/integrations/active_record.rb', line 50

def self.register_type_collector(server, process_name: nil)
  return unless ::Bigcommerce::Prometheus.active_record_enabled

  server.add_type_collector(::Bigcommerce::Prometheus::TypeCollectors::ActiveRecordSql.new)
rescue StandardError => e
  log_warn(process_name, "Failed to register ActiveRecord type collector: #{e.message}")
end

.start(client: nil) ⇒ Object

Idempotent: returns the same instance on repeated calls within a process, so calling .start more than once (e.g. from both the gem’s instrumentor and a consuming app’s initializer) does not register duplicate subscribers and double-count every SQL query.

Noop when ActiveRecord is not loaded so non-Rails consumers (or any process that never loads ActiveRecord) can call this safely.



36
37
38
39
40
# File 'lib/bigcommerce/prometheus/integrations/active_record.rb', line 36

def self.start(client: nil)
  return unless active_record_loaded?

  @start ||= new(client: client || ::Bigcommerce::Prometheus.client).tap(&:subscribe!)
end

.start_safe(client: nil, process_name: nil) ⇒ Object

Wrapper for instrumentor wiring: starts the AR integration, gated on the active_record_enabled config flag, and swallows errors so an additive feature failure cannot take down core instrumentation.



61
62
63
64
65
66
67
# File 'lib/bigcommerce/prometheus/integrations/active_record.rb', line 61

def self.start_safe(client: nil, process_name: nil)
  return unless ::Bigcommerce::Prometheus.active_record_enabled

  start(client: client)
rescue StandardError => e
  log_warn(process_name, "Failed to start ActiveRecord integration: #{e.message}")
end

Instance Method Details

#call(event) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/bigcommerce/prometheus/integrations/active_record.rb', line 85

def call(event)
  return if IGNORED_NAMES.include?(event.payload[:name])

  @client.send_json(
    type: TYPE,
    duration_seconds: event.duration / 1000.0,
    custom_labels: { operation: classify(event.payload[:sql]) }
  )
rescue StandardError
  # Never let metric instrumentation propagate into the request path.
  nil
end

#subscribe!Object



79
80
81
82
83
# File 'lib/bigcommerce/prometheus/integrations/active_record.rb', line 79

def subscribe!
  ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
    call(ActiveSupport::Notifications::Event.new(*args))
  end
end