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
@@ -18,7 +18,7 @@ module BrainzLab
18
18
  end
19
19
 
20
20
  @installed = true
21
- BrainzLab.debug_log("Redis instrumentation installed")
21
+ BrainzLab.debug_log('Redis instrumentation installed')
22
22
  end
23
23
 
24
24
  def installed?
@@ -32,7 +32,7 @@ module BrainzLab
32
32
  private
33
33
 
34
34
  def redis_5_or_newer?
35
- defined?(::Redis::VERSION) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("5.0")
35
+ defined?(::Redis::VERSION) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new('5.0')
36
36
  end
37
37
 
38
38
  def install_middleware!
@@ -50,10 +50,6 @@ module BrainzLab
50
50
 
51
51
  # Middleware for Redis 5+ (RedisClient)
52
52
  module Middleware
53
- def connect(redis_config)
54
- super
55
- end
56
-
57
53
  def call(command, redis_config)
58
54
  return super unless should_track?
59
55
 
@@ -78,10 +74,10 @@ module BrainzLab
78
74
  ignore.map(&:downcase).include?(cmd_name)
79
75
  end
80
76
 
81
- def track_command(command, &block)
77
+ def track_command(command)
82
78
  return yield if should_skip_command?(command)
79
+
83
80
  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
- error_info = nil
85
81
 
86
82
  begin
87
83
  result = yield
@@ -94,9 +90,8 @@ module BrainzLab
94
90
  end
95
91
  end
96
92
 
97
- def track_pipeline(commands, &block)
93
+ def track_pipeline(commands)
98
94
  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
- error_info = nil
100
95
 
101
96
  begin
102
97
  result = yield
@@ -119,7 +114,7 @@ module BrainzLab
119
114
  if BrainzLab.configuration.reflex_enabled
120
115
  BrainzLab::Reflex.add_breadcrumb(
121
116
  "Redis #{cmd_name}",
122
- category: "redis",
117
+ category: 'redis',
123
118
  level: level,
124
119
  data: {
125
120
  command: cmd_name,
@@ -138,14 +133,14 @@ module BrainzLab
138
133
 
139
134
  def record_pipeline(commands, started_at, error = nil)
140
135
  duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
141
- cmd_names = commands.map { |c| c.first.to_s.upcase }.uniq.join(", ")
136
+ cmd_names = commands.map { |c| c.first.to_s.upcase }.uniq.join(', ')
142
137
  level = error ? :error : :info
143
138
 
144
139
  # Add breadcrumb for Reflex
145
140
  if BrainzLab.configuration.reflex_enabled
146
141
  BrainzLab::Reflex.add_breadcrumb(
147
142
  "Redis PIPELINE (#{commands.size} commands)",
148
- category: "redis",
143
+ category: 'redis',
149
144
  level: level,
150
145
  data: {
151
146
  commands: cmd_names,
@@ -157,7 +152,7 @@ module BrainzLab
157
152
  end
158
153
 
159
154
  # Record span for Pulse APM
160
- record_pulse_span("PIPELINE", nil, duration_ms, error, commands.size)
155
+ record_pulse_span('PIPELINE', nil, duration_ms, error, commands.size)
161
156
  rescue StandardError => e
162
157
  BrainzLab.debug_log("Redis instrumentation error: #{e.message}")
163
158
  end
@@ -169,7 +164,7 @@ module BrainzLab
169
164
  span = {
170
165
  span_id: SecureRandom.uuid,
171
166
  name: "Redis #{command}",
172
- kind: "redis",
167
+ kind: 'redis',
173
168
  started_at: Time.now.utc - (duration_ms / 1000.0),
174
169
  ended_at: Time.now.utc,
175
170
  duration_ms: duration_ms,
@@ -210,7 +205,6 @@ module BrainzLab
210
205
  return super if should_skip_command?(command)
211
206
 
212
207
  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
213
- error_info = nil
214
208
 
215
209
  begin
216
210
  result = super
@@ -227,7 +221,6 @@ module BrainzLab
227
221
  return super unless should_track?
228
222
 
229
223
  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
230
- error_info = nil
231
224
  commands = pipeline.commands
232
225
 
233
226
  begin
@@ -262,7 +255,7 @@ module BrainzLab
262
255
  if BrainzLab.configuration.reflex_enabled
263
256
  BrainzLab::Reflex.add_breadcrumb(
264
257
  "Redis #{cmd_name}",
265
- category: "redis",
258
+ category: 'redis',
266
259
  level: level,
267
260
  data: {
268
261
  command: cmd_name,
@@ -285,7 +278,7 @@ module BrainzLab
285
278
  if BrainzLab.configuration.reflex_enabled
286
279
  BrainzLab::Reflex.add_breadcrumb(
287
280
  "Redis PIPELINE (#{commands.size} commands)",
288
- category: "redis",
281
+ category: 'redis',
289
282
  level: level,
290
283
  data: {
291
284
  count: commands.size,
@@ -295,7 +288,7 @@ module BrainzLab
295
288
  )
296
289
  end
297
290
 
298
- record_pulse_span("PIPELINE", nil, duration_ms, error, commands.size)
291
+ record_pulse_span('PIPELINE', nil, duration_ms, error, commands.size)
299
292
  rescue StandardError => e
300
293
  BrainzLab.debug_log("Redis instrumentation error: #{e.message}")
301
294
  end
@@ -307,7 +300,7 @@ module BrainzLab
307
300
  span = {
308
301
  span_id: SecureRandom.uuid,
309
302
  name: "Redis #{command}",
310
- kind: "redis",
303
+ kind: 'redis',
311
304
  started_at: Time.now.utc - (duration_ms / 1000.0),
312
305
  ended_at: Time.now.utc,
313
306
  duration_ms: duration_ms,
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ module ResqueInstrumentation
6
+ class << self
7
+ def install!
8
+ return unless defined?(::Resque)
9
+
10
+ install_hooks!
11
+ install_failure_backend!
12
+
13
+ BrainzLab.debug_log('[Instrumentation] Resque instrumentation installed')
14
+ end
15
+
16
+ private
17
+
18
+ def install_hooks!
19
+ ::Resque.before_fork do |_job|
20
+ # Clear any stale connections before forking
21
+ BrainzLab::Recall.reset! if defined?(BrainzLab::Recall)
22
+ BrainzLab::Pulse.reset! if defined?(BrainzLab::Pulse)
23
+ end
24
+
25
+ ::Resque.after_fork do |job|
26
+ # Re-establish connections after forking
27
+ end
28
+ end
29
+
30
+ def install_failure_backend!
31
+ # Create a custom failure backend
32
+ failure_backend = Class.new do
33
+ def initialize(exception, worker, queue, payload)
34
+ @exception = exception
35
+ @worker = worker
36
+ @queue = queue
37
+ @payload = payload
38
+ end
39
+
40
+ def save
41
+ job_class = @payload['class'] || 'Unknown'
42
+
43
+ if BrainzLab.configuration.reflex_effectively_enabled?
44
+ BrainzLab::Reflex.capture(@exception,
45
+ tags: { job_class: job_class, queue: @queue, source: 'resque' },
46
+ extra: {
47
+ worker: @worker.to_s,
48
+ args: @payload['args']
49
+ })
50
+ end
51
+
52
+ return unless BrainzLab.configuration.flux_effectively_enabled?
53
+
54
+ BrainzLab::Flux.increment('resque.job.failed', tags: { job_class: job_class, queue: @queue })
55
+ end
56
+ end
57
+
58
+ # Add our failure backend to the chain
59
+ return unless defined?(::Resque::Failure)
60
+
61
+ ::Resque::Failure.backend = ::Resque::Failure::Multiple.new(
62
+ ::Resque::Failure.backend,
63
+ failure_backend
64
+ )
65
+ end
66
+ end
67
+
68
+ # Middleware module to include in Resque jobs
69
+ module Middleware
70
+ def self.included(base)
71
+ base.extend(ClassMethods)
72
+ end
73
+
74
+ module ClassMethods
75
+ def around_perform_brainzlab(*_args)
76
+ job_class = name
77
+ queue = Resque.queue_from_class(self) || 'default'
78
+ started_at = Time.now
79
+
80
+ BrainzLab::Context.current.set_context(
81
+ job_class: job_class,
82
+ queue: queue
83
+ )
84
+
85
+ begin
86
+ yield
87
+ ensure
88
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
89
+
90
+ if BrainzLab.configuration.pulse_effectively_enabled?
91
+ BrainzLab::Pulse.record_trace(
92
+ "job.#{job_class}",
93
+ kind: 'job',
94
+ started_at: started_at,
95
+ ended_at: Time.now,
96
+ job_class: job_class,
97
+ queue: queue
98
+ )
99
+ end
100
+
101
+ if BrainzLab.configuration.flux_effectively_enabled?
102
+ tags = { job_class: job_class, queue: queue }
103
+ BrainzLab::Flux.distribution('resque.job.duration_ms', duration_ms, tags: tags)
104
+ BrainzLab::Flux.increment('resque.job.processed', tags: tags)
105
+ end
106
+
107
+ BrainzLab.clear_context!
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -29,7 +29,7 @@ module BrainzLab
29
29
  end
30
30
 
31
31
  @installed = true
32
- BrainzLab.debug_log("Sidekiq instrumentation installed")
32
+ BrainzLab.debug_log('Sidekiq instrumentation installed')
33
33
  end
34
34
 
35
35
  def installed?
@@ -47,11 +47,11 @@ module BrainzLab
47
47
  return yield unless should_trace?
48
48
 
49
49
  started_at = Time.now.utc
50
- job_class = job["class"] || worker.class.name
51
- job_id = job["jid"]
50
+ job_class = job['class'] || worker.class.name
51
+ job_id = job['jid']
52
52
 
53
53
  # Calculate queue wait time
54
- enqueued_at = job["enqueued_at"] ? Time.at(job["enqueued_at"]) : nil
54
+ enqueued_at = job['enqueued_at'] ? Time.at(job['enqueued_at']) : nil
55
55
  queue_wait_ms = enqueued_at ? ((started_at - enqueued_at) * 1000).round(2) : nil
56
56
 
57
57
  # Extract parent trace context if present (distributed tracing)
@@ -63,9 +63,9 @@ module BrainzLab
63
63
  # Add breadcrumb
64
64
  BrainzLab::Reflex.add_breadcrumb(
65
65
  "Sidekiq #{job_class}",
66
- category: "job.sidekiq",
66
+ category: 'job.sidekiq',
67
67
  level: :info,
68
- data: { job_id: job_id, queue: queue, retry_count: job["retry_count"] }
68
+ data: { job_id: job_id, queue: queue, retry_count: job['retry_count'] }
69
69
  )
70
70
 
71
71
  # Initialize Pulse tracing
@@ -85,7 +85,7 @@ module BrainzLab
85
85
  queue: queue,
86
86
  started_at: started_at,
87
87
  queue_wait_ms: queue_wait_ms,
88
- retry_count: job["retry_count"] || 0,
88
+ retry_count: job['retry_count'] || 0,
89
89
  parent_context: parent_context,
90
90
  error: error_occurred
91
91
  )
@@ -102,11 +102,11 @@ module BrainzLab
102
102
 
103
103
  def setup_context(job, queue)
104
104
  BrainzLab::Context.current.set_context(
105
- job_class: job["class"],
106
- job_id: job["jid"],
105
+ job_class: job['class'],
106
+ job_id: job['jid'],
107
107
  queue_name: queue,
108
- retry_count: job["retry_count"],
109
- arguments: job["args"]&.map(&:to_s)&.first(5)
108
+ retry_count: job['retry_count'],
109
+ arguments: job['args']&.map(&:to_s)&.first(5)
110
110
  )
111
111
  end
112
112
 
@@ -118,19 +118,20 @@ module BrainzLab
118
118
  end
119
119
 
120
120
  def extract_trace_context(job)
121
- return nil unless job["_brainzlab_trace"]
121
+ return nil unless job['_brainzlab_trace']
122
122
 
123
- trace_data = job["_brainzlab_trace"]
123
+ trace_data = job['_brainzlab_trace']
124
124
  BrainzLab::Pulse::Propagation::Context.new(
125
- trace_id: trace_data["trace_id"],
126
- span_id: trace_data["span_id"],
127
- sampled: trace_data["sampled"] != false
125
+ trace_id: trace_data['trace_id'],
126
+ span_id: trace_data['span_id'],
127
+ sampled: trace_data['sampled'] != false
128
128
  )
129
129
  rescue StandardError
130
130
  nil
131
131
  end
132
132
 
133
- def record_trace(job_class:, job_id:, queue:, started_at:, queue_wait_ms:, retry_count:, parent_context:, error:)
133
+ def record_trace(job_class:, job_id:, queue:, started_at:, queue_wait_ms:, retry_count:, parent_context:,
134
+ error:)
134
135
  ended_at = Time.now.utc
135
136
  duration_ms = ((ended_at - started_at) * 1000).round(2)
136
137
 
@@ -156,7 +157,7 @@ module BrainzLab
156
157
  payload = {
157
158
  trace_id: SecureRandom.uuid,
158
159
  name: job_class,
159
- kind: "job",
160
+ kind: 'job',
160
161
  started_at: started_at.utc.iso8601(3),
161
162
  ended_at: ended_at.utc.iso8601(3),
162
163
  duration_ms: duration_ms,
@@ -200,7 +201,7 @@ module BrainzLab
200
201
 
201
202
  # Client middleware - runs when jobs are enqueued
202
203
  class ClientMiddleware
203
- def call(worker_class, job, queue, redis_pool)
204
+ def call(_worker_class, job, queue, _redis_pool)
204
205
  # Inject trace context for distributed tracing
205
206
  inject_trace_context(job)
206
207
 
@@ -208,9 +209,9 @@ module BrainzLab
208
209
  if BrainzLab.configuration.reflex_enabled
209
210
  BrainzLab::Reflex.add_breadcrumb(
210
211
  "Enqueue #{job['class']}",
211
- category: "job.sidekiq.enqueue",
212
+ category: 'job.sidekiq.enqueue',
212
213
  level: :info,
213
- data: { queue: queue, job_id: job["jid"] }
214
+ data: { queue: queue, job_id: job['jid'] }
214
215
  )
215
216
  end
216
217
 
@@ -231,10 +232,10 @@ module BrainzLab
231
232
 
232
233
  return unless ctx&.valid?
233
234
 
234
- job["_brainzlab_trace"] = {
235
- "trace_id" => ctx.trace_id,
236
- "span_id" => ctx.span_id,
237
- "sampled" => ctx.sampled
235
+ job['_brainzlab_trace'] = {
236
+ 'trace_id' => ctx.trace_id,
237
+ 'span_id' => ctx.span_id,
238
+ 'sampled' => ctx.sampled
238
239
  }
239
240
  rescue StandardError => e
240
241
  BrainzLab.debug_log("Failed to inject Sidekiq trace context: #{e.message}")
@@ -247,13 +248,13 @@ module BrainzLab
247
248
  spans << {
248
249
  span_id: SecureRandom.uuid,
249
250
  name: "Enqueue #{job['class']}",
250
- kind: "job",
251
+ kind: 'job',
251
252
  started_at: Time.now.utc,
252
253
  ended_at: Time.now.utc,
253
254
  duration_ms: 0,
254
255
  data: {
255
- job_class: job["class"],
256
- job_id: job["jid"],
256
+ job_class: job['class'],
257
+ job_id: job['jid'],
257
258
  queue: queue
258
259
  }
259
260
  }
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ module SolidQueueInstrumentation
6
+ class << self
7
+ def install!
8
+ return unless defined?(::SolidQueue)
9
+
10
+ install_job_instrumentation!
11
+ install_worker_instrumentation!
12
+
13
+ BrainzLab.debug_log('[Instrumentation] SolidQueue instrumentation installed')
14
+ end
15
+
16
+ private
17
+
18
+ def install_job_instrumentation!
19
+ return unless defined?(::ActiveJob::Base)
20
+
21
+ ::ActiveJob::Base.class_eval do
22
+ around_perform do |job, block|
23
+ BrainzLab::Instrumentation::SolidQueueInstrumentation.around_perform(job, &block)
24
+ end
25
+
26
+ around_enqueue do |job, block|
27
+ BrainzLab::Instrumentation::SolidQueueInstrumentation.around_enqueue(job, &block)
28
+ end
29
+ end
30
+ end
31
+
32
+ def install_worker_instrumentation!
33
+ # Subscribe to ActiveSupport notifications for SolidQueue
34
+ return unless defined?(::ActiveSupport::Notifications)
35
+
36
+ ::ActiveSupport::Notifications.subscribe(/solid_queue/) do |name, start, finish, _id, payload|
37
+ handle_notification(name, start, finish, payload)
38
+ end
39
+ end
40
+
41
+ def handle_notification(name, start, finish, payload)
42
+ duration_ms = ((finish - start) * 1000).round(2)
43
+
44
+ case name
45
+ when 'perform.solid_queue'
46
+ track_job_perform(payload, duration_ms)
47
+ when 'enqueue.solid_queue'
48
+ track_job_enqueue(payload)
49
+ when 'discard.solid_queue'
50
+ track_job_discard(payload)
51
+ when 'retry.solid_queue'
52
+ track_job_retry(payload)
53
+ end
54
+ end
55
+
56
+ def track_job_perform(payload, duration_ms)
57
+ job_class = payload[:job]&.class&.name || payload[:job_class]
58
+ queue = payload[:queue] || 'default'
59
+
60
+ # Track with Pulse
61
+ if BrainzLab.configuration.pulse_effectively_enabled?
62
+ BrainzLab::Pulse.record_trace(
63
+ "job.#{job_class}",
64
+ kind: 'job',
65
+ started_at: Time.now - (duration_ms / 1000.0),
66
+ ended_at: Time.now,
67
+ job_class: job_class,
68
+ job_id: payload[:job_id],
69
+ queue: queue,
70
+ executions: payload[:executions] || 1,
71
+ error: payload[:error].present?,
72
+ error_class: payload[:error]&.class&.name,
73
+ error_message: payload[:error]&.message
74
+ )
75
+ end
76
+
77
+ # Track with Flux
78
+ if BrainzLab.configuration.flux_effectively_enabled?
79
+ tags = { job_class: job_class, queue: queue }
80
+ BrainzLab::Flux.distribution('solid_queue.job.duration_ms', duration_ms, tags: tags)
81
+ BrainzLab::Flux.increment('solid_queue.job.processed', tags: tags)
82
+
83
+ BrainzLab::Flux.increment('solid_queue.job.failed', tags: tags) if payload[:error]
84
+ end
85
+
86
+ # Add breadcrumb for Reflex
87
+ BrainzLab::Reflex.add_breadcrumb(
88
+ "Job #{job_class} completed in #{duration_ms}ms",
89
+ category: 'job',
90
+ level: payload[:error] ? :error : :info,
91
+ data: { queue: queue, job_id: payload[:job_id], duration_ms: duration_ms }
92
+ )
93
+ end
94
+
95
+ def track_job_enqueue(payload)
96
+ job_class = payload[:job]&.class&.name || payload[:job_class]
97
+ queue = payload[:queue] || 'default'
98
+
99
+ return unless BrainzLab.configuration.flux_effectively_enabled?
100
+
101
+ BrainzLab::Flux.increment('solid_queue.job.enqueued', tags: { job_class: job_class, queue: queue })
102
+ end
103
+
104
+ def track_job_discard(payload)
105
+ job_class = payload[:job]&.class&.name || payload[:job_class]
106
+
107
+ return unless BrainzLab.configuration.flux_effectively_enabled?
108
+
109
+ BrainzLab::Flux.increment('solid_queue.job.discarded', tags: { job_class: job_class })
110
+ end
111
+
112
+ def track_job_retry(payload)
113
+ job_class = payload[:job]&.class&.name || payload[:job_class]
114
+
115
+ return unless BrainzLab.configuration.flux_effectively_enabled?
116
+
117
+ BrainzLab::Flux.increment('solid_queue.job.retried', tags: { job_class: job_class })
118
+ end
119
+ end
120
+
121
+ def self.around_perform(job)
122
+ job_class = job.class.name
123
+ queue = job.queue_name || 'default'
124
+ started_at = Time.now
125
+
126
+ # Set context for the job
127
+ BrainzLab::Context.current.set_context(
128
+ job_class: job_class,
129
+ job_id: job.job_id,
130
+ queue: queue
131
+ )
132
+
133
+ begin
134
+ yield
135
+ rescue StandardError => e
136
+ # Capture error with Reflex
137
+ if BrainzLab.configuration.reflex_effectively_enabled?
138
+ BrainzLab::Reflex.capture(e,
139
+ tags: { job_class: job_class, queue: queue },
140
+ extra: {
141
+ job_id: job.job_id,
142
+ arguments: safe_arguments(job),
143
+ executions: job.executions
144
+ })
145
+ end
146
+ raise
147
+ ensure
148
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
149
+
150
+ # Record trace
151
+ if BrainzLab.configuration.pulse_effectively_enabled?
152
+ BrainzLab::Pulse.record_trace(
153
+ "job.#{job_class}",
154
+ kind: 'job',
155
+ started_at: started_at,
156
+ ended_at: Time.now,
157
+ job_class: job_class,
158
+ job_id: job.job_id,
159
+ queue: queue,
160
+ executions: job.executions
161
+ )
162
+ end
163
+
164
+ # Record metrics
165
+ if BrainzLab.configuration.flux_effectively_enabled?
166
+ tags = { job_class: job_class, queue: queue }
167
+ BrainzLab::Flux.distribution('solid_queue.job.duration_ms', duration_ms, tags: tags)
168
+ end
169
+
170
+ # Clear context
171
+ BrainzLab.clear_context!
172
+ end
173
+ end
174
+
175
+ def self.around_enqueue(job)
176
+ yield
177
+ rescue StandardError => e
178
+ if BrainzLab.configuration.reflex_effectively_enabled?
179
+ BrainzLab::Reflex.capture(e,
180
+ tags: { job_class: job.class.name, queue: job.queue_name },
181
+ extra: { job_id: job.job_id, arguments: safe_arguments(job) })
182
+ end
183
+ raise
184
+ end
185
+
186
+ def self.safe_arguments(job)
187
+ args = job.arguments
188
+ BrainzLab::Reflex.send(:filter_params, args) if args
189
+ rescue StandardError
190
+ '[Unable to serialize]'
191
+ end
192
+ end
193
+ end
194
+ end