brainzlab 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +6 -21
  3. data/README.md +24 -2
  4. data/lib/brainzlab/beacon/client.rb +207 -0
  5. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  6. data/lib/brainzlab/beacon.rb +215 -0
  7. data/lib/brainzlab/configuration.rb +372 -32
  8. data/lib/brainzlab/context.rb +2 -3
  9. data/lib/brainzlab/cortex/cache.rb +59 -0
  10. data/lib/brainzlab/cortex/client.rb +139 -0
  11. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  12. data/lib/brainzlab/cortex.rb +223 -0
  13. data/lib/brainzlab/dendrite/client.rb +230 -0
  14. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  15. data/lib/brainzlab/dendrite.rb +195 -0
  16. data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
  17. data/lib/brainzlab/devtools/assets/devtools.js +322 -0
  18. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  19. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
  20. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  21. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  22. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  23. data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
  24. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  25. data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
  26. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
  27. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
  28. data/lib/brainzlab/devtools.rb +75 -0
  29. data/lib/brainzlab/flux/buffer.rb +96 -0
  30. data/lib/brainzlab/flux/client.rb +68 -0
  31. data/lib/brainzlab/flux/provisioner.rb +57 -0
  32. data/lib/brainzlab/flux.rb +174 -0
  33. data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
  34. data/lib/brainzlab/instrumentation/active_record.rb +28 -13
  35. data/lib/brainzlab/instrumentation/aws.rb +183 -0
  36. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  37. data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
  38. data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
  39. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  40. data/lib/brainzlab/instrumentation/faraday.rb +3 -4
  41. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  42. data/lib/brainzlab/instrumentation/grape.rb +24 -24
  43. data/lib/brainzlab/instrumentation/graphql.rb +24 -23
  44. data/lib/brainzlab/instrumentation/httparty.rb +13 -14
  45. data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
  46. data/lib/brainzlab/instrumentation/net_http.rb +6 -6
  47. data/lib/brainzlab/instrumentation/redis.rb +14 -21
  48. data/lib/brainzlab/instrumentation/resque.rb +114 -0
  49. data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
  50. data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
  51. data/lib/brainzlab/instrumentation/stripe.rb +163 -0
  52. data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
  53. data/lib/brainzlab/instrumentation.rb +84 -12
  54. data/lib/brainzlab/nerve/client.rb +215 -0
  55. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  56. data/lib/brainzlab/nerve.rb +219 -0
  57. data/lib/brainzlab/pulse/client.rb +15 -11
  58. data/lib/brainzlab/pulse/instrumentation.rb +90 -53
  59. data/lib/brainzlab/pulse/propagation.rb +29 -29
  60. data/lib/brainzlab/pulse/provisioner.rb +12 -12
  61. data/lib/brainzlab/pulse/tracer.rb +4 -4
  62. data/lib/brainzlab/pulse.rb +14 -14
  63. data/lib/brainzlab/rails/log_formatter.rb +127 -121
  64. data/lib/brainzlab/rails/log_subscriber.rb +70 -77
  65. data/lib/brainzlab/rails/railtie.rb +96 -86
  66. data/lib/brainzlab/recall/buffer.rb +1 -1
  67. data/lib/brainzlab/recall/client.rb +14 -10
  68. data/lib/brainzlab/recall/logger.rb +16 -18
  69. data/lib/brainzlab/recall/provisioner.rb +29 -12
  70. data/lib/brainzlab/recall.rb +14 -11
  71. data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
  72. data/lib/brainzlab/reflex/client.rb +14 -10
  73. data/lib/brainzlab/reflex/provisioner.rb +12 -12
  74. data/lib/brainzlab/reflex.rb +31 -31
  75. data/lib/brainzlab/sentinel/client.rb +216 -0
  76. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  77. data/lib/brainzlab/sentinel.rb +165 -0
  78. data/lib/brainzlab/signal/client.rb +60 -0
  79. data/lib/brainzlab/signal/provisioner.rb +55 -0
  80. data/lib/brainzlab/signal.rb +136 -0
  81. data/lib/brainzlab/synapse/client.rb +288 -0
  82. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  83. data/lib/brainzlab/synapse.rb +270 -0
  84. data/lib/brainzlab/utilities/circuit_breaker.rb +261 -0
  85. data/lib/brainzlab/utilities/health_check.rb +294 -0
  86. data/lib/brainzlab/utilities/log_formatter.rb +254 -0
  87. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  88. data/lib/brainzlab/utilities.rb +17 -0
  89. data/lib/brainzlab/vault/cache.rb +80 -0
  90. data/lib/brainzlab/vault/client.rb +196 -0
  91. data/lib/brainzlab/vault/provisioner.rb +49 -0
  92. data/lib/brainzlab/vault.rb +262 -0
  93. data/lib/brainzlab/version.rb +1 -1
  94. data/lib/brainzlab/vision/client.rb +128 -0
  95. data/lib/brainzlab/vision/provisioner.rb +136 -0
  96. data/lib/brainzlab/vision.rb +155 -0
  97. data/lib/brainzlab-sdk.rb +1 -1
  98. data/lib/brainzlab.rb +112 -13
  99. data/lib/generators/brainzlab/install/install_generator.rb +29 -27
  100. metadata +60 -1
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'nerve/client'
4
+ require_relative 'nerve/provisioner'
5
+
6
+ module BrainzLab
7
+ module Nerve
8
+ class << self
9
+ # Report a completed job
10
+ # @param job_class [String] Job class name
11
+ # @param job_id [String] Job ID
12
+ # @param queue [String] Queue name
13
+ # @param started_at [Time] When job started
14
+ # @param ended_at [Time] When job ended (defaults to now)
15
+ # @param attributes [Hash] Additional attributes
16
+ #
17
+ # @example
18
+ # BrainzLab::Nerve.report_success(
19
+ # job_class: "ProcessOrderJob",
20
+ # job_id: "abc-123",
21
+ # queue: "default",
22
+ # started_at: 1.minute.ago
23
+ # )
24
+ #
25
+ def report_success(job_class:, job_id:, queue:, started_at:, ended_at: Time.now, **attributes)
26
+ return false unless enabled?
27
+
28
+ ensure_provisioned!
29
+ return false unless BrainzLab.configuration.nerve_valid?
30
+
31
+ client.report_job(
32
+ job_class: job_class,
33
+ job_id: job_id,
34
+ queue: queue,
35
+ status: 'completed',
36
+ started_at: started_at,
37
+ ended_at: ended_at,
38
+ **attributes
39
+ )
40
+ end
41
+
42
+ # Report a failed job
43
+ def report_failure(job_class:, job_id:, queue:, error:, started_at: nil, **attributes)
44
+ return false unless enabled?
45
+
46
+ ensure_provisioned!
47
+ return false unless BrainzLab.configuration.nerve_valid?
48
+
49
+ client.report_failure(
50
+ job_class: job_class,
51
+ job_id: job_id,
52
+ queue: queue,
53
+ error_class: error.class.name,
54
+ error_message: error.message,
55
+ backtrace: error.backtrace,
56
+ started_at: started_at,
57
+ **attributes
58
+ )
59
+ end
60
+
61
+ # Report a job that's currently running
62
+ def report_started(job_class:, job_id:, queue:, **attributes)
63
+ return false unless enabled?
64
+
65
+ ensure_provisioned!
66
+ return false unless BrainzLab.configuration.nerve_valid?
67
+
68
+ client.report_job(
69
+ job_class: job_class,
70
+ job_id: job_id,
71
+ queue: queue,
72
+ status: 'running',
73
+ started_at: Time.now,
74
+ ended_at: Time.now,
75
+ **attributes
76
+ )
77
+ end
78
+
79
+ # Get job statistics
80
+ # @param queue [String] Filter by queue (optional)
81
+ # @param job_class [String] Filter by job class (optional)
82
+ # @param period [String] Time period: "1h", "24h", "7d", "30d"
83
+ def stats(queue: nil, job_class: nil, period: '1h')
84
+ return nil unless enabled?
85
+
86
+ ensure_provisioned!
87
+ return nil unless BrainzLab.configuration.nerve_valid?
88
+
89
+ client.stats(queue: queue, job_class: job_class, period: period)
90
+ end
91
+
92
+ # List recent jobs
93
+ def jobs(queue: nil, status: nil, limit: 100)
94
+ return [] unless enabled?
95
+
96
+ ensure_provisioned!
97
+ return [] unless BrainzLab.configuration.nerve_valid?
98
+
99
+ client.list_jobs(queue: queue, status: status, limit: limit)
100
+ end
101
+
102
+ # List all queues
103
+ def queues
104
+ return [] unless enabled?
105
+
106
+ ensure_provisioned!
107
+ return [] unless BrainzLab.configuration.nerve_valid?
108
+
109
+ client.list_queues
110
+ end
111
+
112
+ # Get queue details
113
+ def queue(name)
114
+ return nil unless enabled?
115
+
116
+ ensure_provisioned!
117
+ return nil unless BrainzLab.configuration.nerve_valid?
118
+
119
+ client.get_queue(name)
120
+ end
121
+
122
+ # Retry a failed job
123
+ def retry(job_id)
124
+ return false unless enabled?
125
+
126
+ ensure_provisioned!
127
+ return false unless BrainzLab.configuration.nerve_valid?
128
+
129
+ client.retry_job(job_id)
130
+ end
131
+
132
+ # Delete a job
133
+ def delete(job_id)
134
+ return false unless enabled?
135
+
136
+ ensure_provisioned!
137
+ return false unless BrainzLab.configuration.nerve_valid?
138
+
139
+ client.delete_job(job_id)
140
+ end
141
+
142
+ # Report queue metrics (for custom job backends)
143
+ def report_metrics(queue:, size:, latency_ms: nil, workers: nil)
144
+ return false unless enabled?
145
+
146
+ ensure_provisioned!
147
+ return false unless BrainzLab.configuration.nerve_valid?
148
+
149
+ client.report_metrics(
150
+ queue: queue,
151
+ size: size,
152
+ latency_ms: latency_ms,
153
+ workers: workers
154
+ )
155
+ end
156
+
157
+ # Track a job execution (block helper)
158
+ # @example
159
+ # BrainzLab::Nerve.track(job_class: "MyJob", job_id: "123", queue: "default") do
160
+ # # job work
161
+ # end
162
+ #
163
+ def track(job_class:, job_id:, queue: 'default', **attributes)
164
+ started_at = Time.now
165
+
166
+ begin
167
+ result = yield
168
+ report_success(
169
+ job_class: job_class,
170
+ job_id: job_id,
171
+ queue: queue,
172
+ started_at: started_at,
173
+ **attributes
174
+ )
175
+ result
176
+ rescue StandardError => e
177
+ report_failure(
178
+ job_class: job_class,
179
+ job_id: job_id,
180
+ queue: queue,
181
+ error: e,
182
+ started_at: started_at,
183
+ **attributes
184
+ )
185
+ raise
186
+ end
187
+ end
188
+
189
+ # === INTERNAL ===
190
+
191
+ def ensure_provisioned!
192
+ return if @provisioned
193
+
194
+ @provisioned = true
195
+ provisioner.ensure_project!
196
+ end
197
+
198
+ def provisioner
199
+ @provisioner ||= Provisioner.new(BrainzLab.configuration)
200
+ end
201
+
202
+ def client
203
+ @client ||= Client.new(BrainzLab.configuration)
204
+ end
205
+
206
+ def reset!
207
+ @client = nil
208
+ @provisioner = nil
209
+ @provisioned = false
210
+ end
211
+
212
+ private
213
+
214
+ def enabled?
215
+ BrainzLab.configuration.nerve_enabled
216
+ end
217
+ end
218
+ end
219
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "net/http"
4
- require "uri"
5
- require "json"
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
6
 
7
7
  module BrainzLab
8
8
  module Pulse
@@ -23,7 +23,7 @@ module BrainzLab
23
23
  if @config.pulse_buffer_size > 1
24
24
  buffer_trace(payload)
25
25
  else
26
- post("/api/v1/traces", payload)
26
+ post('/api/v1/traces', payload)
27
27
  end
28
28
  end
29
29
 
@@ -31,13 +31,13 @@ module BrainzLab
31
31
  return unless @config.pulse_enabled && @config.pulse_valid?
32
32
  return if payloads.empty?
33
33
 
34
- post("/api/v1/traces/batch", { traces: payloads })
34
+ post('/api/v1/traces/batch', { traces: payloads })
35
35
  end
36
36
 
37
37
  def send_metric(payload)
38
38
  return unless @config.pulse_enabled && @config.pulse_valid?
39
39
 
40
- post("/api/v1/metrics", payload)
40
+ post('/api/v1/metrics', payload)
41
41
  end
42
42
 
43
43
  def flush
@@ -79,9 +79,9 @@ module BrainzLab
79
79
  def post(path, body)
80
80
  uri = URI.join(@config.pulse_url, path)
81
81
  request = Net::HTTP::Post.new(uri)
82
- request["Content-Type"] = "application/json"
83
- request["Authorization"] = "Bearer #{@config.pulse_auth_key}"
84
- request["User-Agent"] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
82
+ request['Content-Type'] = 'application/json'
83
+ request['Authorization'] = "Bearer #{@config.pulse_auth_key}"
84
+ request['User-Agent'] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
85
85
  request.body = JSON.generate(body)
86
86
 
87
87
  execute_with_retry(uri, request)
@@ -94,7 +94,7 @@ module BrainzLab
94
94
  retries = 0
95
95
  begin
96
96
  http = Net::HTTP.new(uri.host, uri.port)
97
- http.use_ssl = uri.scheme == "https"
97
+ http.use_ssl = uri.scheme == 'https'
98
98
  http.open_timeout = 5
99
99
  http.read_timeout = 10
100
100
 
@@ -102,7 +102,11 @@ module BrainzLab
102
102
 
103
103
  case response.code.to_i
104
104
  when 200..299
105
- JSON.parse(response.body) rescue {}
105
+ begin
106
+ JSON.parse(response.body)
107
+ rescue StandardError
108
+ {}
109
+ end
106
110
  when 429, 500..599
107
111
  raise RetryableError, "Server error: #{response.code}"
108
112
  else
@@ -22,20 +22,23 @@ module BrainzLab
22
22
  def install_active_record!
23
23
  return unless defined?(ActiveRecord)
24
24
 
25
- ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
25
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
26
26
  event = ActiveSupport::Notifications::Event.new(*args)
27
27
  next if skip_query?(event.payload)
28
28
 
29
+ sql = event.payload[:sql]
29
30
  record_span(
30
- name: event.payload[:name] || "SQL",
31
- kind: "db",
31
+ name: event.payload[:name] || 'SQL',
32
+ kind: 'db',
32
33
  started_at: event.time,
33
34
  ended_at: event.end,
34
35
  duration_ms: event.duration,
35
36
  data: {
36
- sql: truncate_sql(event.payload[:sql]),
37
+ sql: truncate_sql(sql),
37
38
  name: event.payload[:name],
38
- cached: event.payload[:cached] || false
39
+ cached: event.payload[:cached] || false,
40
+ table: extract_table(sql),
41
+ operation: extract_operation(sql)
39
42
  }
40
43
  )
41
44
  end
@@ -45,12 +48,12 @@ module BrainzLab
45
48
  def install_action_view!
46
49
  return unless defined?(ActionView)
47
50
 
48
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |*args|
51
+ ActiveSupport::Notifications.subscribe('render_template.action_view') do |*args|
49
52
  event = ActiveSupport::Notifications::Event.new(*args)
50
53
 
51
54
  record_span(
52
55
  name: short_path(event.payload[:identifier]),
53
- kind: "render",
56
+ kind: 'render',
54
57
  started_at: event.time,
55
58
  ended_at: event.end,
56
59
  duration_ms: event.duration,
@@ -61,12 +64,12 @@ module BrainzLab
61
64
  )
62
65
  end
63
66
 
64
- ActiveSupport::Notifications.subscribe("render_partial.action_view") do |*args|
67
+ ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
65
68
  event = ActiveSupport::Notifications::Event.new(*args)
66
69
 
67
70
  record_span(
68
71
  name: short_path(event.payload[:identifier]),
69
- kind: "render",
72
+ kind: 'render',
70
73
  started_at: event.time,
71
74
  ended_at: event.end,
72
75
  duration_ms: event.duration,
@@ -77,12 +80,12 @@ module BrainzLab
77
80
  )
78
81
  end
79
82
 
80
- ActiveSupport::Notifications.subscribe("render_collection.action_view") do |*args|
83
+ ActiveSupport::Notifications.subscribe('render_collection.action_view') do |*args|
81
84
  event = ActiveSupport::Notifications::Event.new(*args)
82
85
 
83
86
  record_span(
84
87
  name: short_path(event.payload[:identifier]),
85
- kind: "render",
88
+ kind: 'render',
86
89
  started_at: event.time,
87
90
  ended_at: event.end,
88
91
  duration_ms: event.duration,
@@ -100,11 +103,11 @@ module BrainzLab
100
103
  %w[cache_read.active_support cache_write.active_support cache_delete.active_support].each do |event_name|
101
104
  ActiveSupport::Notifications.subscribe(event_name) do |*args|
102
105
  event = ActiveSupport::Notifications::Event.new(*args)
103
- operation = event_name.split(".").first.sub("cache_", "")
106
+ operation = event_name.split('.').first.sub('cache_', '')
104
107
 
105
108
  record_span(
106
109
  name: "Cache #{operation}",
107
- kind: "cache",
110
+ kind: 'cache',
108
111
  started_at: event.time,
109
112
  ended_at: event.end,
110
113
  duration_ms: event.duration,
@@ -122,7 +125,7 @@ module BrainzLab
122
125
  def install_action_controller!
123
126
  return unless defined?(ActionController)
124
127
 
125
- ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
128
+ ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
126
129
  event = ActiveSupport::Notifications::Event.new(*args)
127
130
  payload = event.payload
128
131
 
@@ -138,12 +141,12 @@ module BrainzLab
138
141
  def install_http_clients!
139
142
  # Net::HTTP instrumentation
140
143
  if defined?(Net::HTTP)
141
- ActiveSupport::Notifications.subscribe("request.net_http") do |*args|
144
+ ActiveSupport::Notifications.subscribe('request.net_http') do |*args|
142
145
  event = ActiveSupport::Notifications::Event.new(*args)
143
146
 
144
147
  record_span(
145
148
  name: "HTTP #{event.payload[:method]} #{event.payload[:host]}",
146
- kind: "http",
149
+ kind: 'http',
147
150
  started_at: event.time,
148
151
  ended_at: event.end,
149
152
  duration_ms: event.duration,
@@ -158,26 +161,26 @@ module BrainzLab
158
161
  end
159
162
 
160
163
  # Faraday instrumentation
161
- if defined?(Faraday)
162
- ActiveSupport::Notifications.subscribe("request.faraday") do |*args|
163
- event = ActiveSupport::Notifications::Event.new(*args)
164
- env = event.payload[:env]
165
- next unless env
164
+ return unless defined?(Faraday)
166
165
 
167
- record_span(
168
- name: "HTTP #{env.method.to_s.upcase} #{env.url.host}",
169
- kind: "http",
170
- started_at: event.time,
171
- ended_at: event.end,
172
- duration_ms: event.duration,
173
- data: {
174
- method: env.method.to_s.upcase,
175
- host: env.url.host,
176
- path: env.url.path,
177
- status: env.status
178
- }
179
- )
180
- end
166
+ ActiveSupport::Notifications.subscribe('request.faraday') do |*args|
167
+ event = ActiveSupport::Notifications::Event.new(*args)
168
+ env = event.payload[:env]
169
+ next unless env
170
+
171
+ record_span(
172
+ name: "HTTP #{env.method.to_s.upcase} #{env.url.host}",
173
+ kind: 'http',
174
+ started_at: event.time,
175
+ ended_at: event.end,
176
+ duration_ms: event.duration,
177
+ data: {
178
+ method: env.method.to_s.upcase,
179
+ host: env.url.host,
180
+ path: env.url.path,
181
+ status: env.status
182
+ }
183
+ )
181
184
  end
182
185
  end
183
186
 
@@ -186,13 +189,13 @@ module BrainzLab
186
189
  return unless defined?(ActiveJob)
187
190
 
188
191
  # Track job enqueuing
189
- ActiveSupport::Notifications.subscribe("enqueue.active_job") do |*args|
192
+ ActiveSupport::Notifications.subscribe('enqueue.active_job') do |*args|
190
193
  event = ActiveSupport::Notifications::Event.new(*args)
191
194
  job = event.payload[:job]
192
195
 
193
196
  record_span(
194
197
  name: "Enqueue #{job.class.name}",
195
- kind: "job",
198
+ kind: 'job',
196
199
  started_at: event.time,
197
200
  ended_at: event.end,
198
201
  duration_ms: event.duration,
@@ -205,14 +208,14 @@ module BrainzLab
205
208
  end
206
209
 
207
210
  # Track job retry
208
- ActiveSupport::Notifications.subscribe("retry_stopped.active_job") do |*args|
211
+ ActiveSupport::Notifications.subscribe('retry_stopped.active_job') do |*args|
209
212
  event = ActiveSupport::Notifications::Event.new(*args)
210
213
  job = event.payload[:job]
211
214
  error = event.payload[:error]
212
215
 
213
216
  record_span(
214
217
  name: "Retry stopped #{job.class.name}",
215
- kind: "job",
218
+ kind: 'job',
216
219
  started_at: event.time,
217
220
  ended_at: event.end,
218
221
  duration_ms: event.duration,
@@ -229,14 +232,14 @@ module BrainzLab
229
232
  end
230
233
 
231
234
  # Track job discard
232
- ActiveSupport::Notifications.subscribe("discard.active_job") do |*args|
235
+ ActiveSupport::Notifications.subscribe('discard.active_job') do |*args|
233
236
  event = ActiveSupport::Notifications::Event.new(*args)
234
237
  job = event.payload[:job]
235
238
  error = event.payload[:error]
236
239
 
237
240
  record_span(
238
241
  name: "Discarded #{job.class.name}",
239
- kind: "job",
242
+ kind: 'job',
240
243
  started_at: event.time,
241
244
  ended_at: event.end,
242
245
  duration_ms: event.duration,
@@ -257,12 +260,12 @@ module BrainzLab
257
260
  def install_action_cable!
258
261
  return unless defined?(ActionCable)
259
262
 
260
- ActiveSupport::Notifications.subscribe("perform_action.action_cable") do |*args|
263
+ ActiveSupport::Notifications.subscribe('perform_action.action_cable') do |*args|
261
264
  event = ActiveSupport::Notifications::Event.new(*args)
262
265
 
263
266
  record_span(
264
267
  name: "Cable #{event.payload[:channel_class]}##{event.payload[:action]}",
265
- kind: "cable",
268
+ kind: 'cable',
266
269
  started_at: event.time,
267
270
  ended_at: event.end,
268
271
  duration_ms: event.duration,
@@ -273,12 +276,12 @@ module BrainzLab
273
276
  )
274
277
  end
275
278
 
276
- ActiveSupport::Notifications.subscribe("transmit.action_cable") do |*args|
279
+ ActiveSupport::Notifications.subscribe('transmit.action_cable') do |*args|
277
280
  event = ActiveSupport::Notifications::Event.new(*args)
278
281
 
279
282
  record_span(
280
283
  name: "Cable transmit #{event.payload[:channel_class]}",
281
- kind: "cable",
284
+ kind: 'cable',
282
285
  started_at: event.time,
283
286
  ended_at: event.end,
284
287
  duration_ms: event.duration,
@@ -289,12 +292,12 @@ module BrainzLab
289
292
  )
290
293
  end
291
294
 
292
- ActiveSupport::Notifications.subscribe("broadcast.action_cable") do |*args|
295
+ ActiveSupport::Notifications.subscribe('broadcast.action_cable') do |*args|
293
296
  event = ActiveSupport::Notifications::Event.new(*args)
294
297
 
295
298
  record_span(
296
299
  name: "Cable broadcast #{event.payload[:broadcasting]}",
297
- kind: "cable",
300
+ kind: 'cable',
298
301
  started_at: event.time,
299
302
  ended_at: event.end,
300
303
  duration_ms: event.duration,
@@ -306,7 +309,8 @@ module BrainzLab
306
309
  end
307
310
  end
308
311
 
309
- def record_span(name:, kind:, started_at:, ended_at:, duration_ms:, error: false, error_class: nil, error_message: nil, data: {})
312
+ def record_span(name:, kind:, started_at:, ended_at:, duration_ms:, error: false, error_class: nil,
313
+ error_message: nil, data: {})
310
314
  spans = Thread.current[:brainzlab_pulse_spans]
311
315
  return unless spans
312
316
 
@@ -331,10 +335,10 @@ module BrainzLab
331
335
 
332
336
  def skip_query?(payload)
333
337
  # Skip SCHEMA queries and internal Rails queries
334
- return true if payload[:name] == "SCHEMA"
335
- return true if payload[:name]&.start_with?("EXPLAIN")
336
- return true if payload[:sql]&.include?("pg_")
337
- return true if payload[:sql]&.include?("information_schema")
338
+ return true if payload[:name] == 'SCHEMA'
339
+ return true if payload[:name]&.start_with?('EXPLAIN')
340
+ return true if payload[:sql]&.include?('pg_')
341
+ return true if payload[:sql]&.include?('information_schema')
338
342
  return true if payload[:cached] && !include_cached_queries?
339
343
 
340
344
  false
@@ -346,17 +350,50 @@ module BrainzLab
346
350
 
347
351
  def truncate_sql(sql)
348
352
  return nil unless sql
353
+
349
354
  sql.to_s[0, 1000]
350
355
  end
351
356
 
352
357
  def truncate_key(key)
353
358
  return nil unless key
359
+
354
360
  key.to_s[0, 200]
355
361
  end
356
362
 
357
363
  def short_path(path)
358
364
  return nil unless path
359
- path.to_s.split("/").last(2).join("/")
365
+
366
+ path.to_s.split('/').last(2).join('/')
367
+ end
368
+
369
+ def extract_table(sql)
370
+ return nil unless sql
371
+
372
+ # Match FROM "table" or FROM table patterns
373
+ # Also handles INSERT INTO, UPDATE, DELETE FROM
374
+ case sql.to_s
375
+ when /\bFROM\s+["'`]?(\w+)["'`]?/i
376
+ Regexp.last_match(1)
377
+ when /\bINTO\s+["'`]?(\w+)["'`]?/i
378
+ Regexp.last_match(1)
379
+ when /\bUPDATE\s+["'`]?(\w+)["'`]?/i
380
+ Regexp.last_match(1)
381
+ when /\bJOIN\s+["'`]?(\w+)["'`]?/i
382
+ Regexp.last_match(1)
383
+ end
384
+ end
385
+
386
+ def extract_operation(sql)
387
+ return nil unless sql
388
+
389
+ case sql.to_s.strip.upcase
390
+ when /\ASELECT/i then 'SELECT'
391
+ when /\AINSERT/i then 'INSERT'
392
+ when /\AUPDATE/i then 'UPDATE'
393
+ when /\ADELETE/i then 'DELETE'
394
+ when /\ABEGIN/i, /\ACOMMIT/i, /\AROLLBACK/i then 'TRANSACTION'
395
+ else 'QUERY'
396
+ end
360
397
  end
361
398
  end
362
399
  end