vectra-client 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +86 -37
- data/SECURITY.md +134 -4
- data/docs/_layouts/page.html +2 -0
- data/docs/guides/monitoring.md +860 -0
- data/docs/guides/runbooks/cache-issues.md +267 -0
- data/docs/guides/runbooks/high-error-rate.md +152 -0
- data/docs/guides/runbooks/high-latency.md +287 -0
- data/docs/guides/runbooks/pool-exhausted.md +216 -0
- data/docs/guides/security.md +348 -0
- data/lib/vectra/audit_log.rb +225 -0
- data/lib/vectra/circuit_breaker.rb +336 -0
- data/lib/vectra/client.rb +2 -0
- data/lib/vectra/credential_rotation.rb +199 -0
- data/lib/vectra/health_check.rb +254 -0
- data/lib/vectra/instrumentation/honeybadger.rb +128 -0
- data/lib/vectra/instrumentation/sentry.rb +117 -0
- data/lib/vectra/logging.rb +242 -0
- data/lib/vectra/rate_limiter.rb +304 -0
- data/lib/vectra/version.rb +1 -1
- data/lib/vectra.rb +6 -0
- metadata +15 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vectra
|
|
4
|
+
# Audit logging for security and compliance
|
|
5
|
+
#
|
|
6
|
+
# Provides structured audit logs for security-sensitive operations,
|
|
7
|
+
# including authentication, authorization, and data access.
|
|
8
|
+
#
|
|
9
|
+
# @example Basic usage
|
|
10
|
+
# audit = Vectra::AuditLog.new(output: "log/audit.json.log")
|
|
11
|
+
#
|
|
12
|
+
# audit.log_access(
|
|
13
|
+
# user_id: "user123",
|
|
14
|
+
# operation: "query",
|
|
15
|
+
# index: "sensitive-data",
|
|
16
|
+
# result_count: 10
|
|
17
|
+
# )
|
|
18
|
+
#
|
|
19
|
+
class AuditLog
|
|
20
|
+
# Audit event types
|
|
21
|
+
EVENT_TYPES = %i[
|
|
22
|
+
access
|
|
23
|
+
authentication
|
|
24
|
+
authorization
|
|
25
|
+
configuration_change
|
|
26
|
+
credential_rotation
|
|
27
|
+
data_modification
|
|
28
|
+
error
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
attr_reader :logger, :enabled
|
|
32
|
+
|
|
33
|
+
# Initialize audit logger
|
|
34
|
+
#
|
|
35
|
+
# @param output [IO, String] Log output destination
|
|
36
|
+
# @param enabled [Boolean] Enable/disable audit logging
|
|
37
|
+
# @param metadata [Hash] Default metadata for all events
|
|
38
|
+
def initialize(output: $stdout, enabled: true, **metadata)
|
|
39
|
+
@enabled = enabled
|
|
40
|
+
@logger = enabled ? Vectra::JsonLogger.new(output, **metadata) : nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Log access event
|
|
44
|
+
#
|
|
45
|
+
# @param user_id [String, nil] User identifier
|
|
46
|
+
# @param operation [Symbol, String] Operation type
|
|
47
|
+
# @param index [String] Index accessed
|
|
48
|
+
# @param result_count [Integer, nil] Number of results
|
|
49
|
+
# @param metadata [Hash] Additional metadata
|
|
50
|
+
def log_access(operation:, index: nil, result_count: nil, user_id: nil, **metadata)
|
|
51
|
+
log_event(
|
|
52
|
+
type: :access,
|
|
53
|
+
user_id: user_id,
|
|
54
|
+
operation: operation.to_s,
|
|
55
|
+
resource: index,
|
|
56
|
+
result_count: result_count,
|
|
57
|
+
**metadata
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Log authentication event
|
|
62
|
+
#
|
|
63
|
+
# @param user_id [String, nil] User identifier
|
|
64
|
+
# @param success [Boolean] Authentication success
|
|
65
|
+
# @param provider [String, nil] Provider name
|
|
66
|
+
# @param metadata [Hash] Additional metadata
|
|
67
|
+
def log_authentication(success:, provider: nil, user_id: nil, **metadata)
|
|
68
|
+
log_event(
|
|
69
|
+
type: :authentication,
|
|
70
|
+
user_id: user_id,
|
|
71
|
+
success: success,
|
|
72
|
+
provider: provider,
|
|
73
|
+
**metadata
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Log authorization event
|
|
78
|
+
#
|
|
79
|
+
# @param user_id [String] User identifier
|
|
80
|
+
# @param resource [String] Resource accessed
|
|
81
|
+
# @param allowed [Boolean] Authorization result
|
|
82
|
+
# @param reason [String, nil] Reason if denied
|
|
83
|
+
def log_authorization(user_id:, resource:, allowed:, reason: nil)
|
|
84
|
+
log_event(
|
|
85
|
+
type: :authorization,
|
|
86
|
+
user_id: user_id,
|
|
87
|
+
resource: resource,
|
|
88
|
+
allowed: allowed,
|
|
89
|
+
reason: reason
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Log configuration change
|
|
94
|
+
#
|
|
95
|
+
# @param user_id [String] User who made change
|
|
96
|
+
# @param change_type [String] Type of change
|
|
97
|
+
# @param old_value [Object, nil] Previous value
|
|
98
|
+
# @param new_value [Object, nil] New value
|
|
99
|
+
def log_configuration_change(user_id:, change_type:, old_value: nil, new_value: nil)
|
|
100
|
+
log_event(
|
|
101
|
+
type: :configuration_change,
|
|
102
|
+
user_id: user_id,
|
|
103
|
+
change_type: change_type,
|
|
104
|
+
old_value: sanitize_value(old_value),
|
|
105
|
+
new_value: sanitize_value(new_value)
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Log credential rotation
|
|
110
|
+
#
|
|
111
|
+
# @param provider [String] Provider name
|
|
112
|
+
# @param success [Boolean] Rotation success
|
|
113
|
+
# @param rotated_by [String, nil] User who initiated rotation
|
|
114
|
+
def log_credential_rotation(provider:, success:, rotated_by: nil)
|
|
115
|
+
log_event(
|
|
116
|
+
type: :credential_rotation,
|
|
117
|
+
provider: provider,
|
|
118
|
+
success: success,
|
|
119
|
+
rotated_by: rotated_by
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Log data modification
|
|
124
|
+
#
|
|
125
|
+
# @param user_id [String, nil] User identifier
|
|
126
|
+
# @param operation [String] Operation (upsert, delete, update)
|
|
127
|
+
# @param index [String] Index modified
|
|
128
|
+
# @param record_count [Integer] Number of records affected
|
|
129
|
+
def log_data_modification(operation:, index:, record_count:, user_id: nil)
|
|
130
|
+
log_event(
|
|
131
|
+
type: :data_modification,
|
|
132
|
+
user_id: user_id,
|
|
133
|
+
operation: operation.to_s,
|
|
134
|
+
resource: index,
|
|
135
|
+
record_count: record_count
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Log error event
|
|
140
|
+
#
|
|
141
|
+
# @param error [Exception] Error that occurred
|
|
142
|
+
# @param context [Hash] Error context
|
|
143
|
+
def log_error(error:, **context)
|
|
144
|
+
log_event(
|
|
145
|
+
type: :error,
|
|
146
|
+
error_class: error.class.name,
|
|
147
|
+
error_message: error.message,
|
|
148
|
+
severity: error_severity(error),
|
|
149
|
+
**context
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def log_event(type:, **data)
|
|
156
|
+
return unless @enabled && @logger
|
|
157
|
+
|
|
158
|
+
@logger.info(
|
|
159
|
+
"audit.#{type}",
|
|
160
|
+
event_type: type.to_s,
|
|
161
|
+
timestamp: Time.now.utc.iso8601(3),
|
|
162
|
+
**data
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def sanitize_value(value)
|
|
167
|
+
case value
|
|
168
|
+
when String
|
|
169
|
+
# Mask sensitive values: keep a short prefix and suffix, hide the middle
|
|
170
|
+
return value unless value.length >= 10
|
|
171
|
+
|
|
172
|
+
prefix = value[0, 9]
|
|
173
|
+
suffix = value[-4, 4]
|
|
174
|
+
"#{prefix}...#{suffix}"
|
|
175
|
+
when Hash
|
|
176
|
+
value.transform_values { |v| sanitize_value(v) }
|
|
177
|
+
else
|
|
178
|
+
value
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def error_severity(error)
|
|
183
|
+
case error
|
|
184
|
+
when Vectra::AuthenticationError
|
|
185
|
+
"critical"
|
|
186
|
+
when Vectra::ServerError, Vectra::ConnectionError
|
|
187
|
+
"high"
|
|
188
|
+
when Vectra::RateLimitError
|
|
189
|
+
"medium"
|
|
190
|
+
else
|
|
191
|
+
"low"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Global audit log instance
|
|
197
|
+
module AuditLogging
|
|
198
|
+
class << self
|
|
199
|
+
attr_accessor :audit_log
|
|
200
|
+
|
|
201
|
+
# Setup global audit logging
|
|
202
|
+
#
|
|
203
|
+
# @param output [IO, String] Log output
|
|
204
|
+
# @param enabled [Boolean] Enable audit logging
|
|
205
|
+
# @param metadata [Hash] Default metadata
|
|
206
|
+
# @return [AuditLog]
|
|
207
|
+
def setup!(output: "log/audit.json.log", enabled: true, **metadata)
|
|
208
|
+
@audit_log = AuditLog.new(output: output, enabled: enabled, **metadata)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Log audit event
|
|
212
|
+
#
|
|
213
|
+
# @param type [Symbol] Event type
|
|
214
|
+
# @param data [Hash] Event data
|
|
215
|
+
def log(type, **data)
|
|
216
|
+
return unless @audit_log
|
|
217
|
+
|
|
218
|
+
@audit_log.public_send("log_#{type}", **data)
|
|
219
|
+
rescue NoMethodError
|
|
220
|
+
# Event type not supported
|
|
221
|
+
@audit_log.instance_variable_get(:@logger)&.info("audit.#{type}", **data)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vectra
|
|
4
|
+
# Circuit Breaker pattern for handling provider failures
|
|
5
|
+
#
|
|
6
|
+
# Prevents cascading failures by temporarily stopping requests to a failing provider.
|
|
7
|
+
# The circuit has three states:
|
|
8
|
+
# - :closed - Normal operation, requests pass through
|
|
9
|
+
# - :open - Requests fail immediately without calling provider
|
|
10
|
+
# - :half_open - Limited requests allowed to test if provider recovered
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# breaker = Vectra::CircuitBreaker.new(
|
|
14
|
+
# failure_threshold: 5,
|
|
15
|
+
# recovery_timeout: 30
|
|
16
|
+
# )
|
|
17
|
+
#
|
|
18
|
+
# breaker.call do
|
|
19
|
+
# client.query(index: "my-index", vector: vec, top_k: 10)
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example With fallback
|
|
23
|
+
# breaker.call(fallback: -> { cached_results }) do
|
|
24
|
+
# client.query(...)
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example Per-provider circuit breakers
|
|
28
|
+
# breakers = {
|
|
29
|
+
# pinecone: Vectra::CircuitBreaker.new(name: "pinecone"),
|
|
30
|
+
# qdrant: Vectra::CircuitBreaker.new(name: "qdrant")
|
|
31
|
+
# }
|
|
32
|
+
#
|
|
33
|
+
class CircuitBreaker
|
|
34
|
+
STATES = [:closed, :open, :half_open].freeze
|
|
35
|
+
|
|
36
|
+
# Error raised when circuit is open
|
|
37
|
+
class OpenCircuitError < Vectra::Error
|
|
38
|
+
attr_reader :circuit_name, :failures, :opened_at
|
|
39
|
+
|
|
40
|
+
def initialize(circuit_name:, failures:, opened_at:)
|
|
41
|
+
@circuit_name = circuit_name
|
|
42
|
+
@failures = failures
|
|
43
|
+
@opened_at = opened_at
|
|
44
|
+
super("Circuit '#{circuit_name}' is open after #{failures} failures")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
attr_reader :name, :state, :failure_count, :success_count,
|
|
49
|
+
:last_failure_at, :opened_at
|
|
50
|
+
|
|
51
|
+
# Initialize a new circuit breaker
|
|
52
|
+
#
|
|
53
|
+
# @param name [String] Circuit name for logging/metrics
|
|
54
|
+
# @param failure_threshold [Integer] Failures before opening circuit (default: 5)
|
|
55
|
+
# @param success_threshold [Integer] Successes in half-open to close (default: 3)
|
|
56
|
+
# @param recovery_timeout [Integer] Seconds before trying half-open (default: 30)
|
|
57
|
+
# @param monitored_errors [Array<Class>] Errors that count as failures
|
|
58
|
+
def initialize(
|
|
59
|
+
name: "default",
|
|
60
|
+
failure_threshold: 5,
|
|
61
|
+
success_threshold: 3,
|
|
62
|
+
recovery_timeout: 30,
|
|
63
|
+
monitored_errors: nil
|
|
64
|
+
)
|
|
65
|
+
@name = name
|
|
66
|
+
@failure_threshold = failure_threshold
|
|
67
|
+
@success_threshold = success_threshold
|
|
68
|
+
@recovery_timeout = recovery_timeout
|
|
69
|
+
@monitored_errors = monitored_errors || default_monitored_errors
|
|
70
|
+
|
|
71
|
+
@state = :closed
|
|
72
|
+
@failure_count = 0
|
|
73
|
+
@success_count = 0
|
|
74
|
+
@last_failure_at = nil
|
|
75
|
+
@opened_at = nil
|
|
76
|
+
@mutex = Mutex.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Execute block through circuit breaker
|
|
80
|
+
#
|
|
81
|
+
# @param fallback [Proc, nil] Fallback to call when circuit is open
|
|
82
|
+
# @yield The operation to execute
|
|
83
|
+
# @return [Object] Result of block or fallback
|
|
84
|
+
# @raise [OpenCircuitError] If circuit is open and no fallback provided
|
|
85
|
+
def call(fallback: nil, &)
|
|
86
|
+
check_state!
|
|
87
|
+
|
|
88
|
+
if open?
|
|
89
|
+
return handle_open_circuit(fallback)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
execute_with_monitoring(&)
|
|
93
|
+
rescue *@monitored_errors => e
|
|
94
|
+
record_failure(e)
|
|
95
|
+
raise
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Force circuit to closed state (manual reset)
|
|
99
|
+
#
|
|
100
|
+
# @return [void]
|
|
101
|
+
def reset!
|
|
102
|
+
@mutex.synchronize do
|
|
103
|
+
transition_to(:closed)
|
|
104
|
+
@failure_count = 0
|
|
105
|
+
@success_count = 0
|
|
106
|
+
@last_failure_at = nil
|
|
107
|
+
@opened_at = nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Force circuit to open state (manual trip)
|
|
112
|
+
#
|
|
113
|
+
# @return [void]
|
|
114
|
+
def trip!
|
|
115
|
+
@mutex.synchronize do
|
|
116
|
+
transition_to(:open)
|
|
117
|
+
@opened_at = Time.now
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Check if circuit is closed (normal operation)
|
|
122
|
+
#
|
|
123
|
+
# @return [Boolean]
|
|
124
|
+
def closed?
|
|
125
|
+
state == :closed
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Check if circuit is open (blocking requests)
|
|
129
|
+
#
|
|
130
|
+
# @return [Boolean]
|
|
131
|
+
def open?
|
|
132
|
+
state == :open
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check if circuit is half-open (testing recovery)
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean]
|
|
138
|
+
def half_open?
|
|
139
|
+
state == :half_open
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Get circuit statistics
|
|
143
|
+
#
|
|
144
|
+
# @return [Hash]
|
|
145
|
+
def stats
|
|
146
|
+
{
|
|
147
|
+
name: name,
|
|
148
|
+
state: state,
|
|
149
|
+
failure_count: failure_count,
|
|
150
|
+
success_count: success_count,
|
|
151
|
+
failure_threshold: @failure_threshold,
|
|
152
|
+
success_threshold: @success_threshold,
|
|
153
|
+
recovery_timeout: @recovery_timeout,
|
|
154
|
+
last_failure_at: last_failure_at,
|
|
155
|
+
opened_at: opened_at
|
|
156
|
+
}
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
def default_monitored_errors
|
|
162
|
+
[
|
|
163
|
+
Vectra::ServerError,
|
|
164
|
+
Vectra::ConnectionError,
|
|
165
|
+
Vectra::TimeoutError
|
|
166
|
+
]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def check_state!
|
|
170
|
+
@mutex.synchronize do
|
|
171
|
+
# Check if we should transition from open to half-open
|
|
172
|
+
if open? && recovery_timeout_elapsed?
|
|
173
|
+
transition_to(:half_open)
|
|
174
|
+
@success_count = 0
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def recovery_timeout_elapsed?
|
|
180
|
+
return false unless opened_at
|
|
181
|
+
|
|
182
|
+
Time.now - opened_at >= @recovery_timeout
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def handle_open_circuit(fallback)
|
|
186
|
+
if fallback
|
|
187
|
+
log_fallback
|
|
188
|
+
fallback.call
|
|
189
|
+
else
|
|
190
|
+
raise OpenCircuitError.new(
|
|
191
|
+
circuit_name: name,
|
|
192
|
+
failures: failure_count,
|
|
193
|
+
opened_at: opened_at
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def execute_with_monitoring
|
|
199
|
+
result = yield
|
|
200
|
+
record_success
|
|
201
|
+
result
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def record_success
|
|
205
|
+
@mutex.synchronize do
|
|
206
|
+
@success_count += 1
|
|
207
|
+
|
|
208
|
+
# In half-open, check if we should close
|
|
209
|
+
if half_open? && @success_count >= @success_threshold
|
|
210
|
+
transition_to(:closed)
|
|
211
|
+
@failure_count = 0
|
|
212
|
+
log_circuit_closed
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def record_failure(error)
|
|
218
|
+
@mutex.synchronize do
|
|
219
|
+
@failure_count += 1
|
|
220
|
+
@last_failure_at = Time.now
|
|
221
|
+
|
|
222
|
+
# In half-open, immediately open again
|
|
223
|
+
if half_open?
|
|
224
|
+
transition_to(:open)
|
|
225
|
+
@opened_at = Time.now
|
|
226
|
+
log_circuit_reopened(error)
|
|
227
|
+
return
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# In closed, check threshold
|
|
231
|
+
if closed? && @failure_count >= @failure_threshold
|
|
232
|
+
transition_to(:open)
|
|
233
|
+
@opened_at = Time.now
|
|
234
|
+
log_circuit_opened(error)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def transition_to(new_state)
|
|
240
|
+
@state = new_state
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def log_circuit_opened(error)
|
|
244
|
+
logger&.error(
|
|
245
|
+
"[Vectra::CircuitBreaker] Circuit '#{name}' opened after #{failure_count} failures. " \
|
|
246
|
+
"Last error: #{error.class} - #{error.message}"
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def log_circuit_closed
|
|
251
|
+
logger&.info(
|
|
252
|
+
"[Vectra::CircuitBreaker] Circuit '#{name}' closed after #{success_count} successes"
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def log_circuit_reopened(error)
|
|
257
|
+
logger&.warn(
|
|
258
|
+
"[Vectra::CircuitBreaker] Circuit '#{name}' reopened. " \
|
|
259
|
+
"Recovery failed: #{error.class} - #{error.message}"
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def log_fallback
|
|
264
|
+
logger&.info(
|
|
265
|
+
"[Vectra::CircuitBreaker] Circuit '#{name}' open, using fallback"
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def logger
|
|
270
|
+
Vectra.configuration.logger
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Circuit breaker registry for managing multiple circuits
|
|
275
|
+
#
|
|
276
|
+
# @example
|
|
277
|
+
# Vectra::CircuitBreakerRegistry.register(:pinecone, failure_threshold: 3)
|
|
278
|
+
# Vectra::CircuitBreakerRegistry.register(:qdrant, failure_threshold: 5)
|
|
279
|
+
#
|
|
280
|
+
# Vectra::CircuitBreakerRegistry[:pinecone].call { ... }
|
|
281
|
+
#
|
|
282
|
+
module CircuitBreakerRegistry
|
|
283
|
+
class << self
|
|
284
|
+
# Get or create a circuit breaker
|
|
285
|
+
#
|
|
286
|
+
# @param name [Symbol, String] Circuit name
|
|
287
|
+
# @return [CircuitBreaker]
|
|
288
|
+
def [](name)
|
|
289
|
+
circuits[name.to_sym]
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Register a new circuit breaker
|
|
293
|
+
#
|
|
294
|
+
# @param name [Symbol, String] Circuit name
|
|
295
|
+
# @param options [Hash] CircuitBreaker options
|
|
296
|
+
# @return [CircuitBreaker]
|
|
297
|
+
def register(name, **options)
|
|
298
|
+
circuits[name.to_sym] = CircuitBreaker.new(name: name.to_s, **options)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Get all registered circuits
|
|
302
|
+
#
|
|
303
|
+
# @return [Hash<Symbol, CircuitBreaker>]
|
|
304
|
+
def all
|
|
305
|
+
circuits.dup
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Reset all circuits
|
|
309
|
+
#
|
|
310
|
+
# @return [void]
|
|
311
|
+
def reset_all!
|
|
312
|
+
circuits.each_value(&:reset!)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Get stats for all circuits
|
|
316
|
+
#
|
|
317
|
+
# @return [Hash<Symbol, Hash>]
|
|
318
|
+
def stats
|
|
319
|
+
circuits.transform_values(&:stats)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Clear all registered circuits
|
|
323
|
+
#
|
|
324
|
+
# @return [void]
|
|
325
|
+
def clear!
|
|
326
|
+
@circuits = {}
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
private
|
|
330
|
+
|
|
331
|
+
def circuits
|
|
332
|
+
@circuits ||= {}
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|