Module: MetrixWire::Instrument::ActiveSupportSubscriber

Defined in:
lib/metrixwire/instrument/active_support.rb

Overview

Subscribes to ActiveSupport::Notifications for the idiomatic, reliable Rails path: SQL queries, controller actions (route refinement + errors), and cache reads/writes. Each subscriber is guarded and never throws.

Constant Summary collapse

SCHEMA_SQL =
/\A\s*(BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE|SET|SHOW|PRAGMA)\b/i.freeze

Class Method Summary collapse

Class Method Details

.cache_op(name, payload) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/metrixwire/instrument/active_support.rb', line 122

def cache_op(name, payload)
  case name
  when "cache_read.active_support"
    ["read", payload.key?(:hit) ? (payload[:hit] ? true : false) : nil]
  when "cache_fetch_hit.active_support"
    ["read", true]
  when "cache_write.active_support"
    ["write", nil]
  when "cache_delete.active_support"
    ["delete", nil]
  else
    [name.split(".").first.sub("cache_", ""), nil]
  end
end

.event_of(args) ⇒ Object

Build an ActiveSupport::Notifications::Event from the raw subscribe args.



149
150
151
# File 'lib/metrixwire/instrument/active_support.rb', line 149

def event_of(args)
  ::ActiveSupport::Notifications::Event.new(*args)
end

.installObject



13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/metrixwire/instrument/active_support.rb', line 13

def install
  return unless defined?(::ActiveSupport::Notifications)
  return if @installed

  subscribe_sql
  subscribe_process_action
  subscribe_cache
  subscribe_transaction
  @installed = true
rescue StandardError
  nil
end

.row_count_from_payload(payload) ⇒ Object



137
138
139
140
141
142
143
144
145
146
# File 'lib/metrixwire/instrument/active_support.rb', line 137

def row_count_from_payload(payload)
  row_count = payload[:row_count]
  return { rowCount: row_count.to_i } if row_count

  result = payload[:result]
  n = Helpers.extract_row_count(result)
  n.nil? ? nil : { rowCount: n }
rescue StandardError
  nil
end

.subscribe_cacheObject

Cache reads/writes → custom cache spans. hit is known for cache_read.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/metrixwire/instrument/active_support.rb', line 85

def subscribe_cache
  ::ActiveSupport::Notifications.subscribe(/\Acache_(read|fetch_hit|write|delete)\.active_support\z/) do |*args|
    begin
      trace = Context.current
      next if trace.nil?

      event = event_of(args)
      name = event.name.to_s
      payload = event.payload
      op, hit = cache_op(name, payload)
      meta = { kind: "cache", op: op }
      meta[:hit] = hit unless hit.nil?
      Helpers.add_span("custom", "cache #{op}", event.duration, meta: meta, source_location: nil)
    rescue StandardError
      nil
    end
  end
end

.subscribe_process_actionObject

Refine the route to controller#action, capture 5xx + exceptions.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/metrixwire/instrument/active_support.rb', line 51

def subscribe_process_action
  ::ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
    begin
      trace = Context.current
      next if trace.nil?

      payload = event_of(args).payload
      controller = payload[:controller]
      action = payload[:action]
      method = payload[:method] || trace.method
      if controller && action
        trace.route = "#{method} #{controller}##{action}"
      end

      status = payload[:status].to_i
      trace.status = "error" if status >= 500

      if (ex = payload[:exception_object])
        trace.capture_exception(ex)
      elsif (ex_arr = payload[:exception])
        # [class_name, message]
        trace.set_meta(:exception, {
          type: ex_arr[0].to_s,
          message: ex_arr[1].to_s
        })
        trace.status = "error"
      end
    rescue StandardError
      nil
    end
  end
end

.subscribe_sqlObject

db_query spans from sql.active_record. rowCount from the result payload when ActiveRecord exposes it.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/metrixwire/instrument/active_support.rb', line 28

def subscribe_sql
  ::ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
    begin
      trace = Context.current
      next if trace.nil?

      event = event_of(args)
      payload = event.payload
      name = payload[:name].to_s
      sql = payload[:sql].to_s
      # Skip schema/transaction control + AR's own housekeeping queries.
      next if sql.empty? || SCHEMA_SQL.match?(sql)
      next if name == "SCHEMA" || name == "TRANSACTION"

      meta = row_count_from_payload(payload)
      Helpers.add_span("db_query", sql, event.duration, meta: meta, source_location: nil)
    rescue StandardError
      nil
    end
  end
end

.subscribe_transactionObject

ActiveRecord transaction notifications (Rails 7.2+) → transaction spans.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/metrixwire/instrument/active_support.rb', line 105

def subscribe_transaction
  ::ActiveSupport::Notifications.subscribe("transaction.active_record") do |*args|
    begin
      trace = Context.current
      next if trace.nil?

      event = event_of(args)
      Helpers.add_span("custom", "DB transaction", event.duration,
                       meta: { kind: "transaction" }, source_location: nil)
    rescue StandardError
      nil
    end
  end
rescue StandardError
  nil
end