brainzlab 0.1.12 → 0.1.20
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 +7 -0
- data/lib/brainzlab/configuration.rb +30 -2
- data/lib/brainzlab/devtools/middleware/asset_server.rb +1 -0
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +1 -0
- data/lib/brainzlab/devtools/middleware/error_page.rb +56 -8
- data/lib/brainzlab/flux/buffer.rb +2 -2
- data/lib/brainzlab/flux/client.rb +2 -2
- data/lib/brainzlab/instrumentation/active_support_cache.rb +60 -30
- data/lib/brainzlab/instrumentation/net_http.rb +21 -16
- data/lib/brainzlab/instrumentation.rb +6 -0
- data/lib/brainzlab/pulse.rb +7 -1
- data/lib/brainzlab/rails/log_formatter.rb +1 -1
- data/lib/brainzlab/rails/railtie.rb +18 -3
- data/lib/brainzlab/recall/buffer.rb +3 -1
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab.rb +25 -0
- data/lib/fluyenta-ruby.rb +3 -0
- metadata +11 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac1922e302a243a61b38d955da04782ae4e66c11908bea728a39b6ad3c68634e
|
|
4
|
+
data.tar.gz: 748d7f686bf204d0e0ef9322fb445dd75b62c13404472758631e976cc135c295
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee4bdba72d0fed3db7489bf0335de8dd4b8139a941eb381b4a35a3425ca0fa7755f3702869ac93405a5cd0e9def39174ced81eb559c3de99b6970e353c99f977
|
|
7
|
+
data.tar.gz: 8aec3a840b93948c73ad1e1e518182b316b4a8a29c20c8b5a6819a88c5f12e647d9c576dc865e04991ac0ea4976e2536d21751b1291ebbaf5594713a9317b2b7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.13] - 2026-02-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Rails LogFormatter** - Fix `TypeError: wrong element type Hash at 0 (expected array)` in `format_params_toml`
|
|
10
|
+
- `hash_like?` incorrectly matched Arrays (they respond to `to_h` and `each`), causing `Array#to_h` to fail when params contained arrays of hashes
|
|
11
|
+
|
|
5
12
|
## [0.1.1] - 2025-12-23
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -10,7 +10,8 @@ module BrainzLab
|
|
|
10
10
|
# mode has a custom setter with validation
|
|
11
11
|
attr_reader :mode
|
|
12
12
|
|
|
13
|
-
attr_accessor :
|
|
13
|
+
attr_accessor :enabled,
|
|
14
|
+
:secret_key,
|
|
14
15
|
:environment,
|
|
15
16
|
:service,
|
|
16
17
|
:host,
|
|
@@ -169,6 +170,9 @@ module BrainzLab
|
|
|
169
170
|
}.freeze
|
|
170
171
|
|
|
171
172
|
def initialize
|
|
173
|
+
# SDK enabled flag — set BRAINZLAB_SDK_ENABLED=false to completely disable
|
|
174
|
+
@enabled = ENV.fetch('BRAINZLAB_SDK_ENABLED', 'true') != 'false'
|
|
175
|
+
|
|
172
176
|
# Authentication
|
|
173
177
|
@secret_key = ENV.fetch('BRAINZLAB_SECRET_KEY', nil)
|
|
174
178
|
|
|
@@ -422,7 +426,11 @@ module BrainzLab
|
|
|
422
426
|
end
|
|
423
427
|
|
|
424
428
|
def valid?
|
|
425
|
-
!@secret_key.nil? && !@secret_key.empty?
|
|
429
|
+
@enabled && !@secret_key.nil? && !@secret_key.empty?
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def enabled?
|
|
433
|
+
@enabled == true
|
|
426
434
|
end
|
|
427
435
|
|
|
428
436
|
def reflex_valid?
|
|
@@ -537,8 +545,24 @@ module BrainzLab
|
|
|
537
545
|
@debug == true
|
|
538
546
|
end
|
|
539
547
|
|
|
548
|
+
# Returns hostnames of all configured SDK service URLs
|
|
549
|
+
# Used by Net::HTTP instrumentation to skip tracking SDK's own HTTP calls
|
|
550
|
+
def sdk_service_hosts
|
|
551
|
+
@sdk_service_hosts ||= begin
|
|
552
|
+
urls = [
|
|
553
|
+
@recall_url, @reflex_url, @pulse_url, @flux_url,
|
|
554
|
+
@signal_url, @vault_url, @vision_url, @cortex_url,
|
|
555
|
+
@beacon_url, @nerve_url, @dendrite_url, @sentinel_url,
|
|
556
|
+
@synapse_url
|
|
557
|
+
].compact
|
|
558
|
+
|
|
559
|
+
urls.filter_map { |url| URI.parse(url).host rescue nil }.uniq
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
540
563
|
# Check if recall is effectively enabled (considering self-tracking)
|
|
541
564
|
def recall_effectively_enabled?
|
|
565
|
+
return false unless @enabled
|
|
542
566
|
return false unless @recall_enabled
|
|
543
567
|
return true unless @disable_self_tracking
|
|
544
568
|
|
|
@@ -549,6 +573,7 @@ module BrainzLab
|
|
|
549
573
|
|
|
550
574
|
# Check if reflex is effectively enabled (considering self-tracking)
|
|
551
575
|
def reflex_effectively_enabled?
|
|
576
|
+
return false unless @enabled
|
|
552
577
|
return false unless @reflex_enabled
|
|
553
578
|
return true unless @disable_self_tracking
|
|
554
579
|
|
|
@@ -559,6 +584,7 @@ module BrainzLab
|
|
|
559
584
|
|
|
560
585
|
# Check if pulse is effectively enabled (considering self-tracking)
|
|
561
586
|
def pulse_effectively_enabled?
|
|
587
|
+
return false unless @enabled
|
|
562
588
|
return false unless @pulse_enabled
|
|
563
589
|
return true unless @disable_self_tracking
|
|
564
590
|
|
|
@@ -569,6 +595,7 @@ module BrainzLab
|
|
|
569
595
|
|
|
570
596
|
# Check if flux is effectively enabled (considering self-tracking)
|
|
571
597
|
def flux_effectively_enabled?
|
|
598
|
+
return false unless @enabled
|
|
572
599
|
return false unless @flux_enabled
|
|
573
600
|
return true unless @disable_self_tracking
|
|
574
601
|
|
|
@@ -579,6 +606,7 @@ module BrainzLab
|
|
|
579
606
|
|
|
580
607
|
# Check if signal is effectively enabled (considering self-tracking)
|
|
581
608
|
def signal_effectively_enabled?
|
|
609
|
+
return false unless @enabled
|
|
582
610
|
return false unless @signal_enabled
|
|
583
611
|
return true unless @disable_self_tracking
|
|
584
612
|
|
|
@@ -38,6 +38,7 @@ module BrainzLab
|
|
|
38
38
|
return false unless DevTools.debug_panel_enabled?
|
|
39
39
|
return false unless DevTools.allowed_environment?
|
|
40
40
|
return false unless DevTools.allowed_ip?(extract_ip(env))
|
|
41
|
+
return false if env['REQUEST_METHOD'] == 'OPTIONS'
|
|
41
42
|
return false if asset_request?(env['PATH_INFO'])
|
|
42
43
|
return false if devtools_asset_request?(env['PATH_INFO'])
|
|
43
44
|
return false if turbo_stream_request?(env)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
module BrainzLab
|
|
4
6
|
module DevTools
|
|
5
7
|
module Middleware
|
|
@@ -10,29 +12,33 @@ module BrainzLab
|
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def call(env)
|
|
15
|
+
return @app.call(env) if env['REQUEST_METHOD'] == 'OPTIONS'
|
|
13
16
|
return @app.call(env) unless should_handle?(env)
|
|
14
17
|
|
|
15
18
|
begin
|
|
16
19
|
status, headers, body = @app.call(env)
|
|
17
20
|
|
|
18
21
|
# Check if this is an error response that we should intercept
|
|
19
|
-
if status >= 400 && html_response?(headers) && !json_request?(env)
|
|
22
|
+
if status >= 400 && html_response?(headers) && !json_request?(env) && !api_path?(env)
|
|
20
23
|
# Check if this looks like Rails' default error page
|
|
21
24
|
body_content = collect_body(body)
|
|
22
25
|
if body_content.include?('Action Controller: Exception caught') || body_content.include?('background: #C00')
|
|
23
26
|
# Extract exception info from the page
|
|
24
27
|
exception_info = extract_exception_from_html(body_content)
|
|
25
28
|
if exception_info
|
|
26
|
-
data = collect_debug_data_from_info(env, exception_info)
|
|
27
|
-
return render_error_page_from_info(exception_info, data)
|
|
29
|
+
data = collect_debug_data_from_info(env, exception_info, status)
|
|
30
|
+
return render_error_page_from_info(exception_info, data, status)
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
[status, headers, body]
|
|
33
36
|
rescue Exception => e
|
|
34
|
-
#
|
|
35
|
-
|
|
37
|
+
# For JSON/API requests, return a proper JSON error response
|
|
38
|
+
if json_request?(env) || api_path?(env)
|
|
39
|
+
capture_to_reflex(e)
|
|
40
|
+
return json_error_response(e)
|
|
41
|
+
end
|
|
36
42
|
|
|
37
43
|
# Still capture to Reflex if available
|
|
38
44
|
capture_to_reflex(e)
|
|
@@ -87,7 +93,7 @@ module BrainzLab
|
|
|
87
93
|
.gsub(' ', ' ')
|
|
88
94
|
end
|
|
89
95
|
|
|
90
|
-
def collect_debug_data_from_info(env, info)
|
|
96
|
+
def collect_debug_data_from_info(env, info, status = 500)
|
|
91
97
|
context = defined?(BrainzLab::Context) ? BrainzLab::Context.current : nil
|
|
92
98
|
collector_data = Data::Collector.get_request_data
|
|
93
99
|
|
|
@@ -113,7 +119,7 @@ module BrainzLab
|
|
|
113
119
|
}
|
|
114
120
|
end
|
|
115
121
|
|
|
116
|
-
def render_error_page_from_info(info, data)
|
|
122
|
+
def render_error_page_from_info(info, data, status = 500)
|
|
117
123
|
# Create a simple exception-like object
|
|
118
124
|
exception = StandardError.new(info[:message])
|
|
119
125
|
exception.define_singleton_method(:class) do
|
|
@@ -126,7 +132,7 @@ module BrainzLab
|
|
|
126
132
|
html = @renderer.render(exception, data)
|
|
127
133
|
|
|
128
134
|
[
|
|
129
|
-
|
|
135
|
+
status,
|
|
130
136
|
{
|
|
131
137
|
'Content-Type' => 'text/html; charset=utf-8',
|
|
132
138
|
'Content-Length' => html.bytesize.to_s,
|
|
@@ -169,6 +175,48 @@ module BrainzLab
|
|
|
169
175
|
env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
|
|
170
176
|
end
|
|
171
177
|
|
|
178
|
+
def api_path?(env)
|
|
179
|
+
path = env['PATH_INFO'] || ''
|
|
180
|
+
path.start_with?('/api/')
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def exception_to_status(exception)
|
|
184
|
+
case exception.class.name
|
|
185
|
+
when 'ActionController::RoutingError', 'AbstractController::ActionNotFound'
|
|
186
|
+
404
|
|
187
|
+
when 'ActionController::MethodNotAllowed'
|
|
188
|
+
405
|
|
189
|
+
when 'ActionController::BadRequest', 'ActionDispatch::Http::Parameters::ParseError'
|
|
190
|
+
400
|
|
191
|
+
when 'ActionController::UnknownFormat'
|
|
192
|
+
406
|
|
193
|
+
else
|
|
194
|
+
500
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def json_error_response(exception)
|
|
199
|
+
status_code = exception_to_status(exception)
|
|
200
|
+
message = case status_code
|
|
201
|
+
when 400 then 'Bad request'
|
|
202
|
+
when 404 then 'Not found'
|
|
203
|
+
when 405 then 'Method not allowed'
|
|
204
|
+
when 406 then 'Not acceptable'
|
|
205
|
+
else 'Internal server error'
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
body = JSON.generate({ error: message })
|
|
209
|
+
[
|
|
210
|
+
status_code,
|
|
211
|
+
{
|
|
212
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
|
213
|
+
'Content-Length' => body.bytesize.to_s,
|
|
214
|
+
'X-Content-Type-Options' => 'nosniff'
|
|
215
|
+
},
|
|
216
|
+
[body]
|
|
217
|
+
]
|
|
218
|
+
end
|
|
219
|
+
|
|
172
220
|
def capture_to_reflex(exception)
|
|
173
221
|
return unless defined?(BrainzLab::Reflex)
|
|
174
222
|
|
|
@@ -76,7 +76,7 @@ module BrainzLab
|
|
|
76
76
|
|
|
77
77
|
@client.send_batch(events: events, metrics: metrics)
|
|
78
78
|
rescue StandardError => e
|
|
79
|
-
BrainzLab.
|
|
79
|
+
BrainzLab.debug_log("[Flux] Batch send failed: #{e.message}")
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def start_flush_thread
|
|
@@ -86,7 +86,7 @@ module BrainzLab
|
|
|
86
86
|
begin
|
|
87
87
|
flush! if size.positive?
|
|
88
88
|
rescue StandardError => e
|
|
89
|
-
BrainzLab.
|
|
89
|
+
BrainzLab.debug_log("[Flux] Flush thread error: #{e.message}")
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
end
|
|
@@ -48,11 +48,11 @@ module BrainzLab
|
|
|
48
48
|
|
|
49
49
|
response = http.request(request)
|
|
50
50
|
|
|
51
|
-
BrainzLab.
|
|
51
|
+
BrainzLab.debug_log("[Flux] Request failed: #{response.code} - #{response.body}") unless response.is_a?(Net::HTTPSuccess)
|
|
52
52
|
|
|
53
53
|
response
|
|
54
54
|
rescue StandardError => e
|
|
55
|
-
BrainzLab.
|
|
55
|
+
BrainzLab.debug_log("[Flux] Request error: #{e.message}")
|
|
56
56
|
nil
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -43,8 +43,10 @@ module BrainzLab
|
|
|
43
43
|
# ============================================
|
|
44
44
|
def install_cache_read_subscriber!
|
|
45
45
|
ActiveSupport::Notifications.subscribe('cache_read.active_support') do |*args|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
BrainzLab.with_instrumentation_guard do
|
|
47
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
48
|
+
handle_cache_read(event)
|
|
49
|
+
end
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
|
|
@@ -76,8 +78,10 @@ module BrainzLab
|
|
|
76
78
|
# ============================================
|
|
77
79
|
def install_cache_read_multi_subscriber!
|
|
78
80
|
ActiveSupport::Notifications.subscribe('cache_read_multi.active_support') do |*args|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
BrainzLab.with_instrumentation_guard do
|
|
82
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
83
|
+
handle_cache_read_multi(event)
|
|
84
|
+
end
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
|
|
@@ -122,8 +126,10 @@ module BrainzLab
|
|
|
122
126
|
# ============================================
|
|
123
127
|
def install_cache_write_subscriber!
|
|
124
128
|
ActiveSupport::Notifications.subscribe('cache_write.active_support') do |*args|
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
BrainzLab.with_instrumentation_guard do
|
|
130
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
131
|
+
handle_cache_write(event)
|
|
132
|
+
end
|
|
127
133
|
end
|
|
128
134
|
end
|
|
129
135
|
|
|
@@ -150,8 +156,10 @@ module BrainzLab
|
|
|
150
156
|
# ============================================
|
|
151
157
|
def install_cache_write_multi_subscriber!
|
|
152
158
|
ActiveSupport::Notifications.subscribe('cache_write_multi.active_support') do |*args|
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
BrainzLab.with_instrumentation_guard do
|
|
160
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
161
|
+
handle_cache_write_multi(event)
|
|
162
|
+
end
|
|
155
163
|
end
|
|
156
164
|
end
|
|
157
165
|
|
|
@@ -186,8 +194,10 @@ module BrainzLab
|
|
|
186
194
|
# ============================================
|
|
187
195
|
def install_cache_delete_subscriber!
|
|
188
196
|
ActiveSupport::Notifications.subscribe('cache_delete.active_support') do |*args|
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
BrainzLab.with_instrumentation_guard do
|
|
198
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
199
|
+
handle_cache_delete(event)
|
|
200
|
+
end
|
|
191
201
|
end
|
|
192
202
|
end
|
|
193
203
|
|
|
@@ -211,8 +221,10 @@ module BrainzLab
|
|
|
211
221
|
# ============================================
|
|
212
222
|
def install_cache_exist_subscriber!
|
|
213
223
|
ActiveSupport::Notifications.subscribe('cache_exist?.active_support') do |*args|
|
|
214
|
-
|
|
215
|
-
|
|
224
|
+
BrainzLab.with_instrumentation_guard do
|
|
225
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
226
|
+
handle_cache_exist(event)
|
|
227
|
+
end
|
|
216
228
|
end
|
|
217
229
|
end
|
|
218
230
|
|
|
@@ -236,8 +248,10 @@ module BrainzLab
|
|
|
236
248
|
# ============================================
|
|
237
249
|
def install_cache_fetch_hit_subscriber!
|
|
238
250
|
ActiveSupport::Notifications.subscribe('cache_fetch_hit.active_support') do |*args|
|
|
239
|
-
|
|
240
|
-
|
|
251
|
+
BrainzLab.with_instrumentation_guard do
|
|
252
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
253
|
+
handle_cache_fetch_hit(event)
|
|
254
|
+
end
|
|
241
255
|
end
|
|
242
256
|
end
|
|
243
257
|
|
|
@@ -264,8 +278,10 @@ module BrainzLab
|
|
|
264
278
|
# ============================================
|
|
265
279
|
def install_cache_generate_subscriber!
|
|
266
280
|
ActiveSupport::Notifications.subscribe('cache_generate.active_support') do |*args|
|
|
267
|
-
|
|
268
|
-
|
|
281
|
+
BrainzLab.with_instrumentation_guard do
|
|
282
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
283
|
+
handle_cache_generate(event)
|
|
284
|
+
end
|
|
269
285
|
end
|
|
270
286
|
end
|
|
271
287
|
|
|
@@ -312,8 +328,10 @@ module BrainzLab
|
|
|
312
328
|
# ============================================
|
|
313
329
|
def install_cache_increment_subscriber!
|
|
314
330
|
ActiveSupport::Notifications.subscribe('cache_increment.active_support') do |*args|
|
|
315
|
-
|
|
316
|
-
|
|
331
|
+
BrainzLab.with_instrumentation_guard do
|
|
332
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
333
|
+
handle_cache_increment(event)
|
|
334
|
+
end
|
|
317
335
|
end
|
|
318
336
|
end
|
|
319
337
|
|
|
@@ -349,8 +367,10 @@ module BrainzLab
|
|
|
349
367
|
# ============================================
|
|
350
368
|
def install_cache_decrement_subscriber!
|
|
351
369
|
ActiveSupport::Notifications.subscribe('cache_decrement.active_support') do |*args|
|
|
352
|
-
|
|
353
|
-
|
|
370
|
+
BrainzLab.with_instrumentation_guard do
|
|
371
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
372
|
+
handle_cache_decrement(event)
|
|
373
|
+
end
|
|
354
374
|
end
|
|
355
375
|
end
|
|
356
376
|
|
|
@@ -386,8 +406,10 @@ module BrainzLab
|
|
|
386
406
|
# ============================================
|
|
387
407
|
def install_cache_delete_multi_subscriber!
|
|
388
408
|
ActiveSupport::Notifications.subscribe('cache_delete_multi.active_support') do |*args|
|
|
389
|
-
|
|
390
|
-
|
|
409
|
+
BrainzLab.with_instrumentation_guard do
|
|
410
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
411
|
+
handle_cache_delete_multi(event)
|
|
412
|
+
end
|
|
391
413
|
end
|
|
392
414
|
end
|
|
393
415
|
|
|
@@ -422,8 +444,10 @@ module BrainzLab
|
|
|
422
444
|
# ============================================
|
|
423
445
|
def install_cache_delete_matched_subscriber!
|
|
424
446
|
ActiveSupport::Notifications.subscribe('cache_delete_matched.active_support') do |*args|
|
|
425
|
-
|
|
426
|
-
|
|
447
|
+
BrainzLab.with_instrumentation_guard do
|
|
448
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
449
|
+
handle_cache_delete_matched(event)
|
|
450
|
+
end
|
|
427
451
|
end
|
|
428
452
|
end
|
|
429
453
|
|
|
@@ -466,8 +490,10 @@ module BrainzLab
|
|
|
466
490
|
# ============================================
|
|
467
491
|
def install_cache_cleanup_subscriber!
|
|
468
492
|
ActiveSupport::Notifications.subscribe('cache_cleanup.active_support') do |*args|
|
|
469
|
-
|
|
470
|
-
|
|
493
|
+
BrainzLab.with_instrumentation_guard do
|
|
494
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
495
|
+
handle_cache_cleanup(event)
|
|
496
|
+
end
|
|
471
497
|
end
|
|
472
498
|
end
|
|
473
499
|
|
|
@@ -502,8 +528,10 @@ module BrainzLab
|
|
|
502
528
|
# ============================================
|
|
503
529
|
def install_cache_prune_subscriber!
|
|
504
530
|
ActiveSupport::Notifications.subscribe('cache_prune.active_support') do |*args|
|
|
505
|
-
|
|
506
|
-
|
|
531
|
+
BrainzLab.with_instrumentation_guard do
|
|
532
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
533
|
+
handle_cache_prune(event)
|
|
534
|
+
end
|
|
507
535
|
end
|
|
508
536
|
end
|
|
509
537
|
|
|
@@ -552,8 +580,10 @@ module BrainzLab
|
|
|
552
580
|
# ============================================
|
|
553
581
|
def install_message_serializer_fallback_subscriber!
|
|
554
582
|
ActiveSupport::Notifications.subscribe('message_serializer_fallback.active_support') do |*args|
|
|
555
|
-
|
|
556
|
-
|
|
583
|
+
BrainzLab.with_instrumentation_guard do
|
|
584
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
585
|
+
handle_message_serializer_fallback(event)
|
|
586
|
+
end
|
|
557
587
|
end
|
|
558
588
|
end
|
|
559
589
|
|
|
@@ -63,6 +63,9 @@ module BrainzLab
|
|
|
63
63
|
|
|
64
64
|
def should_track?
|
|
65
65
|
return false unless BrainzLab.configuration.instrument_http
|
|
66
|
+
# Skip tracking SDK's own HTTP calls to its service endpoints
|
|
67
|
+
# to prevent recursive cascading (SDK HTTP → track → Recall.debug → buffer → flush → SDK HTTP → ...)
|
|
68
|
+
return false if BrainzLab.configuration.sdk_service_hosts.include?(address)
|
|
66
69
|
|
|
67
70
|
ignore_hosts = BrainzLab.configuration.http_ignore_hosts || []
|
|
68
71
|
!ignore_hosts.include?(address)
|
|
@@ -82,22 +85,24 @@ module BrainzLab
|
|
|
82
85
|
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
83
86
|
level = error || (status && status >= 400) ? :error : :info
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
BrainzLab
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
BrainzLab
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
BrainzLab.with_instrumentation_guard do
|
|
89
|
+
# Add breadcrumb for Reflex (in-memory, safe)
|
|
90
|
+
if BrainzLab.configuration.reflex_enabled
|
|
91
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
92
|
+
"#{method} #{url}",
|
|
93
|
+
category: 'http',
|
|
94
|
+
level: level,
|
|
95
|
+
data: { method: method, url: url, status_code: status, duration_ms: duration_ms, error: error }.compact
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Log to Recall at debug level (skipped if already instrumenting)
|
|
100
|
+
if BrainzLab.configuration.recall_enabled
|
|
101
|
+
BrainzLab::Recall.debug(
|
|
102
|
+
"HTTP #{method} #{url} -> #{status || 'ERROR'}",
|
|
103
|
+
method: method, url: url, status_code: status, duration_ms: duration_ms, error: error
|
|
104
|
+
)
|
|
105
|
+
end
|
|
101
106
|
end
|
|
102
107
|
rescue StandardError => e
|
|
103
108
|
# Don't let instrumentation errors crash the app
|
|
@@ -6,6 +6,12 @@ module BrainzLab
|
|
|
6
6
|
def install!
|
|
7
7
|
config = BrainzLab.configuration
|
|
8
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
|
+
|
|
9
15
|
# Skip Rails-specific instrumentation if brainzlab-rails gem is handling it
|
|
10
16
|
# This prevents double-tracking of events
|
|
11
17
|
if config.rails_instrumentation_handled_externally
|
data/lib/brainzlab/pulse.rb
CHANGED
|
@@ -82,7 +82,13 @@ module BrainzLab
|
|
|
82
82
|
ensure_provisioned!
|
|
83
83
|
return unless BrainzLab.configuration.pulse_valid?
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
if BrainzLab.instrumenting?
|
|
86
|
+
# During instrumentation, send in background thread to avoid
|
|
87
|
+
# blocking the host app with synchronous HTTP
|
|
88
|
+
Thread.new { client.send_metric(payload) }
|
|
89
|
+
else
|
|
90
|
+
client.send_metric(payload)
|
|
91
|
+
end
|
|
86
92
|
end
|
|
87
93
|
|
|
88
94
|
# Convenience methods for metrics
|
|
@@ -641,7 +641,7 @@ module BrainzLab
|
|
|
641
641
|
end
|
|
642
642
|
|
|
643
643
|
def hash_like?(obj)
|
|
644
|
-
obj.is_a?(Hash) || (obj.respond_to?(:to_h) && obj.respond_to?(:each))
|
|
644
|
+
obj.is_a?(Hash) || (!obj.is_a?(Array) && obj.respond_to?(:to_h) && obj.respond_to?(:each))
|
|
645
645
|
end
|
|
646
646
|
|
|
647
647
|
def format_params_toml(params, prefix = '', depth = 0)
|
|
@@ -55,6 +55,12 @@ module BrainzLab
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
config.after_initialize do
|
|
58
|
+
# Skip all SDK initialization if disabled
|
|
59
|
+
unless BrainzLab.configuration.enabled?
|
|
60
|
+
BrainzLab.debug_log('[Railtie] SDK disabled via BRAINZLAB_SDK_ENABLED=false, skipping initialization')
|
|
61
|
+
next
|
|
62
|
+
end
|
|
63
|
+
|
|
58
64
|
# Set up custom log formatter
|
|
59
65
|
BrainzLab::Rails::Railtie.setup_log_formatter if BrainzLab.configuration.log_formatter_enabled
|
|
60
66
|
|
|
@@ -114,8 +120,13 @@ module BrainzLab
|
|
|
114
120
|
end
|
|
115
121
|
|
|
116
122
|
def silence_rails_logging
|
|
117
|
-
# Create a null logger that discards all output
|
|
118
|
-
|
|
123
|
+
# Create a null logger that discards all output.
|
|
124
|
+
# Use ActiveSupport::Logger (not a plain ::Logger) so the logger
|
|
125
|
+
# retains LoggerSilence#silence — SolidQueue's poller calls
|
|
126
|
+
# `ActiveRecord::Base.logger.silence { ... }` every cycle, and a
|
|
127
|
+
# plain Logger lacks `silence` (NoMethodError → workers crash-loop).
|
|
128
|
+
logger_class = defined?(ActiveSupport::Logger) ? ActiveSupport::Logger : Logger
|
|
129
|
+
null_logger = logger_class.new(File::NULL)
|
|
119
130
|
null_logger.level = Logger::FATAL
|
|
120
131
|
|
|
121
132
|
# Silence ActiveRecord SQL logging
|
|
@@ -217,7 +228,11 @@ module BrainzLab
|
|
|
217
228
|
context.request_method = request.request_method
|
|
218
229
|
context.request_path = request.path
|
|
219
230
|
context.request_url = request.url
|
|
220
|
-
context.request_params =
|
|
231
|
+
context.request_params = begin
|
|
232
|
+
filter_params(request.params.to_h)
|
|
233
|
+
rescue ActionDispatch::Http::Parameters::ParseError
|
|
234
|
+
{}
|
|
235
|
+
end
|
|
221
236
|
context.request_headers = extract_headers(env)
|
|
222
237
|
|
|
223
238
|
# Add breadcrumb for request start
|
|
@@ -19,7 +19,9 @@ module BrainzLab
|
|
|
19
19
|
|
|
20
20
|
def push(log_entry)
|
|
21
21
|
@buffer.push(log_entry)
|
|
22
|
-
flush
|
|
22
|
+
# Skip synchronous flush during instrumentation to avoid blocking the host app.
|
|
23
|
+
# The background flush thread will send these entries within recall_flush_interval seconds.
|
|
24
|
+
flush if @buffer.size >= @config.recall_buffer_size && !BrainzLab.instrumenting?
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def flush
|
data/lib/brainzlab/version.rb
CHANGED
data/lib/brainzlab.rb
CHANGED
|
@@ -35,7 +35,32 @@ require_relative 'brainzlab/utilities'
|
|
|
35
35
|
require_relative 'brainzlab/development'
|
|
36
36
|
|
|
37
37
|
module BrainzLab
|
|
38
|
+
# Thread-local re-entrancy guard for instrumentation.
|
|
39
|
+
# When true, SDK operations that would make HTTP calls are skipped
|
|
40
|
+
# to prevent recursive instrumentation from blocking the host app.
|
|
41
|
+
INSTRUMENTING_KEY = :brainzlab_instrumenting
|
|
42
|
+
|
|
38
43
|
class << self
|
|
44
|
+
# Returns true when inside an instrumentation handler.
|
|
45
|
+
# Used by Recall.log, Pulse.record_metric, etc. to skip HTTP calls
|
|
46
|
+
# that would block the host app during notification callbacks.
|
|
47
|
+
def instrumenting?
|
|
48
|
+
Thread.current[INSTRUMENTING_KEY] == true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Executes a block within the instrumentation guard.
|
|
52
|
+
# Prevents recursive/cascading SDK HTTP calls from instrumentation handlers.
|
|
53
|
+
def with_instrumentation_guard
|
|
54
|
+
return if Thread.current[INSTRUMENTING_KEY]
|
|
55
|
+
|
|
56
|
+
Thread.current[INSTRUMENTING_KEY] = true
|
|
57
|
+
begin
|
|
58
|
+
yield
|
|
59
|
+
ensure
|
|
60
|
+
Thread.current[INSTRUMENTING_KEY] = nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
39
64
|
def configure
|
|
40
65
|
yield(configuration) if block_given?
|
|
41
66
|
configuration
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- BrainzLab
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -93,11 +93,11 @@ dependencies:
|
|
|
93
93
|
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '3.0'
|
|
96
|
-
description:
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
description: Ruby SDK for the BrainzLab observability platform. Includes Recall (structured
|
|
97
|
+
logging), Reflex (error tracking), and Pulse (APM with distributed tracing). Auto-instruments
|
|
98
|
+
Rails, Sidekiq, GraphQL, Redis, and more.
|
|
99
99
|
email:
|
|
100
|
-
-
|
|
100
|
+
- rubygems@brainz.llc
|
|
101
101
|
executables: []
|
|
102
102
|
extensions: []
|
|
103
103
|
extra_rdoc_files: []
|
|
@@ -219,6 +219,7 @@ files:
|
|
|
219
219
|
- lib/brainzlab/vision.rb
|
|
220
220
|
- lib/brainzlab/vision/client.rb
|
|
221
221
|
- lib/brainzlab/vision/provisioner.rb
|
|
222
|
+
- lib/fluyenta-ruby.rb
|
|
222
223
|
- lib/generators/brainzlab/install/install_generator.rb
|
|
223
224
|
- lib/generators/brainzlab/install/templates/brainzlab.rb.tt
|
|
224
225
|
homepage: https://brainzlab.ai
|
|
@@ -226,11 +227,11 @@ licenses:
|
|
|
226
227
|
- Nonstandard
|
|
227
228
|
metadata:
|
|
228
229
|
homepage_uri: https://brainzlab.ai
|
|
229
|
-
source_code_uri: https://github.com/brainz-
|
|
230
|
-
changelog_uri: https://github.com/brainz-
|
|
230
|
+
source_code_uri: https://github.com/brainz-llc/brainzlab-ruby
|
|
231
|
+
changelog_uri: https://github.com/brainz-llc/brainzlab-ruby/blob/main/CHANGELOG.md
|
|
231
232
|
documentation_uri: https://docs.brainzlab.ai/sdk/ruby
|
|
232
233
|
rubygems_mfa_required: 'true'
|
|
233
|
-
github_repo: ssh://github.com/brainz-
|
|
234
|
+
github_repo: ssh://github.com/brainz-llc/brainzlab-ruby
|
|
234
235
|
rdoc_options: []
|
|
235
236
|
require_paths:
|
|
236
237
|
- lib
|
|
@@ -247,6 +248,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
247
248
|
requirements: []
|
|
248
249
|
rubygems_version: 3.6.9
|
|
249
250
|
specification_version: 4
|
|
250
|
-
summary: Ruby SDK for BrainzLab
|
|
251
|
-
APM
|
|
251
|
+
summary: Ruby SDK for BrainzLab observability
|
|
252
252
|
test_files: []
|