brainzlab 0.1.1 → 0.1.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/README.md +8 -0
- data/lib/brainzlab/beacon/client.rb +209 -0
- data/lib/brainzlab/beacon/provisioner.rb +44 -0
- data/lib/brainzlab/beacon.rb +215 -0
- data/lib/brainzlab/configuration.rb +341 -3
- data/lib/brainzlab/cortex/cache.rb +59 -0
- data/lib/brainzlab/cortex/client.rb +141 -0
- data/lib/brainzlab/cortex/provisioner.rb +49 -0
- data/lib/brainzlab/cortex.rb +227 -0
- data/lib/brainzlab/dendrite/client.rb +232 -0
- data/lib/brainzlab/dendrite/provisioner.rb +44 -0
- data/lib/brainzlab/dendrite.rb +195 -0
- data/lib/brainzlab/devtools/assets/devtools.css +1106 -0
- data/lib/brainzlab/devtools/assets/devtools.js +322 -0
- data/lib/brainzlab/devtools/assets/logo.svg +6 -0
- data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +500 -0
- data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
- data/lib/brainzlab/devtools/data/collector.rb +248 -0
- data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
- data/lib/brainzlab/devtools/middleware/database_handler.rb +180 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +376 -0
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +155 -0
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +94 -0
- data/lib/brainzlab/devtools.rb +75 -0
- data/lib/brainzlab/flux/buffer.rb +96 -0
- data/lib/brainzlab/flux/client.rb +70 -0
- data/lib/brainzlab/flux/provisioner.rb +57 -0
- data/lib/brainzlab/flux.rb +174 -0
- data/lib/brainzlab/instrumentation/active_record.rb +18 -1
- data/lib/brainzlab/instrumentation/aws.rb +179 -0
- data/lib/brainzlab/instrumentation/dalli.rb +108 -0
- data/lib/brainzlab/instrumentation/excon.rb +152 -0
- data/lib/brainzlab/instrumentation/good_job.rb +102 -0
- data/lib/brainzlab/instrumentation/resque.rb +115 -0
- data/lib/brainzlab/instrumentation/solid_queue.rb +198 -0
- data/lib/brainzlab/instrumentation/stripe.rb +164 -0
- data/lib/brainzlab/instrumentation/typhoeus.rb +104 -0
- data/lib/brainzlab/instrumentation.rb +72 -0
- data/lib/brainzlab/nerve/client.rb +217 -0
- data/lib/brainzlab/nerve/provisioner.rb +44 -0
- data/lib/brainzlab/nerve.rb +219 -0
- data/lib/brainzlab/pulse/instrumentation.rb +35 -2
- data/lib/brainzlab/pulse/propagation.rb +1 -1
- data/lib/brainzlab/pulse/tracer.rb +1 -1
- data/lib/brainzlab/pulse.rb +1 -1
- data/lib/brainzlab/rails/log_subscriber.rb +1 -2
- data/lib/brainzlab/rails/railtie.rb +36 -3
- data/lib/brainzlab/recall/provisioner.rb +17 -0
- data/lib/brainzlab/recall.rb +6 -1
- data/lib/brainzlab/reflex.rb +2 -2
- data/lib/brainzlab/sentinel/client.rb +218 -0
- data/lib/brainzlab/sentinel/provisioner.rb +44 -0
- data/lib/brainzlab/sentinel.rb +165 -0
- data/lib/brainzlab/signal/client.rb +62 -0
- data/lib/brainzlab/signal/provisioner.rb +55 -0
- data/lib/brainzlab/signal.rb +136 -0
- data/lib/brainzlab/synapse/client.rb +290 -0
- data/lib/brainzlab/synapse/provisioner.rb +44 -0
- data/lib/brainzlab/synapse.rb +270 -0
- data/lib/brainzlab/utilities/circuit_breaker.rb +265 -0
- data/lib/brainzlab/utilities/health_check.rb +296 -0
- data/lib/brainzlab/utilities/log_formatter.rb +256 -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 +198 -0
- data/lib/brainzlab/vault/provisioner.rb +49 -0
- data/lib/brainzlab/vault.rb +268 -0
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +128 -0
- data/lib/brainzlab/vision/provisioner.rb +136 -0
- data/lib/brainzlab/vision.rb +157 -0
- data/lib/brainzlab.rb +101 -0
- metadata +60 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "nerve/client"
|
|
4
|
+
require_relative "nerve/provisioner"
|
|
5
|
+
|
|
6
|
+
module BrainzLab
|
|
7
|
+
module Nerve
|
|
8
|
+
class << self
|
|
9
|
+
# Report a completed job
|
|
10
|
+
# @param job_class [String] Job class name
|
|
11
|
+
# @param job_id [String] Job ID
|
|
12
|
+
# @param queue [String] Queue name
|
|
13
|
+
# @param started_at [Time] When job started
|
|
14
|
+
# @param ended_at [Time] When job ended (defaults to now)
|
|
15
|
+
# @param attributes [Hash] Additional attributes
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# BrainzLab::Nerve.report_success(
|
|
19
|
+
# job_class: "ProcessOrderJob",
|
|
20
|
+
# job_id: "abc-123",
|
|
21
|
+
# queue: "default",
|
|
22
|
+
# started_at: 1.minute.ago
|
|
23
|
+
# )
|
|
24
|
+
#
|
|
25
|
+
def report_success(job_class:, job_id:, queue:, started_at:, ended_at: Time.now, **attributes)
|
|
26
|
+
return false unless enabled?
|
|
27
|
+
|
|
28
|
+
ensure_provisioned!
|
|
29
|
+
return false unless BrainzLab.configuration.nerve_valid?
|
|
30
|
+
|
|
31
|
+
client.report_job(
|
|
32
|
+
job_class: job_class,
|
|
33
|
+
job_id: job_id,
|
|
34
|
+
queue: queue,
|
|
35
|
+
status: "completed",
|
|
36
|
+
started_at: started_at,
|
|
37
|
+
ended_at: ended_at,
|
|
38
|
+
**attributes
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Report a failed job
|
|
43
|
+
def report_failure(job_class:, job_id:, queue:, error:, started_at: nil, **attributes)
|
|
44
|
+
return false unless enabled?
|
|
45
|
+
|
|
46
|
+
ensure_provisioned!
|
|
47
|
+
return false unless BrainzLab.configuration.nerve_valid?
|
|
48
|
+
|
|
49
|
+
client.report_failure(
|
|
50
|
+
job_class: job_class,
|
|
51
|
+
job_id: job_id,
|
|
52
|
+
queue: queue,
|
|
53
|
+
error_class: error.class.name,
|
|
54
|
+
error_message: error.message,
|
|
55
|
+
backtrace: error.backtrace,
|
|
56
|
+
started_at: started_at,
|
|
57
|
+
**attributes
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Report a job that's currently running
|
|
62
|
+
def report_started(job_class:, job_id:, queue:, **attributes)
|
|
63
|
+
return false unless enabled?
|
|
64
|
+
|
|
65
|
+
ensure_provisioned!
|
|
66
|
+
return false unless BrainzLab.configuration.nerve_valid?
|
|
67
|
+
|
|
68
|
+
client.report_job(
|
|
69
|
+
job_class: job_class,
|
|
70
|
+
job_id: job_id,
|
|
71
|
+
queue: queue,
|
|
72
|
+
status: "running",
|
|
73
|
+
started_at: Time.now,
|
|
74
|
+
ended_at: Time.now,
|
|
75
|
+
**attributes
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get job statistics
|
|
80
|
+
# @param queue [String] Filter by queue (optional)
|
|
81
|
+
# @param job_class [String] Filter by job class (optional)
|
|
82
|
+
# @param period [String] Time period: "1h", "24h", "7d", "30d"
|
|
83
|
+
def stats(queue: nil, job_class: nil, period: "1h")
|
|
84
|
+
return nil unless enabled?
|
|
85
|
+
|
|
86
|
+
ensure_provisioned!
|
|
87
|
+
return nil unless BrainzLab.configuration.nerve_valid?
|
|
88
|
+
|
|
89
|
+
client.stats(queue: queue, job_class: job_class, period: period)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# List recent jobs
|
|
93
|
+
def jobs(queue: nil, status: nil, limit: 100)
|
|
94
|
+
return [] unless enabled?
|
|
95
|
+
|
|
96
|
+
ensure_provisioned!
|
|
97
|
+
return [] unless BrainzLab.configuration.nerve_valid?
|
|
98
|
+
|
|
99
|
+
client.list_jobs(queue: queue, status: status, limit: limit)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# List all queues
|
|
103
|
+
def queues
|
|
104
|
+
return [] unless enabled?
|
|
105
|
+
|
|
106
|
+
ensure_provisioned!
|
|
107
|
+
return [] unless BrainzLab.configuration.nerve_valid?
|
|
108
|
+
|
|
109
|
+
client.list_queues
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Get queue details
|
|
113
|
+
def queue(name)
|
|
114
|
+
return nil unless enabled?
|
|
115
|
+
|
|
116
|
+
ensure_provisioned!
|
|
117
|
+
return nil unless BrainzLab.configuration.nerve_valid?
|
|
118
|
+
|
|
119
|
+
client.get_queue(name)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Retry a failed job
|
|
123
|
+
def retry(job_id)
|
|
124
|
+
return false unless enabled?
|
|
125
|
+
|
|
126
|
+
ensure_provisioned!
|
|
127
|
+
return false unless BrainzLab.configuration.nerve_valid?
|
|
128
|
+
|
|
129
|
+
client.retry_job(job_id)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Delete a job
|
|
133
|
+
def delete(job_id)
|
|
134
|
+
return false unless enabled?
|
|
135
|
+
|
|
136
|
+
ensure_provisioned!
|
|
137
|
+
return false unless BrainzLab.configuration.nerve_valid?
|
|
138
|
+
|
|
139
|
+
client.delete_job(job_id)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Report queue metrics (for custom job backends)
|
|
143
|
+
def report_metrics(queue:, size:, latency_ms: nil, workers: nil)
|
|
144
|
+
return false unless enabled?
|
|
145
|
+
|
|
146
|
+
ensure_provisioned!
|
|
147
|
+
return false unless BrainzLab.configuration.nerve_valid?
|
|
148
|
+
|
|
149
|
+
client.report_metrics(
|
|
150
|
+
queue: queue,
|
|
151
|
+
size: size,
|
|
152
|
+
latency_ms: latency_ms,
|
|
153
|
+
workers: workers
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Track a job execution (block helper)
|
|
158
|
+
# @example
|
|
159
|
+
# BrainzLab::Nerve.track(job_class: "MyJob", job_id: "123", queue: "default") do
|
|
160
|
+
# # job work
|
|
161
|
+
# end
|
|
162
|
+
#
|
|
163
|
+
def track(job_class:, job_id:, queue: "default", **attributes)
|
|
164
|
+
started_at = Time.now
|
|
165
|
+
|
|
166
|
+
begin
|
|
167
|
+
result = yield
|
|
168
|
+
report_success(
|
|
169
|
+
job_class: job_class,
|
|
170
|
+
job_id: job_id,
|
|
171
|
+
queue: queue,
|
|
172
|
+
started_at: started_at,
|
|
173
|
+
**attributes
|
|
174
|
+
)
|
|
175
|
+
result
|
|
176
|
+
rescue StandardError => e
|
|
177
|
+
report_failure(
|
|
178
|
+
job_class: job_class,
|
|
179
|
+
job_id: job_id,
|
|
180
|
+
queue: queue,
|
|
181
|
+
error: e,
|
|
182
|
+
started_at: started_at,
|
|
183
|
+
**attributes
|
|
184
|
+
)
|
|
185
|
+
raise
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# === INTERNAL ===
|
|
190
|
+
|
|
191
|
+
def ensure_provisioned!
|
|
192
|
+
return if @provisioned
|
|
193
|
+
|
|
194
|
+
@provisioned = true
|
|
195
|
+
provisioner.ensure_project!
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def provisioner
|
|
199
|
+
@provisioner ||= Provisioner.new(BrainzLab.configuration)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def client
|
|
203
|
+
@client ||= Client.new(BrainzLab.configuration)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def reset!
|
|
207
|
+
@client = nil
|
|
208
|
+
@provisioner = nil
|
|
209
|
+
@provisioned = false
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
def enabled?
|
|
215
|
+
BrainzLab.configuration.nerve_enabled
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -26,6 +26,7 @@ module BrainzLab
|
|
|
26
26
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
27
27
|
next if skip_query?(event.payload)
|
|
28
28
|
|
|
29
|
+
sql = event.payload[:sql]
|
|
29
30
|
record_span(
|
|
30
31
|
name: event.payload[:name] || "SQL",
|
|
31
32
|
kind: "db",
|
|
@@ -33,9 +34,11 @@ module BrainzLab
|
|
|
33
34
|
ended_at: event.end,
|
|
34
35
|
duration_ms: event.duration,
|
|
35
36
|
data: {
|
|
36
|
-
sql: truncate_sql(
|
|
37
|
+
sql: truncate_sql(sql),
|
|
37
38
|
name: event.payload[:name],
|
|
38
|
-
cached: event.payload[:cached] || false
|
|
39
|
+
cached: event.payload[:cached] || false,
|
|
40
|
+
table: extract_table(sql),
|
|
41
|
+
operation: extract_operation(sql)
|
|
39
42
|
}
|
|
40
43
|
)
|
|
41
44
|
end
|
|
@@ -358,6 +361,36 @@ module BrainzLab
|
|
|
358
361
|
return nil unless path
|
|
359
362
|
path.to_s.split("/").last(2).join("/")
|
|
360
363
|
end
|
|
364
|
+
|
|
365
|
+
def extract_table(sql)
|
|
366
|
+
return nil unless sql
|
|
367
|
+
|
|
368
|
+
# Match FROM "table" or FROM table patterns
|
|
369
|
+
# Also handles INSERT INTO, UPDATE, DELETE FROM
|
|
370
|
+
case sql.to_s
|
|
371
|
+
when /\bFROM\s+["'`]?(\w+)["'`]?/i
|
|
372
|
+
Regexp.last_match(1)
|
|
373
|
+
when /\bINTO\s+["'`]?(\w+)["'`]?/i
|
|
374
|
+
Regexp.last_match(1)
|
|
375
|
+
when /\bUPDATE\s+["'`]?(\w+)["'`]?/i
|
|
376
|
+
Regexp.last_match(1)
|
|
377
|
+
when /\bJOIN\s+["'`]?(\w+)["'`]?/i
|
|
378
|
+
Regexp.last_match(1)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def extract_operation(sql)
|
|
383
|
+
return nil unless sql
|
|
384
|
+
|
|
385
|
+
case sql.to_s.strip.upcase
|
|
386
|
+
when /\ASELECT/i then "SELECT"
|
|
387
|
+
when /\AINSERT/i then "INSERT"
|
|
388
|
+
when /\AUPDATE/i then "UPDATE"
|
|
389
|
+
when /\ADELETE/i then "DELETE"
|
|
390
|
+
when /\ABEGIN/i, /\ACOMMIT/i, /\AROLLBACK/i then "TRANSACTION"
|
|
391
|
+
else "QUERY"
|
|
392
|
+
end
|
|
393
|
+
end
|
|
361
394
|
end
|
|
362
395
|
end
|
|
363
396
|
end
|
data/lib/brainzlab/pulse.rb
CHANGED
|
@@ -149,8 +149,7 @@ module BrainzLab
|
|
|
149
149
|
|
|
150
150
|
sql
|
|
151
151
|
.gsub(/\b\d+\b/, "?") # Replace numbers
|
|
152
|
-
.gsub(/'[^']*'/, "?") # Replace strings
|
|
153
|
-
.gsub(/"[^"]*"/, "?") # Replace double-quoted strings
|
|
152
|
+
.gsub(/'[^']*'/, "?") # Replace single-quoted strings
|
|
154
153
|
.gsub(/\$\d+/, "?") # Replace positional params
|
|
155
154
|
.gsub(/\/\*.*?\*\//, "") # Remove comments
|
|
156
155
|
.gsub(/\s+/, " ") # Normalize whitespace
|
|
@@ -7,6 +7,17 @@ module BrainzLab
|
|
|
7
7
|
require "generators/brainzlab/install/install_generator"
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# Load Vault secrets early, before configuration
|
|
11
|
+
# This allows secrets to be used in config files
|
|
12
|
+
initializer "brainzlab.load_vault_secrets", before: :load_environment_config do
|
|
13
|
+
if BrainzLab.configuration.vault_enabled && BrainzLab.configuration.vault_auto_load
|
|
14
|
+
BrainzLab.debug_log("[Vault] Auto-loading secrets into ENV...")
|
|
15
|
+
BrainzLab::Vault.load!(
|
|
16
|
+
provider_keys: BrainzLab.configuration.vault_load_provider_keys
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
10
21
|
initializer "brainzlab.configure_rails_initialization" do |app|
|
|
11
22
|
# Set defaults from Rails
|
|
12
23
|
BrainzLab.configure do |config|
|
|
@@ -20,11 +31,33 @@ module BrainzLab
|
|
|
20
31
|
|
|
21
32
|
# Add request context middleware (runs early)
|
|
22
33
|
app.middleware.insert_after ActionDispatch::RequestId, BrainzLab::Rails::Middleware
|
|
34
|
+
|
|
35
|
+
# Add DevTools middlewares if enabled
|
|
36
|
+
if BrainzLab.configuration.devtools_enabled
|
|
37
|
+
require_relative "../devtools"
|
|
38
|
+
|
|
39
|
+
# Asset server (handles /__brainzlab__/* requests)
|
|
40
|
+
app.middleware.insert_before ActionDispatch::Static, BrainzLab::DevTools::Middleware::AssetServer
|
|
41
|
+
|
|
42
|
+
# Database handler (handles /_brainzlab/devtools/database POST requests)
|
|
43
|
+
# Allows running migrations from the error page
|
|
44
|
+
app.middleware.insert_before ActionDispatch::Static, BrainzLab::DevTools::Middleware::DatabaseHandler
|
|
45
|
+
|
|
46
|
+
# Error page (catches exceptions and renders branded error page)
|
|
47
|
+
# Insert BEFORE DebugExceptions so we can intercept the HTML error page
|
|
48
|
+
# that DebugExceptions renders and replace it with our own
|
|
49
|
+
if defined?(ActionDispatch::DebugExceptions)
|
|
50
|
+
app.middleware.insert_before ActionDispatch::DebugExceptions, BrainzLab::DevTools::Middleware::ErrorPage
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Debug panel (injects panel into HTML responses)
|
|
54
|
+
app.middleware.use BrainzLab::DevTools::Middleware::DebugPanel
|
|
55
|
+
end
|
|
23
56
|
end
|
|
24
57
|
|
|
25
58
|
config.after_initialize do
|
|
26
59
|
# Set up custom log formatter
|
|
27
|
-
setup_log_formatter if BrainzLab.configuration.log_formatter_enabled
|
|
60
|
+
BrainzLab::Rails::Railtie.setup_log_formatter if BrainzLab.configuration.log_formatter_enabled
|
|
28
61
|
|
|
29
62
|
# Install instrumentation (HTTP tracking, etc.)
|
|
30
63
|
BrainzLab::Instrumentation.install!
|
|
@@ -569,9 +602,9 @@ module BrainzLab
|
|
|
569
602
|
end
|
|
570
603
|
end
|
|
571
604
|
|
|
572
|
-
# Sidekiq error handler
|
|
605
|
+
# Sidekiq error handler - Sidekiq 7.x+ requires 3 arguments
|
|
573
606
|
class SidekiqErrorHandler
|
|
574
|
-
def call(exception, context)
|
|
607
|
+
def call(exception, context, _config = nil)
|
|
575
608
|
BrainzLab::Reflex.capture(exception,
|
|
576
609
|
tags: { type: "sidekiq" },
|
|
577
610
|
extra: {
|
|
@@ -37,14 +37,31 @@ module BrainzLab
|
|
|
37
37
|
private
|
|
38
38
|
|
|
39
39
|
def should_provision?
|
|
40
|
+
if @config.debug
|
|
41
|
+
log_debug("Checking provision conditions:")
|
|
42
|
+
log_debug(" recall_auto_provision: #{@config.recall_auto_provision}")
|
|
43
|
+
log_debug(" app_name: '#{@config.app_name}'")
|
|
44
|
+
log_debug(" secret_key set: #{@config.secret_key.to_s.strip.length > 0}")
|
|
45
|
+
log_debug(" recall_master_key set: #{@config.recall_master_key.to_s.strip.length > 0}")
|
|
46
|
+
end
|
|
47
|
+
|
|
40
48
|
return false unless @config.recall_auto_provision
|
|
41
49
|
return false unless @config.app_name.to_s.strip.length > 0
|
|
42
50
|
return false if @config.secret_key.to_s.strip.length > 0
|
|
43
51
|
return false unless @config.recall_master_key.to_s.strip.length > 0
|
|
44
52
|
|
|
53
|
+
log_debug("Will provision Recall project") if @config.debug
|
|
45
54
|
true
|
|
46
55
|
end
|
|
47
56
|
|
|
57
|
+
def log_debug(message)
|
|
58
|
+
if @config.logger
|
|
59
|
+
@config.logger.info("[BrainzLab::Debug] #{message}")
|
|
60
|
+
else
|
|
61
|
+
puts "[BrainzLab::Debug] #{message}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
48
65
|
def provision_project
|
|
49
66
|
uri = URI.parse("#{@config.recall_url}/api/v1/projects/provision")
|
|
50
67
|
request = Net::HTTP::Post.new(uri)
|
data/lib/brainzlab/recall.rb
CHANGED
|
@@ -30,7 +30,7 @@ module BrainzLab
|
|
|
30
30
|
|
|
31
31
|
def log(level, message, **data)
|
|
32
32
|
config = BrainzLab.configuration
|
|
33
|
-
return unless config.
|
|
33
|
+
return unless config.recall_effectively_enabled?
|
|
34
34
|
|
|
35
35
|
# Auto-provision project on first log if app_name is configured
|
|
36
36
|
ensure_provisioned!
|
|
@@ -43,6 +43,11 @@ module BrainzLab
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def ensure_provisioned!
|
|
46
|
+
config = BrainzLab.configuration
|
|
47
|
+
if config.debug
|
|
48
|
+
puts "[BrainzLab::Debug] Recall.ensure_provisioned! called, @provisioned=#{@provisioned}"
|
|
49
|
+
end
|
|
50
|
+
|
|
46
51
|
return if @provisioned
|
|
47
52
|
|
|
48
53
|
@provisioned = true
|
data/lib/brainzlab/reflex.rb
CHANGED
|
@@ -77,7 +77,7 @@ module BrainzLab
|
|
|
77
77
|
private
|
|
78
78
|
|
|
79
79
|
def enabled?
|
|
80
|
-
BrainzLab.configuration.
|
|
80
|
+
BrainzLab.configuration.reflex_effectively_enabled?
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def capture_disabled?
|
|
@@ -245,7 +245,7 @@ module BrainzLab
|
|
|
245
245
|
# - "path/to/file.rb:42:in `method_name'" (backtick + single quote)
|
|
246
246
|
# - "path/to/file.rb:42:in 'method_name'" (single quotes)
|
|
247
247
|
# - "path/to/file.rb:42" (no method)
|
|
248
|
-
if line =~ /\A(.+):(\d+):in [`'](
|
|
248
|
+
if line =~ /\A(.+):(\d+):in [`']([^']+)'?\z/
|
|
249
249
|
{
|
|
250
250
|
file: $1,
|
|
251
251
|
line: $2.to_i,
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module BrainzLab
|
|
8
|
+
module Sentinel
|
|
9
|
+
class Client
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
@base_url = config.sentinel_url || "https://sentinel.brainzlab.ai"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List all registered hosts
|
|
16
|
+
def list_hosts(status: nil, page: 1, per_page: 50)
|
|
17
|
+
params = { page: page, per_page: per_page }
|
|
18
|
+
params[:status] = status if status
|
|
19
|
+
|
|
20
|
+
response = request(:get, "/api/v1/hosts", params: params)
|
|
21
|
+
|
|
22
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
23
|
+
|
|
24
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
25
|
+
data[:hosts] || []
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
log_error("list_hosts", e)
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get host details
|
|
32
|
+
def get_host(host_id)
|
|
33
|
+
response = request(:get, "/api/v1/hosts/#{host_id}")
|
|
34
|
+
|
|
35
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
36
|
+
|
|
37
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
log_error("get_host", e)
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get host metrics
|
|
44
|
+
# @param host_id [String] Host ID
|
|
45
|
+
# @param period [String] Time period (1h, 6h, 24h, 7d, 30d)
|
|
46
|
+
# @param metrics [Array<String>] Specific metrics to fetch (cpu, memory, disk, network)
|
|
47
|
+
def get_metrics(host_id, period: "1h", metrics: nil)
|
|
48
|
+
params = { period: period }
|
|
49
|
+
params[:metrics] = metrics.join(",") if metrics
|
|
50
|
+
|
|
51
|
+
response = request(:get, "/api/v1/hosts/#{host_id}/metrics", params: params)
|
|
52
|
+
|
|
53
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
54
|
+
|
|
55
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
log_error("get_metrics", e)
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get top processes for a host
|
|
62
|
+
def get_processes(host_id, sort_by: "cpu", limit: 20)
|
|
63
|
+
params = { sort_by: sort_by, limit: limit }
|
|
64
|
+
|
|
65
|
+
response = request(:get, "/api/v1/hosts/#{host_id}/processes", params: params)
|
|
66
|
+
|
|
67
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
68
|
+
|
|
69
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
70
|
+
data[:processes] || []
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
log_error("get_processes", e)
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# List all containers
|
|
77
|
+
def list_containers(host_id: nil, status: nil)
|
|
78
|
+
params = {}
|
|
79
|
+
params[:host_id] = host_id if host_id
|
|
80
|
+
params[:status] = status if status
|
|
81
|
+
|
|
82
|
+
response = request(:get, "/api/v1/containers", params: params)
|
|
83
|
+
|
|
84
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
85
|
+
|
|
86
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
87
|
+
data[:containers] || []
|
|
88
|
+
rescue StandardError => e
|
|
89
|
+
log_error("list_containers", e)
|
|
90
|
+
[]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get container details
|
|
94
|
+
def get_container(container_id)
|
|
95
|
+
response = request(:get, "/api/v1/containers/#{container_id}")
|
|
96
|
+
|
|
97
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
98
|
+
|
|
99
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
log_error("get_container", e)
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Get container metrics
|
|
106
|
+
def get_container_metrics(container_id, period: "1h")
|
|
107
|
+
params = { period: period }
|
|
108
|
+
|
|
109
|
+
response = request(:get, "/api/v1/containers/#{container_id}/metrics", params: params)
|
|
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_container_metrics", e)
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Get alerts for a host
|
|
120
|
+
def get_alerts(host_id: nil, status: nil, severity: nil)
|
|
121
|
+
params = {}
|
|
122
|
+
params[:host_id] = host_id if host_id
|
|
123
|
+
params[:status] = status if status
|
|
124
|
+
params[:severity] = severity if severity
|
|
125
|
+
|
|
126
|
+
response = request(:get, "/api/v1/alerts", params: params)
|
|
127
|
+
|
|
128
|
+
return [] unless response.is_a?(Net::HTTPSuccess)
|
|
129
|
+
|
|
130
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
|
131
|
+
data[:alerts] || []
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
log_error("get_alerts", e)
|
|
134
|
+
[]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Report metrics from agent (internal use)
|
|
138
|
+
def report_metrics(host_id:, metrics:, timestamp: nil)
|
|
139
|
+
response = request(
|
|
140
|
+
:post,
|
|
141
|
+
"/internal/agent/report",
|
|
142
|
+
body: {
|
|
143
|
+
host_id: host_id,
|
|
144
|
+
metrics: metrics,
|
|
145
|
+
timestamp: timestamp || Time.now.utc.iso8601
|
|
146
|
+
},
|
|
147
|
+
use_agent_key: true
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
|
|
151
|
+
rescue StandardError => e
|
|
152
|
+
log_error("report_metrics", e)
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def provision(project_id:, app_name:)
|
|
157
|
+
response = request(
|
|
158
|
+
:post,
|
|
159
|
+
"/api/v1/projects/provision",
|
|
160
|
+
body: { project_id: project_id, app_name: app_name },
|
|
161
|
+
use_service_key: true
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
|
|
165
|
+
rescue StandardError => e
|
|
166
|
+
log_error("provision", e)
|
|
167
|
+
false
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false, use_agent_key: false)
|
|
173
|
+
uri = URI.parse("#{@base_url}#{path}")
|
|
174
|
+
|
|
175
|
+
if params
|
|
176
|
+
uri.query = URI.encode_www_form(params)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
180
|
+
http.use_ssl = uri.scheme == "https"
|
|
181
|
+
http.open_timeout = 10
|
|
182
|
+
http.read_timeout = 30
|
|
183
|
+
|
|
184
|
+
request = case method
|
|
185
|
+
when :get
|
|
186
|
+
Net::HTTP::Get.new(uri)
|
|
187
|
+
when :post
|
|
188
|
+
Net::HTTP::Post.new(uri)
|
|
189
|
+
when :put
|
|
190
|
+
Net::HTTP::Put.new(uri)
|
|
191
|
+
when :delete
|
|
192
|
+
Net::HTTP::Delete.new(uri)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
request["Content-Type"] = "application/json"
|
|
196
|
+
request["Accept"] = "application/json"
|
|
197
|
+
|
|
198
|
+
if use_service_key
|
|
199
|
+
request["X-Service-Key"] = @config.sentinel_master_key || @config.secret_key
|
|
200
|
+
elsif use_agent_key
|
|
201
|
+
request["X-Agent-Key"] = @config.sentinel_agent_key || @config.sentinel_api_key || @config.secret_key
|
|
202
|
+
else
|
|
203
|
+
auth_key = @config.sentinel_api_key || @config.secret_key
|
|
204
|
+
request["Authorization"] = "Bearer #{auth_key}" if auth_key
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
headers.each { |k, v| request[k] = v }
|
|
208
|
+
request.body = body.to_json if body
|
|
209
|
+
|
|
210
|
+
http.request(request)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def log_error(operation, error)
|
|
214
|
+
BrainzLab.debug_log("[Sentinel::Client] #{operation} failed: #{error.message}")
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|