brainzlab 0.1.10 → 0.1.12
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/README.md +220 -4
- data/lib/brainzlab/beacon/client.rb +21 -1
- data/lib/brainzlab/configuration.rb +51 -2
- data/lib/brainzlab/cortex/client.rb +21 -1
- data/lib/brainzlab/debug.rb +305 -0
- data/lib/brainzlab/dendrite/client.rb +21 -1
- 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 +326 -103
- data/lib/brainzlab/devtools/assets/devtools.js +79 -5
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +11 -0
- data/lib/brainzlab/errors.rb +490 -0
- data/lib/brainzlab/nerve/client.rb +21 -1
- data/lib/brainzlab/pulse/client.rb +66 -5
- data/lib/brainzlab/pulse.rb +17 -4
- data/lib/brainzlab/recall/client.rb +74 -6
- data/lib/brainzlab/recall.rb +19 -2
- data/lib/brainzlab/reflex/client.rb +66 -5
- data/lib/brainzlab/reflex.rb +40 -8
- data/lib/brainzlab/sentinel/client.rb +21 -1
- data/lib/brainzlab/synapse/client.rb +21 -1
- 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 +32 -3
- data/lib/brainzlab/vault/client.rb +21 -1
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +53 -6
- data/lib/brainzlab.rb +42 -0
- metadata +24 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'testing/event_store'
|
|
4
|
+
require_relative 'testing/helpers'
|
|
5
|
+
require_relative 'testing/matchers'
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
# Testing utilities for BrainzLab SDK
|
|
9
|
+
#
|
|
10
|
+
# Provides helpers for stubbing SDK calls, capturing events,
|
|
11
|
+
# and custom matchers for RSpec/Minitest.
|
|
12
|
+
#
|
|
13
|
+
# @example Usage in RSpec
|
|
14
|
+
# # spec/rails_helper.rb or spec/spec_helper.rb
|
|
15
|
+
# require 'brainzlab/testing'
|
|
16
|
+
#
|
|
17
|
+
# RSpec.configure do |config|
|
|
18
|
+
# config.include BrainzLab::Testing::Helpers
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @example Usage in Minitest
|
|
22
|
+
# # test/test_helper.rb
|
|
23
|
+
# require 'brainzlab/testing'
|
|
24
|
+
#
|
|
25
|
+
# class ActiveSupport::TestCase
|
|
26
|
+
# include BrainzLab::Testing::Helpers
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
module Testing
|
|
30
|
+
class << self
|
|
31
|
+
# Global event store for capturing events during tests
|
|
32
|
+
def event_store
|
|
33
|
+
@event_store ||= EventStore.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Reset the event store (called between tests)
|
|
37
|
+
def reset!
|
|
38
|
+
@event_store = EventStore.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Check if testing mode is active
|
|
42
|
+
def enabled?
|
|
43
|
+
@enabled == true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Enable testing mode (stubs all SDK calls)
|
|
47
|
+
def enable!
|
|
48
|
+
return if @enabled
|
|
49
|
+
|
|
50
|
+
@enabled = true
|
|
51
|
+
install_stubs!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Disable testing mode
|
|
55
|
+
def disable!
|
|
56
|
+
return unless @enabled
|
|
57
|
+
|
|
58
|
+
@enabled = false
|
|
59
|
+
remove_stubs!
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def install_stubs!
|
|
65
|
+
# Stub Flux (events/metrics)
|
|
66
|
+
stub_flux!
|
|
67
|
+
|
|
68
|
+
# Stub Recall (logging)
|
|
69
|
+
stub_recall!
|
|
70
|
+
|
|
71
|
+
# Stub Reflex (error tracking)
|
|
72
|
+
stub_reflex!
|
|
73
|
+
|
|
74
|
+
# Stub Pulse (APM/tracing)
|
|
75
|
+
stub_pulse!
|
|
76
|
+
|
|
77
|
+
# Stub Signal (alerts/notifications)
|
|
78
|
+
stub_signal!
|
|
79
|
+
|
|
80
|
+
# Stub other modules
|
|
81
|
+
stub_beacon!
|
|
82
|
+
stub_nerve!
|
|
83
|
+
stub_dendrite!
|
|
84
|
+
stub_sentinel!
|
|
85
|
+
stub_synapse!
|
|
86
|
+
stub_cortex!
|
|
87
|
+
stub_vault!
|
|
88
|
+
stub_vision!
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def remove_stubs!
|
|
92
|
+
# Reset all modules to restore original behavior
|
|
93
|
+
BrainzLab.reset_configuration!
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def stub_flux!
|
|
97
|
+
# Store original methods
|
|
98
|
+
@original_flux_track = BrainzLab::Flux.method(:track)
|
|
99
|
+
|
|
100
|
+
# Replace with capturing versions
|
|
101
|
+
BrainzLab::Flux.define_singleton_method(:track) do |name, properties = {}|
|
|
102
|
+
BrainzLab::Testing.event_store.record_event(name, properties)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
BrainzLab::Flux.define_singleton_method(:track_for_user) do |user, name, properties = {}|
|
|
106
|
+
user_id = user.respond_to?(:id) ? user.id.to_s : user.to_s
|
|
107
|
+
BrainzLab::Testing.event_store.record_event(name, properties.merge(user_id: user_id))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Stub metrics
|
|
111
|
+
%i[gauge increment decrement distribution histogram timing set].each do |method|
|
|
112
|
+
BrainzLab::Flux.define_singleton_method(method) do |name, value = nil, **opts|
|
|
113
|
+
BrainzLab::Testing.event_store.record_metric(method, name, value, opts)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
BrainzLab::Flux.define_singleton_method(:measure) do |name, **opts, &block|
|
|
118
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
119
|
+
result = block.call
|
|
120
|
+
duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
|
121
|
+
BrainzLab::Testing.event_store.record_metric(:distribution, name, duration_ms, opts.merge(unit: 'ms'))
|
|
122
|
+
result
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
BrainzLab::Flux.define_singleton_method(:flush!) { true }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def stub_recall!
|
|
129
|
+
%i[debug info warn error fatal].each do |level|
|
|
130
|
+
BrainzLab::Recall.define_singleton_method(level) do |message, **data|
|
|
131
|
+
BrainzLab::Testing.event_store.record_log(level, message, data)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
BrainzLab::Recall.define_singleton_method(:log) do |level, message, **data|
|
|
136
|
+
BrainzLab::Testing.event_store.record_log(level, message, data)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
BrainzLab::Recall.define_singleton_method(:time) do |label, **data, &block|
|
|
140
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
141
|
+
result = block.call
|
|
142
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round(1)
|
|
143
|
+
BrainzLab::Testing.event_store.record_log(:info, "#{label} (#{duration_ms}ms)", data.merge(duration_ms: duration_ms))
|
|
144
|
+
result
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
BrainzLab::Recall.define_singleton_method(:flush) { true }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def stub_reflex!
|
|
151
|
+
BrainzLab::Reflex.define_singleton_method(:capture) do |exception, **context|
|
|
152
|
+
BrainzLab::Testing.event_store.record_error(exception, context)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
BrainzLab::Reflex.define_singleton_method(:capture_message) do |message, level: :error, **context|
|
|
156
|
+
BrainzLab::Testing.event_store.record_error_message(message, level, context)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def stub_pulse!
|
|
161
|
+
# Stub tracing methods
|
|
162
|
+
BrainzLab::Pulse.define_singleton_method(:start_trace) do |name, **opts|
|
|
163
|
+
BrainzLab::Testing.event_store.record_trace(name, opts.merge(action: :start))
|
|
164
|
+
{ trace_id: 'test-trace-id-12345', name: name }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
BrainzLab::Pulse.define_singleton_method(:finish_trace) do |**opts|
|
|
168
|
+
BrainzLab::Testing.event_store.record_trace('finish', opts.merge(action: :finish))
|
|
169
|
+
true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
BrainzLab::Pulse.define_singleton_method(:span) do |name, **opts, &block|
|
|
173
|
+
BrainzLab::Testing.event_store.record_trace(name, opts.merge(type: :span))
|
|
174
|
+
block&.call
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
BrainzLab::Pulse.define_singleton_method(:record_trace) do |name, **opts|
|
|
178
|
+
BrainzLab::Testing.event_store.record_trace(name, opts)
|
|
179
|
+
true
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
BrainzLab::Pulse.define_singleton_method(:record_span) do |**opts|
|
|
183
|
+
BrainzLab::Testing.event_store.record_trace(opts[:name], opts)
|
|
184
|
+
true
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
BrainzLab::Pulse.define_singleton_method(:record_metric) do |name, **opts|
|
|
188
|
+
BrainzLab::Testing.event_store.record_metric(opts[:kind] || :gauge, name, opts[:value], tags: opts[:tags] || {})
|
|
189
|
+
true
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Stub metric convenience methods
|
|
193
|
+
%i[gauge counter histogram].each do |method|
|
|
194
|
+
BrainzLab::Pulse.define_singleton_method(method) do |name, value, tags: {}|
|
|
195
|
+
BrainzLab::Testing.event_store.record_metric(method, name, value, tags: tags)
|
|
196
|
+
true
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Stub distributed tracing methods
|
|
201
|
+
BrainzLab::Pulse.define_singleton_method(:inject) do |headers, **opts|
|
|
202
|
+
headers['traceparent'] = '00-test-trace-id-12345-test-span-id-67890-01'
|
|
203
|
+
headers
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
BrainzLab::Pulse.define_singleton_method(:extract) do |headers|
|
|
207
|
+
BrainzLab::Pulse::Propagation::Context.new(
|
|
208
|
+
trace_id: 'test-trace-id-12345',
|
|
209
|
+
span_id: 'test-span-id-67890'
|
|
210
|
+
) if headers['traceparent']
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
BrainzLab::Pulse.define_singleton_method(:extract!) do |headers|
|
|
214
|
+
BrainzLab::Pulse.extract(headers)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
BrainzLab::Pulse.define_singleton_method(:propagation_context) do
|
|
218
|
+
BrainzLab::Pulse::Propagation::Context.new(
|
|
219
|
+
trace_id: 'test-trace-id-12345',
|
|
220
|
+
span_id: 'test-span-id-67890'
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
BrainzLab::Pulse.define_singleton_method(:child_context) do
|
|
225
|
+
BrainzLab::Pulse::Propagation::Context.new(
|
|
226
|
+
trace_id: 'test-trace-id-12345',
|
|
227
|
+
span_id: SecureRandom.hex(8)
|
|
228
|
+
)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def stub_signal!
|
|
233
|
+
BrainzLab::Signal.define_singleton_method(:alert) do |name, message, severity: :warning, channels: nil, data: {}|
|
|
234
|
+
BrainzLab::Testing.event_store.record_alert(name, message, severity, channels, data)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
BrainzLab::Signal.define_singleton_method(:notify) do |channel, message, title: nil, data: {}|
|
|
238
|
+
BrainzLab::Testing.event_store.record_notification(channel, message, title, data)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
BrainzLab::Signal.define_singleton_method(:trigger) do |rule_name, context = {}|
|
|
242
|
+
BrainzLab::Testing.event_store.record_trigger(rule_name, context)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
BrainzLab::Signal.define_singleton_method(:test!) { true }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def stub_beacon!
|
|
249
|
+
return unless defined?(BrainzLab::Beacon)
|
|
250
|
+
|
|
251
|
+
BrainzLab::Beacon.define_singleton_method(:create_http_monitor) { |*| { id: 'test-monitor-1', status: 'created' } }
|
|
252
|
+
BrainzLab::Beacon.define_singleton_method(:create_ssl_monitor) { |*| { id: 'test-monitor-2', status: 'created' } }
|
|
253
|
+
BrainzLab::Beacon.define_singleton_method(:create_tcp_monitor) { |*| { id: 'test-monitor-3', status: 'created' } }
|
|
254
|
+
BrainzLab::Beacon.define_singleton_method(:create_dns_monitor) { |*| { id: 'test-monitor-4', status: 'created' } }
|
|
255
|
+
BrainzLab::Beacon.define_singleton_method(:list) { [] }
|
|
256
|
+
BrainzLab::Beacon.define_singleton_method(:get) { |_id| nil }
|
|
257
|
+
BrainzLab::Beacon.define_singleton_method(:update) { |*| true }
|
|
258
|
+
BrainzLab::Beacon.define_singleton_method(:delete) { |_id| true }
|
|
259
|
+
BrainzLab::Beacon.define_singleton_method(:pause) { |_id| true }
|
|
260
|
+
BrainzLab::Beacon.define_singleton_method(:resume) { |_id| true }
|
|
261
|
+
BrainzLab::Beacon.define_singleton_method(:history) { |*| [] }
|
|
262
|
+
BrainzLab::Beacon.define_singleton_method(:status) { { status: 'up', monitors: 0 } }
|
|
263
|
+
BrainzLab::Beacon.define_singleton_method(:all_up?) { true }
|
|
264
|
+
BrainzLab::Beacon.define_singleton_method(:incidents) { [] }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def stub_nerve!
|
|
268
|
+
return unless defined?(BrainzLab::Nerve)
|
|
269
|
+
|
|
270
|
+
BrainzLab::Nerve.define_singleton_method(:flag) { |*| false }
|
|
271
|
+
BrainzLab::Nerve.define_singleton_method(:enabled?) { |*| false }
|
|
272
|
+
BrainzLab::Nerve.define_singleton_method(:disabled?) { |*| true }
|
|
273
|
+
BrainzLab::Nerve.define_singleton_method(:variation) { |*| nil }
|
|
274
|
+
BrainzLab::Nerve.define_singleton_method(:all_flags) { {} }
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def stub_dendrite!
|
|
278
|
+
return unless defined?(BrainzLab::Dendrite)
|
|
279
|
+
|
|
280
|
+
BrainzLab::Dendrite.define_singleton_method(:get) { |*| nil }
|
|
281
|
+
BrainzLab::Dendrite.define_singleton_method(:set) { |*| true }
|
|
282
|
+
BrainzLab::Dendrite.define_singleton_method(:delete) { |*| true }
|
|
283
|
+
BrainzLab::Dendrite.define_singleton_method(:all) { {} }
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def stub_sentinel!
|
|
287
|
+
return unless defined?(BrainzLab::Sentinel)
|
|
288
|
+
|
|
289
|
+
BrainzLab::Sentinel.define_singleton_method(:check) { |*| { allowed: true } }
|
|
290
|
+
BrainzLab::Sentinel.define_singleton_method(:allowed?) { |*| true }
|
|
291
|
+
BrainzLab::Sentinel.define_singleton_method(:denied?) { |*| false }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def stub_synapse!
|
|
295
|
+
return unless defined?(BrainzLab::Synapse)
|
|
296
|
+
|
|
297
|
+
BrainzLab::Synapse.define_singleton_method(:publish) { |*| true }
|
|
298
|
+
BrainzLab::Synapse.define_singleton_method(:subscribe) { |*| true }
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def stub_cortex!
|
|
302
|
+
return unless defined?(BrainzLab::Cortex)
|
|
303
|
+
|
|
304
|
+
BrainzLab::Cortex.define_singleton_method(:read) { |*| nil }
|
|
305
|
+
BrainzLab::Cortex.define_singleton_method(:write) { |*| true }
|
|
306
|
+
BrainzLab::Cortex.define_singleton_method(:delete) { |*| true }
|
|
307
|
+
BrainzLab::Cortex.define_singleton_method(:fetch) { |key, **opts, &block| block&.call }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def stub_vault!
|
|
311
|
+
return unless defined?(BrainzLab::Vault)
|
|
312
|
+
|
|
313
|
+
BrainzLab::Vault.define_singleton_method(:get) { |*| nil }
|
|
314
|
+
BrainzLab::Vault.define_singleton_method(:set) { |*| true }
|
|
315
|
+
BrainzLab::Vault.define_singleton_method(:delete) { |*| true }
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def stub_vision!
|
|
319
|
+
return unless defined?(BrainzLab::Vision)
|
|
320
|
+
|
|
321
|
+
BrainzLab::Vision.define_singleton_method(:track_pageview) { |*| true }
|
|
322
|
+
BrainzLab::Vision.define_singleton_method(:track_event) { |*| true }
|
|
323
|
+
BrainzLab::Vision.define_singleton_method(:identify) { |*| true }
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
@@ -54,7 +54,7 @@ module BrainzLab
|
|
|
54
54
|
case @state
|
|
55
55
|
when :open
|
|
56
56
|
track_rejected
|
|
57
|
-
raise CircuitOpenError,
|
|
57
|
+
raise CircuitOpenError.new(@name, failure_count: @failure_count, last_failure_at: @last_failure_at) unless fallback
|
|
58
58
|
|
|
59
59
|
fallback.respond_to?(:call) ? fallback.call : fallback
|
|
60
60
|
|
|
@@ -65,7 +65,15 @@ module BrainzLab
|
|
|
65
65
|
|
|
66
66
|
# Force the circuit to a specific state
|
|
67
67
|
def force_state!(new_state)
|
|
68
|
-
|
|
68
|
+
unless STATES.include?(new_state)
|
|
69
|
+
raise BrainzLab::ValidationError.new(
|
|
70
|
+
"Invalid circuit breaker state: #{new_state}",
|
|
71
|
+
hint: "Valid states are: #{STATES.join(', ')}",
|
|
72
|
+
code: 'invalid_circuit_state',
|
|
73
|
+
field: 'state',
|
|
74
|
+
context: { provided: new_state, valid_values: STATES }
|
|
75
|
+
)
|
|
76
|
+
end
|
|
69
77
|
|
|
70
78
|
@mutex.synchronize do
|
|
71
79
|
@state = new_state
|
|
@@ -255,7 +263,28 @@ module BrainzLab
|
|
|
255
263
|
end
|
|
256
264
|
|
|
257
265
|
# Error raised when circuit is open
|
|
258
|
-
|
|
266
|
+
# Inherits from BrainzLab::ServiceUnavailableError for structured error handling
|
|
267
|
+
class CircuitOpenError < BrainzLab::ServiceUnavailableError
|
|
268
|
+
attr_reader :circuit_name, :failure_count, :last_failure_at
|
|
269
|
+
|
|
270
|
+
def initialize(circuit_name, failure_count: nil, last_failure_at: nil)
|
|
271
|
+
@circuit_name = circuit_name
|
|
272
|
+
@failure_count = failure_count
|
|
273
|
+
@last_failure_at = last_failure_at
|
|
274
|
+
|
|
275
|
+
super(
|
|
276
|
+
"Circuit '#{circuit_name}' is open",
|
|
277
|
+
hint: 'The circuit breaker has tripped due to repeated failures. The service will be retried automatically after the recovery timeout.',
|
|
278
|
+
code: 'circuit_open',
|
|
279
|
+
service_name: circuit_name,
|
|
280
|
+
context: {
|
|
281
|
+
circuit_name: circuit_name,
|
|
282
|
+
failure_count: failure_count,
|
|
283
|
+
last_failure_at: last_failure_at&.iso8601
|
|
284
|
+
}.compact
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
259
288
|
end
|
|
260
289
|
end
|
|
261
290
|
end
|
|
@@ -189,7 +189,27 @@ module BrainzLab
|
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
def log_error(operation, error)
|
|
192
|
-
|
|
192
|
+
structured_error = ErrorHandler.wrap(error, service: 'Vault', operation: operation)
|
|
193
|
+
BrainzLab.debug_log("[Vault::Client] #{operation} failed: #{structured_error.message}")
|
|
194
|
+
|
|
195
|
+
# Call on_error callback if configured
|
|
196
|
+
if @config.on_error
|
|
197
|
+
@config.on_error.call(structured_error, { service: 'Vault', operation: operation })
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def handle_response_error(response, operation)
|
|
202
|
+
return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent)
|
|
203
|
+
|
|
204
|
+
structured_error = ErrorHandler.from_response(response, service: 'Vault', operation: operation)
|
|
205
|
+
BrainzLab.debug_log("[Vault::Client] #{operation} failed: #{structured_error.message}")
|
|
206
|
+
|
|
207
|
+
# Call on_error callback if configured
|
|
208
|
+
if @config.on_error
|
|
209
|
+
@config.on_error.call(structured_error, { service: 'Vault', operation: operation })
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
structured_error
|
|
193
213
|
end
|
|
194
214
|
end
|
|
195
215
|
end
|
data/lib/brainzlab/version.rb
CHANGED
|
@@ -98,18 +98,65 @@ module BrainzLab
|
|
|
98
98
|
when Net::HTTPSuccess
|
|
99
99
|
JSON.parse(response.body, symbolize_names: true)
|
|
100
100
|
when Net::HTTPUnauthorized
|
|
101
|
-
|
|
101
|
+
structured_error = AuthenticationError.new(
|
|
102
|
+
'Invalid API key',
|
|
103
|
+
hint: 'Verify your Vision API key is correct and has not expired.',
|
|
104
|
+
code: 'vision_unauthorized'
|
|
105
|
+
)
|
|
106
|
+
log_error(path, structured_error)
|
|
107
|
+
{ error: structured_error.message, brainzlab_error: structured_error }
|
|
102
108
|
when Net::HTTPForbidden
|
|
103
|
-
|
|
109
|
+
structured_error = AuthenticationError.new(
|
|
110
|
+
'Vision is not enabled for this project',
|
|
111
|
+
hint: 'Enable Vision in your project settings or check your permissions.',
|
|
112
|
+
code: 'vision_forbidden'
|
|
113
|
+
)
|
|
114
|
+
log_error(path, structured_error)
|
|
115
|
+
{ error: structured_error.message, brainzlab_error: structured_error }
|
|
104
116
|
when Net::HTTPNotFound
|
|
105
|
-
|
|
117
|
+
structured_error = NotFoundError.new(
|
|
118
|
+
"Vision endpoint not found: #{path}",
|
|
119
|
+
hint: 'Verify the Vision service is properly configured.',
|
|
120
|
+
code: 'vision_not_found',
|
|
121
|
+
resource_type: 'endpoint',
|
|
122
|
+
resource_id: path
|
|
123
|
+
)
|
|
124
|
+
log_error(path, structured_error)
|
|
125
|
+
{ error: structured_error.message, brainzlab_error: structured_error }
|
|
126
|
+
when Net::HTTPTooManyRequests
|
|
127
|
+
structured_error = RateLimitError.new(
|
|
128
|
+
'Vision rate limit exceeded',
|
|
129
|
+
retry_after: response['Retry-After']&.to_i,
|
|
130
|
+
code: 'vision_rate_limit'
|
|
131
|
+
)
|
|
132
|
+
log_error(path, structured_error)
|
|
133
|
+
{ error: structured_error.message, brainzlab_error: structured_error }
|
|
106
134
|
else
|
|
107
|
-
|
|
135
|
+
structured_error = ErrorHandler.from_response(response, service: 'Vision', operation: path)
|
|
136
|
+
log_error(path, structured_error)
|
|
137
|
+
{ error: structured_error.message, brainzlab_error: structured_error }
|
|
108
138
|
end
|
|
109
139
|
rescue JSON::ParserError => e
|
|
110
|
-
|
|
140
|
+
structured_error = ServerError.new(
|
|
141
|
+
"Invalid JSON response from Vision: #{e.message}",
|
|
142
|
+
hint: 'The Vision service returned an unexpected response format.',
|
|
143
|
+
code: 'vision_invalid_response'
|
|
144
|
+
)
|
|
145
|
+
log_error(path, structured_error)
|
|
146
|
+
{ error: structured_error.message, brainzlab_error: structured_error }
|
|
111
147
|
rescue StandardError => e
|
|
112
|
-
|
|
148
|
+
structured_error = ErrorHandler.wrap(e, service: 'Vision', operation: path)
|
|
149
|
+
log_error(path, structured_error)
|
|
150
|
+
{ error: structured_error.message, brainzlab_error: structured_error }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def log_error(operation, error)
|
|
154
|
+
BrainzLab.debug_log("[Vision::Client] #{operation} failed: #{error.message}")
|
|
155
|
+
|
|
156
|
+
# Call on_error callback if configured
|
|
157
|
+
if @config.on_error
|
|
158
|
+
@config.on_error.call(error, { service: 'Vision', operation: operation })
|
|
159
|
+
end
|
|
113
160
|
end
|
|
114
161
|
|
|
115
162
|
def auth_key
|
data/lib/brainzlab.rb
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# BrainzLab SDK - Official Ruby SDK for BrainzLab products
|
|
4
|
+
#
|
|
5
|
+
# For testing utilities, require 'brainzlab/testing' in your test helper:
|
|
6
|
+
#
|
|
7
|
+
# require 'brainzlab/testing'
|
|
8
|
+
#
|
|
9
|
+
# RSpec.configure do |config|
|
|
10
|
+
# config.include BrainzLab::Testing::Helpers
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# See BrainzLab::Testing for more details.
|
|
14
|
+
|
|
3
15
|
require_relative 'brainzlab/version'
|
|
16
|
+
require_relative 'brainzlab/errors'
|
|
4
17
|
require_relative 'brainzlab/configuration'
|
|
18
|
+
require_relative 'brainzlab/debug'
|
|
5
19
|
require_relative 'brainzlab/context'
|
|
6
20
|
require_relative 'brainzlab/recall'
|
|
7
21
|
require_relative 'brainzlab/reflex'
|
|
@@ -18,6 +32,7 @@ require_relative 'brainzlab/sentinel'
|
|
|
18
32
|
require_relative 'brainzlab/synapse'
|
|
19
33
|
require_relative 'brainzlab/instrumentation'
|
|
20
34
|
require_relative 'brainzlab/utilities'
|
|
35
|
+
require_relative 'brainzlab/development'
|
|
21
36
|
|
|
22
37
|
module BrainzLab
|
|
23
38
|
class << self
|
|
@@ -45,6 +60,7 @@ module BrainzLab
|
|
|
45
60
|
Dendrite.reset!
|
|
46
61
|
Sentinel.reset!
|
|
47
62
|
Synapse.reset!
|
|
63
|
+
Development.reset!
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
# Context management
|
|
@@ -94,6 +110,32 @@ module BrainzLab
|
|
|
94
110
|
configuration.debug?
|
|
95
111
|
end
|
|
96
112
|
|
|
113
|
+
# Query events stored in development mode
|
|
114
|
+
# @param service [Symbol, nil] filter by service (:recall, :reflex, :pulse, etc.)
|
|
115
|
+
# @param event_type [String, nil] filter by event type ('log', 'error', 'trace', etc.)
|
|
116
|
+
# @param since [Time, nil] filter events after this time
|
|
117
|
+
# @param limit [Integer] max number of events to return (default: 100)
|
|
118
|
+
# @return [Array<Hash>] matching events
|
|
119
|
+
# @example
|
|
120
|
+
# BrainzLab.development_events # All events
|
|
121
|
+
# BrainzLab.development_events(service: :recall) # Only Recall logs
|
|
122
|
+
# BrainzLab.development_events(service: :reflex, limit: 10) # Last 10 errors
|
|
123
|
+
# BrainzLab.development_events(since: 1.hour.ago) # Events from last hour
|
|
124
|
+
def development_events(service: nil, event_type: nil, since: nil, limit: 100)
|
|
125
|
+
Development.events(service: service, event_type: event_type, since: since, limit: limit)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Clear all events stored in development mode
|
|
129
|
+
def clear_development_events!
|
|
130
|
+
Development.clear!
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Get stats about stored development events
|
|
134
|
+
# @return [Hash] counts by service
|
|
135
|
+
def development_stats
|
|
136
|
+
Development.stats
|
|
137
|
+
end
|
|
138
|
+
|
|
97
139
|
# Health check - verifies connectivity to all enabled services
|
|
98
140
|
# @return [Hash] Status of each service
|
|
99
141
|
def health_check
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brainzlab
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brainz Lab
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.5'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: sqlite3
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: rake
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -102,9 +116,13 @@ files:
|
|
|
102
116
|
- lib/brainzlab/cortex/cache.rb
|
|
103
117
|
- lib/brainzlab/cortex/client.rb
|
|
104
118
|
- lib/brainzlab/cortex/provisioner.rb
|
|
119
|
+
- lib/brainzlab/debug.rb
|
|
105
120
|
- lib/brainzlab/dendrite.rb
|
|
106
121
|
- lib/brainzlab/dendrite/client.rb
|
|
107
122
|
- lib/brainzlab/dendrite/provisioner.rb
|
|
123
|
+
- lib/brainzlab/development.rb
|
|
124
|
+
- lib/brainzlab/development/logger.rb
|
|
125
|
+
- lib/brainzlab/development/store.rb
|
|
108
126
|
- lib/brainzlab/devtools.rb
|
|
109
127
|
- lib/brainzlab/devtools/assets/devtools.css
|
|
110
128
|
- lib/brainzlab/devtools/assets/devtools.js
|
|
@@ -118,6 +136,7 @@ files:
|
|
|
118
136
|
- lib/brainzlab/devtools/middleware/error_page.rb
|
|
119
137
|
- lib/brainzlab/devtools/renderers/debug_panel_renderer.rb
|
|
120
138
|
- lib/brainzlab/devtools/renderers/error_page_renderer.rb
|
|
139
|
+
- lib/brainzlab/errors.rb
|
|
121
140
|
- lib/brainzlab/flux.rb
|
|
122
141
|
- lib/brainzlab/flux/buffer.rb
|
|
123
142
|
- lib/brainzlab/flux/client.rb
|
|
@@ -183,6 +202,10 @@ files:
|
|
|
183
202
|
- lib/brainzlab/synapse.rb
|
|
184
203
|
- lib/brainzlab/synapse/client.rb
|
|
185
204
|
- lib/brainzlab/synapse/provisioner.rb
|
|
205
|
+
- lib/brainzlab/testing.rb
|
|
206
|
+
- lib/brainzlab/testing/event_store.rb
|
|
207
|
+
- lib/brainzlab/testing/helpers.rb
|
|
208
|
+
- lib/brainzlab/testing/matchers.rb
|
|
186
209
|
- lib/brainzlab/utilities.rb
|
|
187
210
|
- lib/brainzlab/utilities/circuit_breaker.rb
|
|
188
211
|
- lib/brainzlab/utilities/health_check.rb
|