brainzlab 0.1.0 → 0.1.2

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +30 -0
  4. data/lib/brainzlab/beacon/client.rb +209 -0
  5. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  6. data/lib/brainzlab/beacon.rb +215 -0
  7. data/lib/brainzlab/configuration.rb +341 -3
  8. data/lib/brainzlab/cortex/cache.rb +59 -0
  9. data/lib/brainzlab/cortex/client.rb +141 -0
  10. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  11. data/lib/brainzlab/cortex.rb +227 -0
  12. data/lib/brainzlab/dendrite/client.rb +232 -0
  13. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  14. data/lib/brainzlab/dendrite.rb +195 -0
  15. data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
  16. data/lib/brainzlab/devtools/assets/devtools.js +322 -0
  17. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  18. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
  19. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  20. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  21. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  22. data/lib/brainzlab/devtools/middleware/database_handler.rb +180 -0
  23. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  24. data/lib/brainzlab/devtools/middleware/error_page.rb +376 -0
  25. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +155 -0
  26. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +94 -0
  27. data/lib/brainzlab/devtools.rb +75 -0
  28. data/lib/brainzlab/flux/buffer.rb +96 -0
  29. data/lib/brainzlab/flux/client.rb +70 -0
  30. data/lib/brainzlab/flux/provisioner.rb +57 -0
  31. data/lib/brainzlab/flux.rb +174 -0
  32. data/lib/brainzlab/instrumentation/active_record.rb +18 -1
  33. data/lib/brainzlab/instrumentation/aws.rb +179 -0
  34. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  35. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  36. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  37. data/lib/brainzlab/instrumentation/resque.rb +115 -0
  38. data/lib/brainzlab/instrumentation/solid_queue.rb +198 -0
  39. data/lib/brainzlab/instrumentation/stripe.rb +164 -0
  40. data/lib/brainzlab/instrumentation/typhoeus.rb +104 -0
  41. data/lib/brainzlab/instrumentation.rb +72 -0
  42. data/lib/brainzlab/nerve/client.rb +217 -0
  43. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  44. data/lib/brainzlab/nerve.rb +219 -0
  45. data/lib/brainzlab/pulse/instrumentation.rb +35 -2
  46. data/lib/brainzlab/pulse/propagation.rb +1 -1
  47. data/lib/brainzlab/pulse/tracer.rb +1 -1
  48. data/lib/brainzlab/pulse.rb +1 -1
  49. data/lib/brainzlab/rails/log_subscriber.rb +1 -2
  50. data/lib/brainzlab/rails/railtie.rb +36 -3
  51. data/lib/brainzlab/recall/provisioner.rb +17 -0
  52. data/lib/brainzlab/recall.rb +6 -1
  53. data/lib/brainzlab/reflex.rb +20 -5
  54. data/lib/brainzlab/sentinel/client.rb +218 -0
  55. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  56. data/lib/brainzlab/sentinel.rb +165 -0
  57. data/lib/brainzlab/signal/client.rb +62 -0
  58. data/lib/brainzlab/signal/provisioner.rb +55 -0
  59. data/lib/brainzlab/signal.rb +136 -0
  60. data/lib/brainzlab/synapse/client.rb +290 -0
  61. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  62. data/lib/brainzlab/synapse.rb +270 -0
  63. data/lib/brainzlab/utilities/circuit_breaker.rb +265 -0
  64. data/lib/brainzlab/utilities/health_check.rb +296 -0
  65. data/lib/brainzlab/utilities/log_formatter.rb +256 -0
  66. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  67. data/lib/brainzlab/utilities.rb +17 -0
  68. data/lib/brainzlab/vault/cache.rb +80 -0
  69. data/lib/brainzlab/vault/client.rb +198 -0
  70. data/lib/brainzlab/vault/provisioner.rb +49 -0
  71. data/lib/brainzlab/vault.rb +268 -0
  72. data/lib/brainzlab/version.rb +1 -1
  73. data/lib/brainzlab/vision/client.rb +128 -0
  74. data/lib/brainzlab/vision/provisioner.rb +136 -0
  75. data/lib/brainzlab/vision.rb +157 -0
  76. data/lib/brainzlab.rb +101 -0
  77. metadata +62 -2
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ module DalliInstrumentation
6
+ TRACKED_COMMANDS = %w[get set add replace delete incr decr cas get_multi set_multi].freeze
7
+
8
+ class << self
9
+ def install!
10
+ return unless defined?(::Dalli::Client)
11
+
12
+ install_client_instrumentation!
13
+
14
+ BrainzLab.debug_log("[Instrumentation] Dalli/Memcached instrumentation installed")
15
+ end
16
+
17
+ private
18
+
19
+ def install_client_instrumentation!
20
+ ::Dalli::Client.class_eval do
21
+ TRACKED_COMMANDS.each do |cmd|
22
+ original_method = "original_#{cmd}"
23
+ next if method_defined?(original_method)
24
+
25
+ alias_method original_method, cmd
26
+
27
+ define_method(cmd) do |*args, &block|
28
+ BrainzLab::Instrumentation::DalliInstrumentation.track_command(cmd, args) do
29
+ send(original_method, *args, &block)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.track_command(command, args)
38
+ started_at = Time.now
39
+
40
+ begin
41
+ result = yield
42
+ track_success(command, args, started_at, result)
43
+ result
44
+ rescue StandardError => e
45
+ track_error(command, args, started_at, e)
46
+ raise
47
+ end
48
+ end
49
+
50
+ def self.track_success(command, args, started_at, result)
51
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
52
+ key = extract_key(args)
53
+
54
+ # Add breadcrumb
55
+ BrainzLab::Reflex.add_breadcrumb(
56
+ "Memcached #{command.upcase}",
57
+ category: "cache",
58
+ level: :info,
59
+ data: { command: command, key: key, duration_ms: duration_ms }
60
+ )
61
+
62
+ # Track with Flux
63
+ if BrainzLab.configuration.flux_effectively_enabled?
64
+ tags = { command: command }
65
+ BrainzLab::Flux.distribution("memcached.duration_ms", duration_ms, tags: tags)
66
+ BrainzLab::Flux.increment("memcached.commands", tags: tags)
67
+
68
+ # Track cache hits/misses for get commands
69
+ if command == "get"
70
+ if result.nil?
71
+ BrainzLab::Flux.increment("memcached.miss", tags: tags)
72
+ else
73
+ BrainzLab::Flux.increment("memcached.hit", tags: tags)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def self.track_error(command, args, started_at, error)
80
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
81
+ key = extract_key(args)
82
+
83
+ BrainzLab::Reflex.add_breadcrumb(
84
+ "Memcached #{command.upcase} failed: #{error.message}",
85
+ category: "cache",
86
+ level: :error,
87
+ data: { command: command, key: key, error: error.class.name }
88
+ )
89
+
90
+ if BrainzLab.configuration.flux_effectively_enabled?
91
+ BrainzLab::Flux.increment("memcached.errors", tags: { command: command })
92
+ end
93
+ end
94
+
95
+ def self.extract_key(args)
96
+ key = args.first
97
+ case key
98
+ when String
99
+ key.length > 50 ? "#{key[0..47]}..." : key
100
+ when Array
101
+ "[#{key.size} keys]"
102
+ else
103
+ key.to_s[0..50]
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ module ExconInstrumentation
6
+ class << self
7
+ def install!
8
+ return unless defined?(::Excon)
9
+
10
+ install_middleware!
11
+
12
+ BrainzLab.debug_log("[Instrumentation] Excon instrumentation installed")
13
+ end
14
+
15
+ private
16
+
17
+ def install_middleware!
18
+ # Add our instrumentor to Excon defaults
19
+ ::Excon.defaults[:instrumentor] = BrainzLabInstrumentor
20
+
21
+ # Also set up middleware
22
+ if ::Excon.defaults[:middlewares]
23
+ ::Excon.defaults[:middlewares] = [Middleware] + ::Excon.defaults[:middlewares]
24
+ end
25
+ end
26
+ end
27
+
28
+ # Excon Instrumentor for ActiveSupport-style notifications
29
+ module BrainzLabInstrumentor
30
+ def self.instrument(name, params = {}, &block)
31
+ started_at = Time.now
32
+
33
+ begin
34
+ result = yield if block_given?
35
+ track_request(name, params, started_at, nil)
36
+ result
37
+ rescue StandardError => e
38
+ track_request(name, params, started_at, e)
39
+ raise
40
+ end
41
+ end
42
+
43
+ def self.track_request(name, params, started_at, error)
44
+ return if skip_tracking?(params)
45
+
46
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
47
+ host = params[:host] || "unknown"
48
+ method = (params[:method] || "GET").to_s.upcase
49
+ path = params[:path] || "/"
50
+ status = params[:status]
51
+
52
+ # Add breadcrumb
53
+ BrainzLab::Reflex.add_breadcrumb(
54
+ "HTTP #{method} #{host}#{path}",
55
+ category: "http",
56
+ level: error ? :error : :info,
57
+ data: {
58
+ method: method,
59
+ host: host,
60
+ path: path,
61
+ status: status,
62
+ duration_ms: duration_ms
63
+ }
64
+ )
65
+
66
+ # Track with Pulse
67
+ if BrainzLab.configuration.pulse_effectively_enabled?
68
+ BrainzLab::Pulse.span("http.excon", kind: "http") do
69
+ # Already completed, just recording
70
+ end
71
+ end
72
+
73
+ # Track with Flux
74
+ if BrainzLab.configuration.flux_effectively_enabled?
75
+ tags = { host: host, method: method, status: status.to_s }
76
+ BrainzLab::Flux.distribution("http.excon.duration_ms", duration_ms, tags: tags)
77
+ BrainzLab::Flux.increment("http.excon.requests", tags: tags)
78
+
79
+ if error || (status && status >= 400)
80
+ BrainzLab::Flux.increment("http.excon.errors", tags: tags)
81
+ end
82
+ end
83
+ end
84
+
85
+ def self.skip_tracking?(params)
86
+ host = params[:host]
87
+ return true unless host
88
+
89
+ ignore_hosts = BrainzLab.configuration.http_ignore_hosts || []
90
+ ignore_hosts.any? { |h| host.include?(h) }
91
+ end
92
+ end
93
+
94
+ # Excon Middleware
95
+ class Middleware
96
+ def initialize(stack)
97
+ @stack = stack
98
+ end
99
+
100
+ def request_call(datum)
101
+ datum[:brainzlab_started_at] = Time.now
102
+ @stack.request_call(datum)
103
+ end
104
+
105
+ def response_call(datum)
106
+ track_response(datum)
107
+ @stack.response_call(datum)
108
+ end
109
+
110
+ def error_call(datum)
111
+ track_response(datum, error: true)
112
+ @stack.error_call(datum)
113
+ end
114
+
115
+ private
116
+
117
+ def track_response(datum, error: false)
118
+ started_at = datum[:brainzlab_started_at]
119
+ return unless started_at
120
+
121
+ host = datum[:host]
122
+ return if skip_host?(host)
123
+
124
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
125
+ method = (datum[:method] || "GET").to_s.upcase
126
+ path = datum[:path] || "/"
127
+ status = datum[:response]&.dig(:status)
128
+
129
+ BrainzLab::Reflex.add_breadcrumb(
130
+ "HTTP #{method} #{host}#{path} -> #{status || 'error'}",
131
+ category: "http",
132
+ level: error ? :error : :info,
133
+ data: { method: method, host: host, status: status, duration_ms: duration_ms }
134
+ )
135
+
136
+ if BrainzLab.configuration.flux_effectively_enabled?
137
+ tags = { host: host, method: method }
138
+ tags[:status] = status.to_s if status
139
+ BrainzLab::Flux.distribution("http.excon.duration_ms", duration_ms, tags: tags)
140
+ end
141
+ end
142
+
143
+ def skip_host?(host)
144
+ return true unless host
145
+
146
+ ignore_hosts = BrainzLab.configuration.http_ignore_hosts || []
147
+ ignore_hosts.any? { |h| host.include?(h) }
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ module GoodJobInstrumentation
6
+ class << self
7
+ def install!
8
+ return unless defined?(::GoodJob)
9
+
10
+ install_notifier!
11
+ install_middleware!
12
+
13
+ BrainzLab.debug_log("[Instrumentation] GoodJob instrumentation installed")
14
+ end
15
+
16
+ private
17
+
18
+ def install_notifier!
19
+ return unless defined?(::ActiveSupport::Notifications)
20
+
21
+ # GoodJob emits ActiveSupport notifications
22
+ ::ActiveSupport::Notifications.subscribe("perform_job.good_job") do |*args|
23
+ event = ::ActiveSupport::Notifications::Event.new(*args)
24
+ handle_perform(event)
25
+ end
26
+
27
+ ::ActiveSupport::Notifications.subscribe("finished_job_task.good_job") do |*args|
28
+ event = ::ActiveSupport::Notifications::Event.new(*args)
29
+ handle_finished(event)
30
+ end
31
+ end
32
+
33
+ def install_middleware!
34
+ return unless defined?(::GoodJob::Adapter)
35
+
36
+ # Add our callback to GoodJob
37
+ if ::GoodJob.respond_to?(:on_thread_error)
38
+ ::GoodJob.on_thread_error = ->(error) do
39
+ BrainzLab::Reflex.capture(error, tags: { source: "good_job" }) if BrainzLab.configuration.reflex_effectively_enabled?
40
+ end
41
+ end
42
+ end
43
+
44
+ def handle_perform(event)
45
+ payload = event.payload
46
+ job = payload[:job]
47
+ job_class = job&.class&.name || payload[:job_class] || "Unknown"
48
+ queue = job&.queue_name || payload[:queue_name] || "default"
49
+ duration_ms = event.duration.round(2)
50
+
51
+ # Track with Pulse
52
+ if BrainzLab.configuration.pulse_effectively_enabled?
53
+ BrainzLab::Pulse.record_trace(
54
+ "job.#{job_class}",
55
+ kind: "job",
56
+ started_at: event.time,
57
+ ended_at: event.end,
58
+ job_class: job_class,
59
+ job_id: job&.job_id || payload[:job_id],
60
+ queue: queue,
61
+ error: payload[:error].present?,
62
+ error_class: payload[:error]&.class&.name,
63
+ error_message: payload[:error]&.message
64
+ )
65
+ end
66
+
67
+ # Track with Flux
68
+ if BrainzLab.configuration.flux_effectively_enabled?
69
+ tags = { job_class: job_class, queue: queue }
70
+ BrainzLab::Flux.distribution("good_job.job.duration_ms", duration_ms, tags: tags)
71
+ BrainzLab::Flux.increment("good_job.job.processed", tags: tags)
72
+
73
+ if payload[:error]
74
+ BrainzLab::Flux.increment("good_job.job.failed", tags: tags)
75
+ end
76
+ end
77
+
78
+ # Capture error with Reflex
79
+ if payload[:error] && BrainzLab.configuration.reflex_effectively_enabled?
80
+ BrainzLab::Reflex.capture(payload[:error],
81
+ tags: { job_class: job_class, queue: queue, source: "good_job" },
82
+ extra: { job_id: job&.job_id, duration_ms: duration_ms }
83
+ )
84
+ end
85
+ end
86
+
87
+ def handle_finished(event)
88
+ payload = event.payload
89
+
90
+ if BrainzLab.configuration.flux_effectively_enabled?
91
+ result = payload[:result]
92
+ if result == :discarded
93
+ BrainzLab::Flux.increment("good_job.job.discarded")
94
+ elsif result == :retried
95
+ BrainzLab::Flux.increment("good_job.job.retried")
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,115 @@
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
+ )
51
+ end
52
+
53
+ if BrainzLab.configuration.flux_effectively_enabled?
54
+ BrainzLab::Flux.increment("resque.job.failed", tags: { job_class: job_class, queue: @queue })
55
+ end
56
+ end
57
+ end
58
+
59
+ # Add our failure backend to the chain
60
+ if defined?(::Resque::Failure)
61
+ ::Resque::Failure.backend = ::Resque::Failure::Multiple.new(
62
+ ::Resque::Failure.backend,
63
+ failure_backend
64
+ )
65
+ end
66
+ end
67
+ end
68
+
69
+ # Middleware module to include in Resque jobs
70
+ module Middleware
71
+ def self.included(base)
72
+ base.extend(ClassMethods)
73
+ end
74
+
75
+ module ClassMethods
76
+ def around_perform_brainzlab(*args)
77
+ job_class = self.name
78
+ queue = Resque.queue_from_class(self) || "default"
79
+ started_at = Time.now
80
+
81
+ BrainzLab::Context.current.set_context(
82
+ job_class: job_class,
83
+ queue: queue
84
+ )
85
+
86
+ begin
87
+ yield
88
+ ensure
89
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
90
+
91
+ if BrainzLab.configuration.pulse_effectively_enabled?
92
+ BrainzLab::Pulse.record_trace(
93
+ "job.#{job_class}",
94
+ kind: "job",
95
+ started_at: started_at,
96
+ ended_at: Time.now,
97
+ job_class: job_class,
98
+ queue: queue
99
+ )
100
+ end
101
+
102
+ if BrainzLab.configuration.flux_effectively_enabled?
103
+ tags = { job_class: job_class, queue: queue }
104
+ BrainzLab::Flux.distribution("resque.job.duration_ms", duration_ms, tags: tags)
105
+ BrainzLab::Flux.increment("resque.job.processed", tags: tags)
106
+ end
107
+
108
+ BrainzLab.clear_context!
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,198 @@
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
+ if defined?(::ActiveSupport::Notifications)
35
+ ::ActiveSupport::Notifications.subscribe(/solid_queue/) do |name, start, finish, id, payload|
36
+ handle_notification(name, start, finish, payload)
37
+ end
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
+ if payload[:error]
84
+ BrainzLab::Flux.increment("solid_queue.job.failed", tags: tags)
85
+ end
86
+ end
87
+
88
+ # Add breadcrumb for Reflex
89
+ BrainzLab::Reflex.add_breadcrumb(
90
+ "Job #{job_class} completed in #{duration_ms}ms",
91
+ category: "job",
92
+ level: payload[:error] ? :error : :info,
93
+ data: { queue: queue, job_id: payload[:job_id], duration_ms: duration_ms }
94
+ )
95
+ end
96
+
97
+ def track_job_enqueue(payload)
98
+ job_class = payload[:job]&.class&.name || payload[:job_class]
99
+ queue = payload[:queue] || "default"
100
+
101
+ if BrainzLab.configuration.flux_effectively_enabled?
102
+ BrainzLab::Flux.increment("solid_queue.job.enqueued", tags: { job_class: job_class, queue: queue })
103
+ end
104
+ end
105
+
106
+ def track_job_discard(payload)
107
+ job_class = payload[:job]&.class&.name || payload[:job_class]
108
+
109
+ if BrainzLab.configuration.flux_effectively_enabled?
110
+ BrainzLab::Flux.increment("solid_queue.job.discarded", tags: { job_class: job_class })
111
+ end
112
+ end
113
+
114
+ def track_job_retry(payload)
115
+ job_class = payload[:job]&.class&.name || payload[:job_class]
116
+
117
+ if BrainzLab.configuration.flux_effectively_enabled?
118
+ BrainzLab::Flux.increment("solid_queue.job.retried", tags: { job_class: job_class })
119
+ end
120
+ end
121
+ end
122
+
123
+ def self.around_perform(job)
124
+ job_class = job.class.name
125
+ queue = job.queue_name || "default"
126
+ started_at = Time.now
127
+
128
+ # Set context for the job
129
+ BrainzLab::Context.current.set_context(
130
+ job_class: job_class,
131
+ job_id: job.job_id,
132
+ queue: queue
133
+ )
134
+
135
+ begin
136
+ yield
137
+ rescue StandardError => e
138
+ # Capture error with Reflex
139
+ if BrainzLab.configuration.reflex_effectively_enabled?
140
+ BrainzLab::Reflex.capture(e,
141
+ tags: { job_class: job_class, queue: queue },
142
+ extra: {
143
+ job_id: job.job_id,
144
+ arguments: safe_arguments(job),
145
+ executions: job.executions
146
+ }
147
+ )
148
+ end
149
+ raise
150
+ ensure
151
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
152
+
153
+ # Record trace
154
+ if BrainzLab.configuration.pulse_effectively_enabled?
155
+ BrainzLab::Pulse.record_trace(
156
+ "job.#{job_class}",
157
+ kind: "job",
158
+ started_at: started_at,
159
+ ended_at: Time.now,
160
+ job_class: job_class,
161
+ job_id: job.job_id,
162
+ queue: queue,
163
+ executions: job.executions
164
+ )
165
+ end
166
+
167
+ # Record metrics
168
+ if BrainzLab.configuration.flux_effectively_enabled?
169
+ tags = { job_class: job_class, queue: queue }
170
+ BrainzLab::Flux.distribution("solid_queue.job.duration_ms", duration_ms, tags: tags)
171
+ end
172
+
173
+ # Clear context
174
+ BrainzLab.clear_context!
175
+ end
176
+ end
177
+
178
+ def self.around_enqueue(job)
179
+ yield
180
+ rescue StandardError => e
181
+ if BrainzLab.configuration.reflex_effectively_enabled?
182
+ BrainzLab::Reflex.capture(e,
183
+ tags: { job_class: job.class.name, queue: job.queue_name },
184
+ extra: { job_id: job.job_id, arguments: safe_arguments(job) }
185
+ )
186
+ end
187
+ raise
188
+ end
189
+
190
+ def self.safe_arguments(job)
191
+ args = job.arguments
192
+ BrainzLab::Reflex.send(:filter_params, args) if args
193
+ rescue StandardError
194
+ "[Unable to serialize]"
195
+ end
196
+ end
197
+ end
198
+ end