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.
- checksums.yaml +4 -4
- data/LICENSE +6 -21
- data/README.md +24 -2
- data/lib/brainzlab/beacon/client.rb +207 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +372 -32
- data/lib/brainzlab/context.rb +2 -3
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +139 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/dendrite/client.rb +230 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
- data/lib/brainzlab/devtools/assets/devtools.js +322 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +57 -0
- data/lib/brainzlab/flux.rb +174 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
- data/lib/brainzlab/instrumentation/active_record.rb +28 -13
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
- data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +3 -4
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +24 -24
- data/lib/brainzlab/instrumentation/graphql.rb +24 -23
- data/lib/brainzlab/instrumentation/httparty.rb +13 -14
- data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
- data/lib/brainzlab/instrumentation/net_http.rb +6 -6
- data/lib/brainzlab/instrumentation/redis.rb +14 -21
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
- data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
- data/lib/brainzlab/instrumentation/stripe.rb +163 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
- data/lib/brainzlab/instrumentation.rb +84 -12
- data/lib/brainzlab/nerve/client.rb +215 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +15 -11
- data/lib/brainzlab/pulse/instrumentation.rb +90 -53
- data/lib/brainzlab/pulse/propagation.rb +29 -29
- data/lib/brainzlab/pulse/provisioner.rb +12 -12
- data/lib/brainzlab/pulse/tracer.rb +4 -4
- data/lib/brainzlab/pulse.rb +14 -14
- data/lib/brainzlab/rails/log_formatter.rb +127 -121
- data/lib/brainzlab/rails/log_subscriber.rb +70 -77
- data/lib/brainzlab/rails/railtie.rb +96 -86
- data/lib/brainzlab/recall/buffer.rb +1 -1
- data/lib/brainzlab/recall/client.rb +14 -10
- data/lib/brainzlab/recall/logger.rb +16 -18
- data/lib/brainzlab/recall/provisioner.rb +29 -12
- data/lib/brainzlab/recall.rb +14 -11
- data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
- data/lib/brainzlab/reflex/client.rb +14 -10
- data/lib/brainzlab/reflex/provisioner.rb +12 -12
- data/lib/brainzlab/reflex.rb +31 -31
- data/lib/brainzlab/sentinel/client.rb +216 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +60 -0
- data/lib/brainzlab/signal/provisioner.rb +55 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +288 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +261 -0
- data/lib/brainzlab/utilities/health_check.rb +294 -0
- data/lib/brainzlab/utilities/log_formatter.rb +254 -0
- data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
- data/lib/brainzlab/utilities.rb +17 -0
- data/lib/brainzlab/vault/cache.rb +80 -0
- data/lib/brainzlab/vault/client.rb +196 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -0
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +128 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +155 -0
- data/lib/brainzlab-sdk.rb +1 -1
- data/lib/brainzlab.rb +112 -13
- data/lib/generators/brainzlab/install/install_generator.rb +29 -27
- metadata +60 -1
|
@@ -18,7 +18,7 @@ module BrainzLab
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
@installed = true
|
|
21
|
-
BrainzLab.debug_log(
|
|
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(
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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[
|
|
51
|
-
job_id = job[
|
|
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[
|
|
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:
|
|
66
|
+
category: 'job.sidekiq',
|
|
67
67
|
level: :info,
|
|
68
|
-
data: { job_id: job_id, queue: queue, retry_count: job[
|
|
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[
|
|
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[
|
|
106
|
-
job_id: job[
|
|
105
|
+
job_class: job['class'],
|
|
106
|
+
job_id: job['jid'],
|
|
107
107
|
queue_name: queue,
|
|
108
|
-
retry_count: job[
|
|
109
|
-
arguments: job[
|
|
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[
|
|
121
|
+
return nil unless job['_brainzlab_trace']
|
|
122
122
|
|
|
123
|
-
trace_data = job[
|
|
123
|
+
trace_data = job['_brainzlab_trace']
|
|
124
124
|
BrainzLab::Pulse::Propagation::Context.new(
|
|
125
|
-
trace_id: trace_data[
|
|
126
|
-
span_id: trace_data[
|
|
127
|
-
sampled: trace_data[
|
|
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:,
|
|
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:
|
|
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(
|
|
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:
|
|
212
|
+
category: 'job.sidekiq.enqueue',
|
|
212
213
|
level: :info,
|
|
213
|
-
data: { queue: queue, job_id: job[
|
|
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[
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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:
|
|
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[
|
|
256
|
-
job_id: job[
|
|
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
|