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.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +11 -0
  4. data/README.md +571 -0
  5. data/lib/brainzlab/beacon/client.rb +227 -0
  6. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  7. data/lib/brainzlab/beacon.rb +215 -0
  8. data/lib/brainzlab/configuration.rb +676 -0
  9. data/lib/brainzlab/context.rb +90 -0
  10. data/lib/brainzlab/cortex/cache.rb +59 -0
  11. data/lib/brainzlab/cortex/client.rb +159 -0
  12. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  13. data/lib/brainzlab/cortex.rb +223 -0
  14. data/lib/brainzlab/debug.rb +305 -0
  15. data/lib/brainzlab/dendrite/client.rb +250 -0
  16. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  17. data/lib/brainzlab/dendrite.rb +195 -0
  18. data/lib/brainzlab/development/logger.rb +150 -0
  19. data/lib/brainzlab/development/store.rb +121 -0
  20. data/lib/brainzlab/development.rb +72 -0
  21. data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
  22. data/lib/brainzlab/devtools/assets/devtools.js +396 -0
  23. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  24. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -0
  25. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  26. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  27. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  28. data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
  29. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  30. data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
  31. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
  32. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
  33. data/lib/brainzlab/devtools.rb +75 -0
  34. data/lib/brainzlab/errors.rb +490 -0
  35. data/lib/brainzlab/flux/buffer.rb +96 -0
  36. data/lib/brainzlab/flux/client.rb +68 -0
  37. data/lib/brainzlab/flux/provisioner.rb +124 -0
  38. data/lib/brainzlab/flux.rb +184 -0
  39. data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
  40. data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
  41. data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
  42. data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
  43. data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
  44. data/lib/brainzlab/instrumentation/action_view.rb +380 -0
  45. data/lib/brainzlab/instrumentation/active_job.rb +569 -0
  46. data/lib/brainzlab/instrumentation/active_record.rb +559 -0
  47. data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
  48. data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
  49. data/lib/brainzlab/instrumentation/aws.rb +183 -0
  50. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  51. data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
  52. data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
  53. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  54. data/lib/brainzlab/instrumentation/faraday.rb +181 -0
  55. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  56. data/lib/brainzlab/instrumentation/grape.rb +293 -0
  57. data/lib/brainzlab/instrumentation/graphql.rb +252 -0
  58. data/lib/brainzlab/instrumentation/httparty.rb +193 -0
  59. data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
  60. data/lib/brainzlab/instrumentation/net_http.rb +114 -0
  61. data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
  62. data/lib/brainzlab/instrumentation/railties.rb +134 -0
  63. data/lib/brainzlab/instrumentation/redis.rb +324 -0
  64. data/lib/brainzlab/instrumentation/resque.rb +114 -0
  65. data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
  66. data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
  67. data/lib/brainzlab/instrumentation/stripe.rb +163 -0
  68. data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
  69. data/lib/brainzlab/instrumentation.rb +360 -0
  70. data/lib/brainzlab/nerve/client.rb +235 -0
  71. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  72. data/lib/brainzlab/nerve.rb +219 -0
  73. data/lib/brainzlab/pulse/client.rb +203 -0
  74. data/lib/brainzlab/pulse/instrumentation.rb +401 -0
  75. data/lib/brainzlab/pulse/propagation.rb +241 -0
  76. data/lib/brainzlab/pulse/provisioner.rb +114 -0
  77. data/lib/brainzlab/pulse/tracer.rb +111 -0
  78. data/lib/brainzlab/pulse.rb +294 -0
  79. data/lib/brainzlab/rails/log_formatter.rb +807 -0
  80. data/lib/brainzlab/rails/log_subscriber.rb +334 -0
  81. data/lib/brainzlab/rails/railtie.rb +606 -0
  82. data/lib/brainzlab/recall/buffer.rb +66 -0
  83. data/lib/brainzlab/recall/client.rb +158 -0
  84. data/lib/brainzlab/recall/logger.rb +116 -0
  85. data/lib/brainzlab/recall/provisioner.rb +130 -0
  86. data/lib/brainzlab/recall.rb +175 -0
  87. data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
  88. data/lib/brainzlab/reflex/client.rb +150 -0
  89. data/lib/brainzlab/reflex/provisioner.rb +116 -0
  90. data/lib/brainzlab/reflex.rb +421 -0
  91. data/lib/brainzlab/sentinel/client.rb +236 -0
  92. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  93. data/lib/brainzlab/sentinel.rb +165 -0
  94. data/lib/brainzlab/signal/client.rb +60 -0
  95. data/lib/brainzlab/signal/provisioner.rb +115 -0
  96. data/lib/brainzlab/signal.rb +136 -0
  97. data/lib/brainzlab/synapse/client.rb +308 -0
  98. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  99. data/lib/brainzlab/synapse.rb +270 -0
  100. data/lib/brainzlab/testing/event_store.rb +377 -0
  101. data/lib/brainzlab/testing/helpers.rb +650 -0
  102. data/lib/brainzlab/testing/matchers.rb +391 -0
  103. data/lib/brainzlab/testing.rb +327 -0
  104. data/lib/brainzlab/utilities/circuit_breaker.rb +290 -0
  105. data/lib/brainzlab/utilities/health_check.rb +294 -0
  106. data/lib/brainzlab/utilities/log_formatter.rb +254 -0
  107. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  108. data/lib/brainzlab/utilities.rb +17 -0
  109. data/lib/brainzlab/vault/cache.rb +80 -0
  110. data/lib/brainzlab/vault/client.rb +216 -0
  111. data/lib/brainzlab/vault/provisioner.rb +49 -0
  112. data/lib/brainzlab/vault.rb +262 -0
  113. data/lib/brainzlab/version.rb +5 -0
  114. data/lib/brainzlab/vision/client.rb +175 -0
  115. data/lib/brainzlab/vision/provisioner.rb +136 -0
  116. data/lib/brainzlab/vision.rb +155 -0
  117. data/lib/brainzlab-sdk.rb +3 -0
  118. data/lib/brainzlab.rb +306 -0
  119. data/lib/generators/brainzlab/install/install_generator.rb +63 -0
  120. data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
  121. metadata +251 -0
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ module TyphoeusInstrumentation
6
+ class << self
7
+ def install!
8
+ return unless defined?(::Typhoeus)
9
+
10
+ install_callbacks!
11
+
12
+ BrainzLab.debug_log('[Instrumentation] Typhoeus instrumentation installed')
13
+ end
14
+
15
+ private
16
+
17
+ def install_callbacks!
18
+ ::Typhoeus.on_complete do |response|
19
+ track_request(response)
20
+ end
21
+ end
22
+
23
+ def track_request(response)
24
+ request = response.request
25
+ return unless request
26
+
27
+ uri = begin
28
+ URI.parse(request.base_url)
29
+ rescue StandardError
30
+ nil
31
+ end
32
+ return unless uri
33
+
34
+ host = uri.host
35
+ return if skip_host?(host)
36
+
37
+ method = (request.options[:method] || :get).to_s.upcase
38
+ path = uri.path.empty? ? '/' : uri.path
39
+ status = response.response_code
40
+ duration_ms = (response.total_time * 1000).round(2)
41
+
42
+ # Add breadcrumb
43
+ BrainzLab::Reflex.add_breadcrumb(
44
+ "HTTP #{method} #{host}#{path} -> #{status}",
45
+ category: 'http',
46
+ level: response.success? ? :info : :error,
47
+ data: {
48
+ method: method,
49
+ host: host,
50
+ path: path,
51
+ status: status,
52
+ duration_ms: duration_ms
53
+ }
54
+ )
55
+
56
+ # Track with Flux
57
+ return unless BrainzLab.configuration.flux_effectively_enabled?
58
+
59
+ tags = { host: host, method: method, status: status.to_s }
60
+ BrainzLab::Flux.distribution('http.typhoeus.duration_ms', duration_ms, tags: tags)
61
+ BrainzLab::Flux.increment('http.typhoeus.requests', tags: tags)
62
+
63
+ BrainzLab::Flux.increment('http.typhoeus.errors', tags: tags) unless response.success?
64
+
65
+ return unless response.timed_out?
66
+
67
+ BrainzLab::Flux.increment('http.typhoeus.timeouts', tags: { host: host })
68
+ end
69
+
70
+ def skip_host?(host)
71
+ return true unless host
72
+
73
+ ignore_hosts = BrainzLab.configuration.http_ignore_hosts || []
74
+ ignore_hosts.any? { |h| host.include?(h) }
75
+ end
76
+ end
77
+
78
+ # Hydra instrumentation for parallel requests
79
+ module HydraInstrumentation
80
+ def self.install!
81
+ return unless defined?(::Typhoeus::Hydra)
82
+
83
+ ::Typhoeus::Hydra.class_eval do
84
+ alias_method :original_run, :run
85
+
86
+ def run
87
+ started_at = Time.now
88
+ request_count = queued_requests.size
89
+
90
+ result = original_run
91
+
92
+ duration_ms = ((Time.now - started_at) * 1000).round(2)
93
+
94
+ if BrainzLab.configuration.flux_effectively_enabled?
95
+ BrainzLab::Flux.distribution('http.typhoeus.hydra.duration_ms', duration_ms)
96
+ BrainzLab::Flux.distribution('http.typhoeus.hydra.request_count', request_count)
97
+ end
98
+
99
+ result
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,360 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ class << self
6
+ def install!
7
+ config = BrainzLab.configuration
8
+
9
+ # Skip all instrumentation if SDK is disabled
10
+ unless config.enabled?
11
+ BrainzLab.debug_log('[Instrumentation] SDK disabled via BRAINZLAB_SDK_ENABLED=false, skipping all instrumentation')
12
+ return
13
+ end
14
+
15
+ # Skip Rails-specific instrumentation if brainzlab-rails gem is handling it
16
+ # This prevents double-tracking of events
17
+ if config.rails_instrumentation_handled_externally
18
+ BrainzLab.debug_log('[Instrumentation] Rails instrumentation handled by brainzlab-rails gem, skipping SDK instrumentation')
19
+ install_non_rails_instrumentation!(config)
20
+ return
21
+ end
22
+
23
+ # HTTP client instrumentation
24
+ if config.instrument_http
25
+ install_net_http!
26
+ install_faraday!
27
+ install_httparty!
28
+ end
29
+
30
+ # Database instrumentation (breadcrumbs for Reflex)
31
+ install_active_record! if config.instrument_active_record
32
+
33
+ # Redis instrumentation
34
+ install_redis! if config.instrument_redis
35
+
36
+ # Background job instrumentation
37
+ install_sidekiq! if config.instrument_sidekiq
38
+
39
+ # GraphQL instrumentation
40
+ install_graphql! if config.instrument_graphql
41
+
42
+ # MongoDB instrumentation
43
+ install_mongodb! if config.instrument_mongodb
44
+
45
+ # Elasticsearch instrumentation
46
+ install_elasticsearch! if config.instrument_elasticsearch
47
+
48
+ # Rails MVC instrumentation
49
+ install_action_controller! if config.instrument_action_controller
50
+ install_action_view! if config.instrument_action_view
51
+ install_action_mailer! if config.instrument_action_mailer
52
+
53
+ # ActiveJob instrumentation (covers all job backends)
54
+ install_active_job! if config.instrument_active_job
55
+
56
+ # ActiveSupport::Cache instrumentation
57
+ install_active_support_cache! if config.instrument_active_support_cache
58
+
59
+ # Delayed::Job instrumentation
60
+ install_delayed_job! if config.instrument_delayed_job
61
+
62
+ # Grape API instrumentation
63
+ install_grape! if config.instrument_grape
64
+
65
+ # Modern job queue instrumentation
66
+ install_solid_queue! if config.instrument_solid_queue
67
+ install_good_job! if config.instrument_good_job
68
+ install_resque! if config.instrument_resque
69
+
70
+ # Additional HTTP clients
71
+ install_excon! if config.instrument_excon
72
+ install_typhoeus! if config.instrument_typhoeus
73
+
74
+ # Caching
75
+ install_dalli! if config.instrument_dalli
76
+
77
+ # Cloud & Payment
78
+ install_aws! if config.instrument_aws
79
+ install_stripe! if config.instrument_stripe
80
+
81
+ # File storage
82
+ install_active_storage! if config.instrument_active_storage
83
+
84
+ # WebSocket
85
+ install_action_cable! if config.instrument_action_cable
86
+
87
+ # Rails framework events
88
+ install_action_dispatch! if config.instrument_action_dispatch
89
+ install_rails_deprecation! if config.instrument_rails_deprecation
90
+ install_action_mailbox! if config.instrument_action_mailbox
91
+ install_railties! if config.instrument_railties
92
+ end
93
+
94
+ def install_net_http!
95
+ require_relative 'instrumentation/net_http'
96
+ NetHttp.install!
97
+ end
98
+
99
+ def install_faraday!
100
+ return unless defined?(::Faraday)
101
+
102
+ require_relative 'instrumentation/faraday'
103
+ FaradayMiddleware.install!
104
+ end
105
+
106
+ def install_httparty!
107
+ return unless defined?(::HTTParty)
108
+
109
+ require_relative 'instrumentation/httparty'
110
+ HTTPartyInstrumentation.install!
111
+ end
112
+
113
+ def install_active_record!
114
+ require_relative 'instrumentation/active_record'
115
+ ActiveRecord.install!
116
+ end
117
+
118
+ def install_redis!
119
+ return unless defined?(::Redis)
120
+
121
+ require_relative 'instrumentation/redis'
122
+ RedisInstrumentation.install!
123
+ end
124
+
125
+ def install_sidekiq!
126
+ return unless defined?(::Sidekiq)
127
+
128
+ require_relative 'instrumentation/sidekiq'
129
+ SidekiqInstrumentation.install!
130
+ end
131
+
132
+ def install_graphql!
133
+ return unless defined?(::GraphQL)
134
+
135
+ require_relative 'instrumentation/graphql'
136
+ GraphQLInstrumentation.install!
137
+ end
138
+
139
+ def install_mongodb!
140
+ return unless defined?(::Mongo) || defined?(::Mongoid)
141
+
142
+ require_relative 'instrumentation/mongodb'
143
+ MongoDBInstrumentation.install!
144
+ end
145
+
146
+ def install_elasticsearch!
147
+ return unless defined?(::Elasticsearch) || defined?(::OpenSearch)
148
+
149
+ require_relative 'instrumentation/elasticsearch'
150
+ ElasticsearchInstrumentation.install!
151
+ end
152
+
153
+ def install_action_controller!
154
+ return unless defined?(::ActionController)
155
+
156
+ require_relative 'instrumentation/action_controller'
157
+ ActionController.install!
158
+ end
159
+
160
+ def install_action_view!
161
+ return unless defined?(::ActionView)
162
+
163
+ require_relative 'instrumentation/action_view'
164
+ ActionView.install!
165
+ end
166
+
167
+ def install_action_mailer!
168
+ return unless defined?(::ActionMailer)
169
+
170
+ require_relative 'instrumentation/action_mailer'
171
+ ActionMailerInstrumentation.install!
172
+ end
173
+
174
+ def install_active_job!
175
+ return unless defined?(::ActiveJob)
176
+
177
+ require_relative 'instrumentation/active_job'
178
+ ActiveJob.install!
179
+ end
180
+
181
+ def install_active_support_cache!
182
+ return unless defined?(::ActiveSupport::Cache)
183
+
184
+ require_relative 'instrumentation/active_support_cache'
185
+ ActiveSupportCache.install!
186
+ end
187
+
188
+ def install_delayed_job!
189
+ return unless defined?(::Delayed::Job) || defined?(::Delayed::Backend)
190
+
191
+ require_relative 'instrumentation/delayed_job'
192
+ DelayedJobInstrumentation.install!
193
+ end
194
+
195
+ def install_grape!
196
+ return unless defined?(::Grape::API)
197
+
198
+ require_relative 'instrumentation/grape'
199
+ GrapeInstrumentation.install!
200
+ end
201
+
202
+ def install_solid_queue!
203
+ return unless defined?(::SolidQueue)
204
+
205
+ require_relative 'instrumentation/solid_queue'
206
+ SolidQueueInstrumentation.install!
207
+ end
208
+
209
+ def install_good_job!
210
+ return unless defined?(::GoodJob)
211
+
212
+ require_relative 'instrumentation/good_job'
213
+ GoodJobInstrumentation.install!
214
+ end
215
+
216
+ def install_resque!
217
+ return unless defined?(::Resque)
218
+
219
+ require_relative 'instrumentation/resque'
220
+ ResqueInstrumentation.install!
221
+ end
222
+
223
+ def install_excon!
224
+ return unless defined?(::Excon)
225
+
226
+ require_relative 'instrumentation/excon'
227
+ ExconInstrumentation.install!
228
+ end
229
+
230
+ def install_typhoeus!
231
+ return unless defined?(::Typhoeus)
232
+
233
+ require_relative 'instrumentation/typhoeus'
234
+ TyphoeusInstrumentation.install!
235
+ end
236
+
237
+ def install_dalli!
238
+ return unless defined?(::Dalli::Client)
239
+
240
+ require_relative 'instrumentation/dalli'
241
+ DalliInstrumentation.install!
242
+ end
243
+
244
+ def install_aws!
245
+ return unless defined?(::Aws)
246
+
247
+ require_relative 'instrumentation/aws'
248
+ AWSInstrumentation.install!
249
+ end
250
+
251
+ def install_stripe!
252
+ return unless defined?(::Stripe)
253
+
254
+ require_relative 'instrumentation/stripe'
255
+ StripeInstrumentation.install!
256
+ end
257
+
258
+ def install_active_storage!
259
+ return unless defined?(::ActiveStorage)
260
+
261
+ require_relative 'instrumentation/active_storage'
262
+ ActiveStorage.install!
263
+ end
264
+
265
+ def install_action_cable!
266
+ return unless defined?(::ActionCable)
267
+
268
+ require_relative 'instrumentation/action_cable'
269
+ ActionCable.install!
270
+ end
271
+
272
+ def install_action_dispatch!
273
+ return unless defined?(::ActionDispatch)
274
+
275
+ require_relative 'instrumentation/action_dispatch'
276
+ ActionDispatch.install!
277
+ end
278
+
279
+ def install_rails_deprecation!
280
+ return unless defined?(::Rails)
281
+
282
+ require_relative 'instrumentation/rails_deprecation'
283
+ RailsDeprecation.install!
284
+ end
285
+
286
+ def install_action_mailbox!
287
+ return unless defined?(::ActionMailbox)
288
+
289
+ require_relative 'instrumentation/action_mailbox'
290
+ ActionMailbox.install!
291
+ end
292
+
293
+ def install_railties!
294
+ return unless defined?(::Rails)
295
+
296
+ require_relative 'instrumentation/railties'
297
+ Railties.install!
298
+ end
299
+
300
+ # Manual installation methods for lazy-loaded libraries
301
+ def install_http!
302
+ install_net_http!
303
+ install_faraday!
304
+ install_httparty!
305
+ end
306
+
307
+ private
308
+
309
+ # Install only non-Rails-specific instrumentation
310
+ # Used when brainzlab-rails gem handles Rails events via ActiveSupport::Notifications
311
+ def install_non_rails_instrumentation!(config)
312
+ # HTTP client instrumentation (not Rails-specific)
313
+ if config.instrument_http
314
+ install_net_http!
315
+ install_faraday!
316
+ install_httparty!
317
+ end
318
+
319
+ # Redis instrumentation (not Rails-specific)
320
+ install_redis! if config.instrument_redis
321
+
322
+ # Sidekiq instrumentation (not Rails-specific, has its own hooks)
323
+ install_sidekiq! if config.instrument_sidekiq
324
+
325
+ # GraphQL instrumentation (not Rails-specific)
326
+ install_graphql! if config.instrument_graphql
327
+
328
+ # MongoDB instrumentation (not Rails-specific)
329
+ install_mongodb! if config.instrument_mongodb
330
+
331
+ # Elasticsearch instrumentation (not Rails-specific)
332
+ install_elasticsearch! if config.instrument_elasticsearch
333
+
334
+ # Delayed::Job instrumentation (not Rails-specific, has its own hooks)
335
+ install_delayed_job! if config.instrument_delayed_job
336
+
337
+ # Grape API instrumentation (not Rails-specific)
338
+ install_grape! if config.instrument_grape
339
+
340
+ # Modern job queue instrumentation (have their own hooks)
341
+ install_solid_queue! if config.instrument_solid_queue
342
+ install_good_job! if config.instrument_good_job
343
+ install_resque! if config.instrument_resque
344
+
345
+ # Additional HTTP clients (not Rails-specific)
346
+ install_excon! if config.instrument_excon
347
+ install_typhoeus! if config.instrument_typhoeus
348
+
349
+ # Dalli/Memcached (not Rails-specific, has its own hooks)
350
+ install_dalli! if config.instrument_dalli
351
+
352
+ # Cloud & Payment (not Rails-specific)
353
+ install_aws! if config.instrument_aws
354
+ install_stripe! if config.instrument_stripe
355
+
356
+ BrainzLab.debug_log('[Instrumentation] Non-Rails instrumentation installed (HTTP, Redis, Sidekiq, GraphQL, etc.)')
357
+ end
358
+ end
359
+ end
360
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module BrainzLab
8
+ module Nerve
9
+ class Client
10
+ def initialize(config)
11
+ @config = config
12
+ @base_url = config.nerve_url || 'https://nerve.brainzlab.ai'
13
+ end
14
+
15
+ # Report a job execution
16
+ def report_job(job_class:, job_id:, queue:, status:, started_at:, ended_at:, **attributes)
17
+ response = request(
18
+ :post,
19
+ '/api/v1/jobs',
20
+ body: {
21
+ job_class: job_class,
22
+ job_id: job_id,
23
+ queue: queue,
24
+ status: status,
25
+ started_at: started_at.iso8601(3),
26
+ ended_at: ended_at.iso8601(3),
27
+ duration_ms: ((ended_at - started_at) * 1000).round(2),
28
+ **attributes
29
+ }
30
+ )
31
+
32
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
33
+ rescue StandardError => e
34
+ log_error('report_job', e)
35
+ false
36
+ end
37
+
38
+ # Report a job failure
39
+ def report_failure(job_class:, job_id:, queue:, error_class:, error_message:, backtrace: nil, **attributes)
40
+ response = request(
41
+ :post,
42
+ '/api/v1/jobs/failures',
43
+ body: {
44
+ job_class: job_class,
45
+ job_id: job_id,
46
+ queue: queue,
47
+ error_class: error_class,
48
+ error_message: error_message,
49
+ backtrace: backtrace&.first(20),
50
+ failed_at: Time.now.utc.iso8601(3),
51
+ **attributes
52
+ }
53
+ )
54
+
55
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
56
+ rescue StandardError => e
57
+ log_error('report_failure', e)
58
+ false
59
+ end
60
+
61
+ # Get job statistics
62
+ def stats(queue: nil, job_class: nil, period: '1h')
63
+ params = { period: period }
64
+ params[:queue] = queue if queue
65
+ params[:job_class] = job_class if job_class
66
+
67
+ response = request(:get, '/api/v1/stats', params: params)
68
+
69
+ return nil unless response.is_a?(Net::HTTPSuccess)
70
+
71
+ JSON.parse(response.body, symbolize_names: true)
72
+ rescue StandardError => e
73
+ log_error('stats', e)
74
+ nil
75
+ end
76
+
77
+ # List recent jobs
78
+ def list_jobs(queue: nil, status: nil, limit: 100)
79
+ params = { limit: limit }
80
+ params[:queue] = queue if queue
81
+ params[:status] = status if status
82
+
83
+ response = request(:get, '/api/v1/jobs', params: params)
84
+
85
+ return [] unless response.is_a?(Net::HTTPSuccess)
86
+
87
+ data = JSON.parse(response.body, symbolize_names: true)
88
+ data[:jobs] || []
89
+ rescue StandardError => e
90
+ log_error('list_jobs', e)
91
+ []
92
+ end
93
+
94
+ # List queues
95
+ def list_queues
96
+ response = request(:get, '/api/v1/queues')
97
+
98
+ return [] unless response.is_a?(Net::HTTPSuccess)
99
+
100
+ data = JSON.parse(response.body, symbolize_names: true)
101
+ data[:queues] || []
102
+ rescue StandardError => e
103
+ log_error('list_queues', e)
104
+ []
105
+ end
106
+
107
+ # Get queue details
108
+ def get_queue(name)
109
+ response = request(:get, "/api/v1/queues/#{CGI.escape(name)}")
110
+
111
+ return nil unless response.is_a?(Net::HTTPSuccess)
112
+
113
+ JSON.parse(response.body, symbolize_names: true)
114
+ rescue StandardError => e
115
+ log_error('get_queue', e)
116
+ nil
117
+ end
118
+
119
+ # Retry a failed job
120
+ def retry_job(job_id)
121
+ response = request(:post, "/api/v1/jobs/#{job_id}/retry")
122
+ response.is_a?(Net::HTTPSuccess)
123
+ rescue StandardError => e
124
+ log_error('retry_job', e)
125
+ false
126
+ end
127
+
128
+ # Delete a job
129
+ def delete_job(job_id)
130
+ response = request(:delete, "/api/v1/jobs/#{job_id}")
131
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPNoContent)
132
+ rescue StandardError => e
133
+ log_error('delete_job', e)
134
+ false
135
+ end
136
+
137
+ # Report queue metrics
138
+ def report_metrics(queue:, size:, latency_ms: nil, workers: nil)
139
+ response = request(
140
+ :post,
141
+ '/api/v1/metrics',
142
+ body: {
143
+ queue: queue,
144
+ size: size,
145
+ latency_ms: latency_ms,
146
+ workers: workers,
147
+ timestamp: Time.now.utc.iso8601(3)
148
+ }
149
+ )
150
+
151
+ response.is_a?(Net::HTTPSuccess)
152
+ rescue StandardError => e
153
+ log_error('report_metrics', e)
154
+ false
155
+ end
156
+
157
+ def provision(project_id:, app_name:)
158
+ response = request(
159
+ :post,
160
+ '/api/v1/projects/provision',
161
+ body: { project_id: project_id, app_name: app_name },
162
+ use_service_key: true
163
+ )
164
+
165
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
166
+ rescue StandardError => e
167
+ log_error('provision', e)
168
+ false
169
+ end
170
+
171
+ private
172
+
173
+ def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false)
174
+ uri = URI.parse("#{@base_url}#{path}")
175
+
176
+ uri.query = URI.encode_www_form(params) if params
177
+
178
+ http = Net::HTTP.new(uri.host, uri.port)
179
+ http.use_ssl = uri.scheme == 'https'
180
+ http.open_timeout = 5
181
+ http.read_timeout = 10
182
+
183
+ request = case method
184
+ when :get
185
+ Net::HTTP::Get.new(uri)
186
+ when :post
187
+ Net::HTTP::Post.new(uri)
188
+ when :put
189
+ Net::HTTP::Put.new(uri)
190
+ when :delete
191
+ Net::HTTP::Delete.new(uri)
192
+ end
193
+
194
+ request['Content-Type'] = 'application/json'
195
+ request['Accept'] = 'application/json'
196
+
197
+ if use_service_key
198
+ request['X-Service-Key'] = @config.nerve_master_key || @config.secret_key
199
+ else
200
+ auth_key = @config.nerve_api_key || @config.secret_key
201
+ request['Authorization'] = "Bearer #{auth_key}" if auth_key
202
+ end
203
+
204
+ headers.each { |k, v| request[k] = v }
205
+ request.body = body.to_json if body
206
+
207
+ http.request(request)
208
+ end
209
+
210
+ def log_error(operation, error)
211
+ structured_error = ErrorHandler.wrap(error, service: 'Nerve', operation: operation)
212
+ BrainzLab.debug_log("[Nerve::Client] #{operation} failed: #{structured_error.message}")
213
+
214
+ # Call on_error callback if configured
215
+ if @config.on_error
216
+ @config.on_error.call(structured_error, { service: 'Nerve', operation: operation })
217
+ end
218
+ end
219
+
220
+ def handle_response_error(response, operation)
221
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent)
222
+
223
+ structured_error = ErrorHandler.from_response(response, service: 'Nerve', operation: operation)
224
+ BrainzLab.debug_log("[Nerve::Client] #{operation} failed: #{structured_error.message}")
225
+
226
+ # Call on_error callback if configured
227
+ if @config.on_error
228
+ @config.on_error.call(structured_error, { service: 'Nerve', operation: operation })
229
+ end
230
+
231
+ structured_error
232
+ end
233
+ end
234
+ end
235
+ end