fluyenta-ruby 0.1.14
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 +7 -0
- data/CHANGELOG.md +68 -0
- data/LICENSE +11 -0
- data/README.md +571 -0
- data/lib/brainzlab/beacon/client.rb +227 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +676 -0
- data/lib/brainzlab/context.rb +90 -0
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +159 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +223 -0
- data/lib/brainzlab/debug.rb +305 -0
- data/lib/brainzlab/dendrite/client.rb +250 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/development/logger.rb +150 -0
- data/lib/brainzlab/development/store.rb +121 -0
- data/lib/brainzlab/development.rb +72 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
- data/lib/brainzlab/devtools/assets/devtools.js +396 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -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/errors.rb +490 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +68 -0
- data/lib/brainzlab/flux/provisioner.rb +124 -0
- data/lib/brainzlab/flux.rb +184 -0
- data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
- data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
- data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
- data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
- data/lib/brainzlab/instrumentation/action_view.rb +380 -0
- data/lib/brainzlab/instrumentation/active_job.rb +569 -0
- data/lib/brainzlab/instrumentation/active_record.rb +559 -0
- data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
- data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
- data/lib/brainzlab/instrumentation/aws.rb +183 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
- data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/faraday.rb +181 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/grape.rb +293 -0
- data/lib/brainzlab/instrumentation/graphql.rb +252 -0
- data/lib/brainzlab/instrumentation/httparty.rb +193 -0
- data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
- data/lib/brainzlab/instrumentation/net_http.rb +114 -0
- data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
- data/lib/brainzlab/instrumentation/railties.rb +134 -0
- data/lib/brainzlab/instrumentation/redis.rb +324 -0
- data/lib/brainzlab/instrumentation/resque.rb +114 -0
- data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
- 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 +360 -0
- data/lib/brainzlab/nerve/client.rb +235 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/client.rb +203 -0
- data/lib/brainzlab/pulse/instrumentation.rb +401 -0
- data/lib/brainzlab/pulse/propagation.rb +241 -0
- data/lib/brainzlab/pulse/provisioner.rb +114 -0
- data/lib/brainzlab/pulse/tracer.rb +111 -0
- data/lib/brainzlab/pulse.rb +294 -0
- data/lib/brainzlab/rails/log_formatter.rb +807 -0
- data/lib/brainzlab/rails/log_subscriber.rb +334 -0
- data/lib/brainzlab/rails/railtie.rb +606 -0
- data/lib/brainzlab/recall/buffer.rb +66 -0
- data/lib/brainzlab/recall/client.rb +158 -0
- data/lib/brainzlab/recall/logger.rb +116 -0
- data/lib/brainzlab/recall/provisioner.rb +130 -0
- data/lib/brainzlab/recall.rb +175 -0
- data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
- data/lib/brainzlab/reflex/client.rb +150 -0
- data/lib/brainzlab/reflex/provisioner.rb +116 -0
- data/lib/brainzlab/reflex.rb +421 -0
- data/lib/brainzlab/sentinel/client.rb +236 -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 +115 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +308 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/testing/event_store.rb +377 -0
- data/lib/brainzlab/testing/helpers.rb +650 -0
- data/lib/brainzlab/testing/matchers.rb +391 -0
- data/lib/brainzlab/testing.rb +327 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +290 -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 +216 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +262 -0
- data/lib/brainzlab/version.rb +5 -0
- data/lib/brainzlab/vision/client.rb +175 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +155 -0
- data/lib/brainzlab-sdk.rb +3 -0
- data/lib/brainzlab.rb +306 -0
- data/lib/generators/brainzlab/install/install_generator.rb +63 -0
- data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
- metadata +251 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module SidekiqInstrumentation
|
|
6
|
+
@installed = false
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def install!
|
|
10
|
+
return unless defined?(::Sidekiq)
|
|
11
|
+
return if @installed
|
|
12
|
+
|
|
13
|
+
::Sidekiq.configure_server do |config|
|
|
14
|
+
config.server_middleware do |chain|
|
|
15
|
+
chain.add ServerMiddleware
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Also add client middleware for distributed tracing
|
|
19
|
+
config.client_middleware do |chain|
|
|
20
|
+
chain.add ClientMiddleware
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Client-side middleware for when jobs are enqueued
|
|
25
|
+
::Sidekiq.configure_client do |config|
|
|
26
|
+
config.client_middleware do |chain|
|
|
27
|
+
chain.add ClientMiddleware
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@installed = true
|
|
32
|
+
BrainzLab.debug_log('Sidekiq instrumentation installed')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def installed?
|
|
36
|
+
@installed
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset!
|
|
40
|
+
@installed = false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Server middleware - runs when jobs are processed
|
|
45
|
+
class ServerMiddleware
|
|
46
|
+
def call(worker, job, queue)
|
|
47
|
+
return yield unless should_trace?
|
|
48
|
+
|
|
49
|
+
started_at = Time.now.utc
|
|
50
|
+
job_class = job['class'] || worker.class.name
|
|
51
|
+
job_id = job['jid']
|
|
52
|
+
|
|
53
|
+
# Calculate queue wait time
|
|
54
|
+
enqueued_at = job['enqueued_at'] ? Time.at(job['enqueued_at']) : nil
|
|
55
|
+
queue_wait_ms = enqueued_at ? ((started_at - enqueued_at) * 1000).round(2) : nil
|
|
56
|
+
|
|
57
|
+
# Extract parent trace context if present (distributed tracing)
|
|
58
|
+
parent_context = extract_trace_context(job)
|
|
59
|
+
|
|
60
|
+
# Set up context
|
|
61
|
+
setup_context(job, queue)
|
|
62
|
+
|
|
63
|
+
# Add breadcrumb
|
|
64
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
65
|
+
"Sidekiq #{job_class}",
|
|
66
|
+
category: 'job.sidekiq',
|
|
67
|
+
level: :info,
|
|
68
|
+
data: { job_id: job_id, queue: queue, retry_count: job['retry_count'] }
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Initialize Pulse tracing
|
|
72
|
+
Thread.current[:brainzlab_pulse_spans] = []
|
|
73
|
+
Thread.current[:brainzlab_pulse_breakdown] = nil
|
|
74
|
+
|
|
75
|
+
error_occurred = nil
|
|
76
|
+
begin
|
|
77
|
+
yield
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
error_occurred = e
|
|
80
|
+
raise
|
|
81
|
+
ensure
|
|
82
|
+
record_trace(
|
|
83
|
+
job_class: job_class,
|
|
84
|
+
job_id: job_id,
|
|
85
|
+
queue: queue,
|
|
86
|
+
started_at: started_at,
|
|
87
|
+
queue_wait_ms: queue_wait_ms,
|
|
88
|
+
retry_count: job['retry_count'] || 0,
|
|
89
|
+
parent_context: parent_context,
|
|
90
|
+
error: error_occurred
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
cleanup_context
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def should_trace?
|
|
100
|
+
BrainzLab.configuration.pulse_enabled
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def setup_context(job, queue)
|
|
104
|
+
BrainzLab::Context.current.set_context(
|
|
105
|
+
job_class: job['class'],
|
|
106
|
+
job_id: job['jid'],
|
|
107
|
+
queue_name: queue,
|
|
108
|
+
retry_count: job['retry_count'],
|
|
109
|
+
arguments: job['args']&.map(&:to_s)&.first(5)
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def cleanup_context
|
|
114
|
+
Thread.current[:brainzlab_pulse_spans] = nil
|
|
115
|
+
Thread.current[:brainzlab_pulse_breakdown] = nil
|
|
116
|
+
BrainzLab::Context.clear!
|
|
117
|
+
BrainzLab::Pulse::Propagation.clear!
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def extract_trace_context(job)
|
|
121
|
+
return nil unless job['_brainzlab_trace']
|
|
122
|
+
|
|
123
|
+
trace_data = job['_brainzlab_trace']
|
|
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
|
|
128
|
+
)
|
|
129
|
+
rescue StandardError
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def record_trace(job_class:, job_id:, queue:, started_at:, queue_wait_ms:, retry_count:, parent_context:,
|
|
134
|
+
error:)
|
|
135
|
+
ended_at = Time.now.utc
|
|
136
|
+
duration_ms = ((ended_at - started_at) * 1000).round(2)
|
|
137
|
+
|
|
138
|
+
# Collect spans
|
|
139
|
+
spans = Thread.current[:brainzlab_pulse_spans] || []
|
|
140
|
+
breakdown = Thread.current[:brainzlab_pulse_breakdown] || {}
|
|
141
|
+
|
|
142
|
+
formatted_spans = spans.map do |span|
|
|
143
|
+
{
|
|
144
|
+
span_id: span[:span_id],
|
|
145
|
+
name: span[:name],
|
|
146
|
+
kind: span[:kind],
|
|
147
|
+
started_at: format_timestamp(span[:started_at]),
|
|
148
|
+
ended_at: format_timestamp(span[:ended_at]),
|
|
149
|
+
duration_ms: span[:duration_ms],
|
|
150
|
+
data: span[:data],
|
|
151
|
+
error: span[:error],
|
|
152
|
+
error_class: span[:error_class],
|
|
153
|
+
error_message: span[:error_message]
|
|
154
|
+
}.compact
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
payload = {
|
|
158
|
+
trace_id: SecureRandom.uuid,
|
|
159
|
+
name: job_class,
|
|
160
|
+
kind: 'job',
|
|
161
|
+
started_at: started_at.utc.iso8601(3),
|
|
162
|
+
ended_at: ended_at.utc.iso8601(3),
|
|
163
|
+
duration_ms: duration_ms,
|
|
164
|
+
job_class: job_class,
|
|
165
|
+
job_id: job_id,
|
|
166
|
+
queue: queue,
|
|
167
|
+
queue_wait_ms: queue_wait_ms,
|
|
168
|
+
executions: retry_count + 1,
|
|
169
|
+
db_ms: breakdown[:db_ms],
|
|
170
|
+
error: error.present?,
|
|
171
|
+
error_class: error&.class&.name,
|
|
172
|
+
error_message: error&.message&.slice(0, 1000),
|
|
173
|
+
spans: formatted_spans,
|
|
174
|
+
environment: BrainzLab.configuration.environment,
|
|
175
|
+
commit: BrainzLab.configuration.commit,
|
|
176
|
+
host: BrainzLab.configuration.host
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# Add parent trace info for distributed tracing
|
|
180
|
+
if parent_context&.valid?
|
|
181
|
+
payload[:parent_trace_id] = parent_context.trace_id
|
|
182
|
+
payload[:parent_span_id] = parent_context.span_id
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
BrainzLab::Pulse.client.send_trace(payload.compact)
|
|
186
|
+
rescue StandardError => e
|
|
187
|
+
BrainzLab.debug_log("Sidekiq trace recording failed: #{e.message}")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def format_timestamp(ts)
|
|
191
|
+
return nil unless ts
|
|
192
|
+
|
|
193
|
+
case ts
|
|
194
|
+
when Time, DateTime then ts.utc.iso8601(3)
|
|
195
|
+
when Float, Integer then Time.at(ts).utc.iso8601(3)
|
|
196
|
+
when String then ts
|
|
197
|
+
else ts.to_s
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Client middleware - runs when jobs are enqueued
|
|
203
|
+
class ClientMiddleware
|
|
204
|
+
def call(_worker_class, job, queue, _redis_pool)
|
|
205
|
+
# Inject trace context for distributed tracing
|
|
206
|
+
inject_trace_context(job)
|
|
207
|
+
|
|
208
|
+
# Add breadcrumb for job enqueue
|
|
209
|
+
if BrainzLab.configuration.reflex_enabled
|
|
210
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
211
|
+
"Enqueue #{job['class']}",
|
|
212
|
+
category: 'job.sidekiq.enqueue',
|
|
213
|
+
level: :info,
|
|
214
|
+
data: { queue: queue, job_id: job['jid'] }
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Record span for Pulse
|
|
219
|
+
record_enqueue_span(job, queue)
|
|
220
|
+
|
|
221
|
+
yield
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
private
|
|
225
|
+
|
|
226
|
+
def inject_trace_context(job)
|
|
227
|
+
return unless BrainzLab.configuration.pulse_enabled
|
|
228
|
+
|
|
229
|
+
# Get or create propagation context
|
|
230
|
+
ctx = BrainzLab::Pulse::Propagation.current
|
|
231
|
+
ctx ||= BrainzLab::Pulse.send(:create_propagation_context)
|
|
232
|
+
|
|
233
|
+
return unless ctx&.valid?
|
|
234
|
+
|
|
235
|
+
job['_brainzlab_trace'] = {
|
|
236
|
+
'trace_id' => ctx.trace_id,
|
|
237
|
+
'span_id' => ctx.span_id,
|
|
238
|
+
'sampled' => ctx.sampled
|
|
239
|
+
}
|
|
240
|
+
rescue StandardError => e
|
|
241
|
+
BrainzLab.debug_log("Failed to inject Sidekiq trace context: #{e.message}")
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def record_enqueue_span(job, queue)
|
|
245
|
+
spans = Thread.current[:brainzlab_pulse_spans]
|
|
246
|
+
return unless spans
|
|
247
|
+
|
|
248
|
+
spans << {
|
|
249
|
+
span_id: SecureRandom.uuid,
|
|
250
|
+
name: "Enqueue #{job['class']}",
|
|
251
|
+
kind: 'job',
|
|
252
|
+
started_at: Time.now.utc,
|
|
253
|
+
ended_at: Time.now.utc,
|
|
254
|
+
duration_ms: 0,
|
|
255
|
+
data: {
|
|
256
|
+
job_class: job['class'],
|
|
257
|
+
job_id: job['jid'],
|
|
258
|
+
queue: queue
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
module StripeInstrumentation
|
|
6
|
+
class << self
|
|
7
|
+
def install!
|
|
8
|
+
return unless defined?(::Stripe)
|
|
9
|
+
|
|
10
|
+
install_instrumentation!
|
|
11
|
+
|
|
12
|
+
BrainzLab.debug_log('[Instrumentation] Stripe instrumentation installed')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def install_instrumentation!
|
|
18
|
+
# Stripe uses a request callback system
|
|
19
|
+
if ::Stripe.respond_to?(:add_instrumentation)
|
|
20
|
+
::Stripe.add_instrumentation do |event|
|
|
21
|
+
track_event(event)
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
# Fallback: monkey-patch the API resource
|
|
25
|
+
install_api_resource_patch!
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def install_api_resource_patch!
|
|
30
|
+
return unless defined?(::Stripe::StripeClient)
|
|
31
|
+
return unless ::Stripe::StripeClient.respond_to?(:execute_request)
|
|
32
|
+
|
|
33
|
+
::Stripe::StripeClient.class_eval do
|
|
34
|
+
class << self
|
|
35
|
+
alias_method :original_execute_request, :execute_request
|
|
36
|
+
|
|
37
|
+
def execute_request(method, path, api_base: nil, api_key: nil, headers: {}, params: {}, usage: [])
|
|
38
|
+
started_at = Time.now
|
|
39
|
+
resource = extract_resource(path)
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
response = original_execute_request(
|
|
43
|
+
method, path,
|
|
44
|
+
api_base: api_base,
|
|
45
|
+
api_key: api_key,
|
|
46
|
+
headers: headers,
|
|
47
|
+
params: params,
|
|
48
|
+
usage: usage
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
BrainzLab::Instrumentation::StripeInstrumentation.track_success(
|
|
52
|
+
method, resource, path, started_at, response
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
response
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
BrainzLab::Instrumentation::StripeInstrumentation.track_error(
|
|
58
|
+
method, resource, path, started_at, e
|
|
59
|
+
)
|
|
60
|
+
raise
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def extract_resource(path)
|
|
65
|
+
# /v1/customers/cus_xxx -> customers
|
|
66
|
+
parts = path.to_s.split('/').reject(&:empty?)
|
|
67
|
+
parts[1] || 'unknown'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def track_event(event)
|
|
74
|
+
duration_ms = (event[:duration] * 1000).round(2) if event[:duration]
|
|
75
|
+
method = event[:method].to_s.upcase
|
|
76
|
+
resource = event[:path].to_s.split('/')[2] || 'unknown'
|
|
77
|
+
|
|
78
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
79
|
+
"Stripe #{method} #{resource}",
|
|
80
|
+
category: 'payment',
|
|
81
|
+
level: event[:error] ? :error : :info,
|
|
82
|
+
data: {
|
|
83
|
+
method: method,
|
|
84
|
+
resource: resource,
|
|
85
|
+
status: event[:http_status],
|
|
86
|
+
duration_ms: duration_ms,
|
|
87
|
+
request_id: event[:request_id]
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
92
|
+
|
|
93
|
+
tags = { method: method, resource: resource }
|
|
94
|
+
BrainzLab::Flux.distribution('stripe.duration_ms', duration_ms, tags: tags) if duration_ms
|
|
95
|
+
BrainzLab::Flux.increment('stripe.requests', tags: tags)
|
|
96
|
+
|
|
97
|
+
return unless event[:error]
|
|
98
|
+
|
|
99
|
+
BrainzLab::Flux.increment('stripe.errors', tags: tags.merge(error_type: event[:error_type]))
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.track_success(method, resource, path, started_at, _response)
|
|
104
|
+
duration_ms = ((Time.now - started_at) * 1000).round(2)
|
|
105
|
+
|
|
106
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
107
|
+
"Stripe #{method.to_s.upcase} #{resource}",
|
|
108
|
+
category: 'payment',
|
|
109
|
+
level: :info,
|
|
110
|
+
data: {
|
|
111
|
+
method: method.to_s.upcase,
|
|
112
|
+
resource: resource,
|
|
113
|
+
path: path,
|
|
114
|
+
duration_ms: duration_ms
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return unless BrainzLab.configuration.flux_effectively_enabled?
|
|
119
|
+
|
|
120
|
+
tags = { method: method.to_s.upcase, resource: resource }
|
|
121
|
+
BrainzLab::Flux.distribution('stripe.duration_ms', duration_ms, tags: tags)
|
|
122
|
+
BrainzLab::Flux.increment('stripe.requests', tags: tags)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.track_error(method, resource, _path, started_at, error)
|
|
126
|
+
((Time.now - started_at) * 1000).round(2)
|
|
127
|
+
error_type = case error
|
|
128
|
+
when Stripe::CardError then 'card_error'
|
|
129
|
+
when Stripe::RateLimitError then 'rate_limit'
|
|
130
|
+
when Stripe::InvalidRequestError then 'invalid_request'
|
|
131
|
+
when Stripe::AuthenticationError then 'authentication'
|
|
132
|
+
when Stripe::APIConnectionError then 'connection'
|
|
133
|
+
when Stripe::StripeError then 'stripe_error'
|
|
134
|
+
else 'unknown'
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
138
|
+
"Stripe #{method.to_s.upcase} #{resource} failed: #{error.message}",
|
|
139
|
+
category: 'payment',
|
|
140
|
+
level: :error,
|
|
141
|
+
data: {
|
|
142
|
+
method: method.to_s.upcase,
|
|
143
|
+
resource: resource,
|
|
144
|
+
error_type: error_type,
|
|
145
|
+
error: error.class.name
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if BrainzLab.configuration.flux_effectively_enabled?
|
|
150
|
+
tags = { method: method.to_s.upcase, resource: resource, error_type: error_type }
|
|
151
|
+
BrainzLab::Flux.increment('stripe.errors', tags: tags)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Capture with Reflex (but filter sensitive data)
|
|
155
|
+
return unless BrainzLab.configuration.reflex_effectively_enabled?
|
|
156
|
+
|
|
157
|
+
BrainzLab::Reflex.capture(error,
|
|
158
|
+
tags: { source: 'stripe', resource: resource },
|
|
159
|
+
extra: { error_type: error_type })
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|