brainzlab 0.1.34 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/brainzlab/beacon/client.rb +4 -6
  3. data/lib/brainzlab/configuration.rb +301 -272
  4. data/lib/brainzlab/cortex/client.rb +4 -6
  5. data/lib/brainzlab/debug.rb +6 -6
  6. data/lib/brainzlab/dendrite/client.rb +4 -6
  7. data/lib/brainzlab/development/logger.rb +2 -2
  8. data/lib/brainzlab/development/store.rb +2 -4
  9. data/lib/brainzlab/devtools/middleware/error_page.rb +1 -1
  10. data/lib/brainzlab/errors.rb +5 -12
  11. data/lib/brainzlab/instrumentation/action_cable.rb +4 -4
  12. data/lib/brainzlab/instrumentation/action_controller.rb +12 -12
  13. data/lib/brainzlab/instrumentation/action_dispatch.rb +1 -1
  14. data/lib/brainzlab/instrumentation/action_mailbox.rb +7 -13
  15. data/lib/brainzlab/instrumentation/action_view.rb +29 -33
  16. data/lib/brainzlab/instrumentation/active_job.rb +6 -10
  17. data/lib/brainzlab/instrumentation/active_record.rb +21 -24
  18. data/lib/brainzlab/instrumentation/active_storage.rb +4 -4
  19. data/lib/brainzlab/instrumentation/active_support_cache.rb +6 -8
  20. data/lib/brainzlab/instrumentation/aws.rb +10 -7
  21. data/lib/brainzlab/instrumentation/grape.rb +3 -1
  22. data/lib/brainzlab/instrumentation/rails_deprecation.rb +1 -1
  23. data/lib/brainzlab/instrumentation/railties.rb +2 -2
  24. data/lib/brainzlab/nerve/client.rb +4 -6
  25. data/lib/brainzlab/pulse/client.rb +1 -1
  26. data/lib/brainzlab/pulse.rb +5 -1
  27. data/lib/brainzlab/rails/railtie.rb +1 -3
  28. data/lib/brainzlab/reflex/client.rb +1 -1
  29. data/lib/brainzlab/sentinel/client.rb +4 -6
  30. data/lib/brainzlab/synapse/client.rb +4 -6
  31. data/lib/brainzlab/testing/event_store.rb +3 -3
  32. data/lib/brainzlab/testing/matchers.rb +4 -4
  33. data/lib/brainzlab/testing.rb +5 -3
  34. data/lib/brainzlab/vault/client.rb +4 -6
  35. data/lib/brainzlab/version.rb +1 -1
  36. data/lib/brainzlab/vision/client.rb +3 -3
  37. data/lib/fluyenta-ruby.rb +1 -1
  38. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c67c9e29d88bcc008c8ba0464b933b61f3a9bd255868f59af8b7e0daf31665dc
4
- data.tar.gz: bbfe09e8406b488f995cf312b1b1838cd4f614dc1708316eda3c3b5cd1dcab08
3
+ metadata.gz: bf1207ec18676d63267312a25c4bcbe2519a1ddc70ceb5bb34ac58f7a83e29d2
4
+ data.tar.gz: d8ea421af25cae73f94d4741eed9a404c6297477088eaf3df9b4a000675824da
5
5
  SHA512:
6
- metadata.gz: d83a33af5f933e84f1bec1327f564b79d2fe2dc8c94035beebbc1985d5e44130d88a442ca95bb2654f9a4bb3aed207e3514d083c2fb69d92a2faf0df38becc62
7
- data.tar.gz: 93a945a9c8d6f3d91ef144fd005b2b0f1bcdad9a90c8f7cabdca7f2a4602b6678409cc2e17d2e06baf8e560301c0b9dafbb2339f6e56e353022098ed145e732a
6
+ metadata.gz: 2bee3ad0497520af5744ccd898360f389ac30bf599382d4af14f871d2b1f30659a5879a8ad5a6dfcfd1bf008445e0262a1d2fa5887d00146128099cda99410b3
7
+ data.tar.gz: 0760d616e2b3a32c2c408098d5a69d4a68d3d3ab52f3dbde121d2994c4498bb5c26cbc24fd6d569a3f41d6614cceca3075a9f06df1bbdea1bd5f9da399da2861
@@ -204,9 +204,9 @@ module BrainzLab
204
204
  BrainzLab.debug_log("[Beacon::Client] #{operation} failed: #{structured_error.message}")
205
205
 
206
206
  # Call on_error callback if configured
207
- if @config.on_error
208
- @config.on_error.call(structured_error, { service: 'Beacon', operation: operation })
209
- end
207
+ return unless @config.on_error
208
+
209
+ @config.on_error.call(structured_error, { service: 'Beacon', operation: operation })
210
210
  end
211
211
 
212
212
  def handle_response_error(response, operation)
@@ -216,9 +216,7 @@ module BrainzLab
216
216
  BrainzLab.debug_log("[Beacon::Client] #{operation} failed: #{structured_error.message}")
217
217
 
218
218
  # Call on_error callback if configured
219
- if @config.on_error
220
- @config.on_error.call(structured_error, { service: 'Beacon', operation: operation })
221
- end
219
+ @config.on_error&.call(structured_error, { service: 'Beacon', operation: operation })
222
220
 
223
221
  structured_error
224
222
  end
@@ -199,270 +199,13 @@ module BrainzLab
199
199
  }.freeze
200
200
 
201
201
  def initialize
202
- # SDK enabled flag — set BRAINZLAB_SDK_ENABLED=false to completely disable
203
- @enabled = ENV.fetch('BRAINZLAB_SDK_ENABLED', 'true') != 'false'
204
-
205
- # Authentication
206
- @secret_key = ENV.fetch('BRAINZLAB_SECRET_KEY', nil)
207
-
208
- # Environment
209
- @environment = ENV['BRAINZLAB_ENVIRONMENT'] || detect_environment
210
- @service = ENV.fetch('BRAINZLAB_SERVICE', nil)
211
- @host = ENV['BRAINZLAB_HOST'] || detect_host
212
-
213
- # App name for auto-provisioning
214
- @app_name = ENV.fetch('BRAINZLAB_APP_NAME', nil)
215
-
216
- # Git context
217
- @commit = ENV['GIT_COMMIT'] || ENV['COMMIT_SHA'] || detect_git_commit
218
- @branch = ENV['GIT_BRANCH'] || ENV['BRANCH_NAME'] || detect_git_branch
219
-
220
- # Debug mode - enables verbose logging
221
- @debug = ENV['BRAINZLAB_DEBUG'] == 'true'
222
-
223
- # SDK mode - :production (default) or :development (offline, local storage)
224
- @mode = ENV['BRAINZLAB_MODE']&.to_sym || :production
225
-
226
- # Development mode settings
227
- @development_db_path = ENV['BRAINZLAB_DEV_DB_PATH'] || 'tmp/brainzlab.sqlite3'
228
- @development_log_output = $stdout
229
-
230
- # Disable self-tracking - prevents services from tracking to themselves
231
- # e.g., Recall won't log to itself, Reflex won't track errors to itself
232
- @disable_self_tracking = ENV.fetch('BRAINZLAB_DISABLE_SELF_TRACKING', 'true') == 'true'
233
-
234
- # Recall settings
235
- @recall_enabled = true
236
- @recall_url = ENV['RECALL_URL'] || detect_product_url('recall')
237
- @recall_min_level = :debug
238
- @recall_buffer_size = 50
239
- @recall_flush_interval = 5
240
- @recall_master_key = ENV.fetch('RECALL_MASTER_KEY', nil)
241
- @recall_auto_provision = true
242
-
243
- # Reflex settings
244
- @reflex_enabled = true
245
- @reflex_url = ENV['REFLEX_URL'] || detect_product_url('reflex')
246
- @reflex_api_key = ENV.fetch('REFLEX_API_KEY', nil)
247
- @reflex_master_key = ENV.fetch('REFLEX_MASTER_KEY', nil)
248
- @reflex_auto_provision = true
249
- @reflex_excluded_exceptions = []
250
- @reflex_before_send = nil
251
- @reflex_sample_rate = nil
252
- @reflex_fingerprint = nil # Custom fingerprint callback
253
-
254
- # Pulse settings
255
- @pulse_enabled = true
256
- @pulse_url = ENV['PULSE_URL'] || detect_product_url('pulse')
257
- @pulse_api_key = ENV.fetch('PULSE_API_KEY', nil)
258
- @pulse_master_key = ENV.fetch('PULSE_MASTER_KEY', nil)
259
- @pulse_auto_provision = true
260
- @pulse_buffer_size = 50
261
- @pulse_flush_interval = 5
262
- # Trace sampling. Explicit rate (0.0–1.0) wins; otherwise derived from the
263
- # tier. Defaults to the "internal" tier so the Brainz fleet samples ~10%
264
- # (error traces are always kept). Client-facing apps set
265
- # BRAINZLAB_PULSE_TIER=client (or PULSE_SAMPLE_RATE) to keep everything.
266
- @pulse_sample_rate = (v = ENV['PULSE_SAMPLE_RATE'] || ENV['BRAINZLAB_PULSE_SAMPLE_RATE']).nil? || v.to_s.strip.empty? ? nil : v.to_f
267
- @pulse_sample_tier = (ENV['BRAINZLAB_PULSE_TIER'] || 'internal').to_s.strip.downcase
268
- # Traces at/above this duration (ms) bypass sampling and are ALWAYS kept,
269
- # so slow requests / N+1s are never sampled away (APM's whole purpose).
270
- # Set to 0 to disable. Default 1000ms.
271
- @pulse_always_keep_slow_ms = (ENV['BRAINZLAB_PULSE_ALWAYS_KEEP_SLOW_MS'] || 1000).to_i
272
- @pulse_excluded_paths = %w[/health /ping /up /assets]
273
-
274
- # Flux settings
275
- @flux_enabled = true
276
- @flux_url = ENV['FLUX_URL'] || 'https://flux.brainzlab.ai'
277
- @flux_api_key = ENV.fetch('FLUX_API_KEY', nil)
278
- @flux_ingest_key = ENV.fetch('FLUX_INGEST_KEY', nil)
279
- @flux_master_key = ENV.fetch('FLUX_MASTER_KEY', nil)
280
- @flux_auto_provision = true
281
- @flux_buffer_size = 100
282
- @flux_flush_interval = 5
283
-
284
- # Signal settings
285
- @signal_enabled = true
286
- @signal_url = ENV['SIGNAL_URL'] || detect_product_url('signal')
287
- @signal_api_key = ENV.fetch('SIGNAL_API_KEY', nil)
288
- @signal_ingest_key = ENV.fetch('SIGNAL_INGEST_KEY', nil)
289
- @signal_master_key = ENV.fetch('SIGNAL_MASTER_KEY', nil)
290
- @signal_auto_provision = true
291
-
292
- # Vault settings
293
- @vault_enabled = true
294
- @vault_url = ENV['VAULT_URL'] || 'https://vault.brainzlab.ai'
295
- @vault_api_key = ENV.fetch('VAULT_API_KEY', nil)
296
- @vault_master_key = ENV.fetch('VAULT_MASTER_KEY', nil)
297
- @vault_auto_provision = true
298
- @vault_cache_enabled = true
299
- @vault_cache_ttl = 300 # 5 minutes
300
- @vault_auto_load = ENV.fetch('VAULT_AUTO_LOAD', 'false') == 'true' # Auto-load secrets into ENV
301
- @vault_load_provider_keys = true # Also load provider keys (OpenAI, etc.)
302
-
303
- # Vision settings (AI browser automation)
304
- @vision_enabled = true
305
- @vision_url = ENV['VISION_URL'] || 'https://vision.brainzlab.ai'
306
- @vision_api_key = ENV.fetch('VISION_API_KEY', nil)
307
- @vision_ingest_key = ENV.fetch('VISION_INGEST_KEY', nil)
308
- @vision_master_key = ENV.fetch('VISION_MASTER_KEY', nil)
309
- @vision_auto_provision = true
310
- @vision_default_model = ENV['VISION_DEFAULT_MODEL'] || 'claude-sonnet-4'
311
- @vision_default_browser_provider = ENV['VISION_DEFAULT_BROWSER_PROVIDER'] || 'local'
312
-
313
- # Cortex settings (feature flags)
314
- @cortex_enabled = true
315
- @cortex_url = ENV['CORTEX_URL'] || 'https://cortex.brainzlab.ai'
316
- @cortex_api_key = ENV.fetch('CORTEX_API_KEY', nil)
317
- @cortex_master_key = ENV.fetch('CORTEX_MASTER_KEY', nil)
318
- @cortex_auto_provision = true
319
- @cortex_cache_enabled = true
320
- @cortex_cache_ttl = 60 # 1 minute
321
- @cortex_default_context = {}
322
-
323
- # Beacon settings (uptime monitoring)
324
- @beacon_enabled = true
325
- @beacon_url = ENV['BEACON_URL'] || 'https://beacon.brainzlab.ai'
326
- @beacon_api_key = ENV.fetch('BEACON_API_KEY', nil)
327
- @beacon_master_key = ENV.fetch('BEACON_MASTER_KEY', nil)
328
- @beacon_auto_provision = true
329
-
330
- # Nerve settings (job monitoring)
331
- @nerve_enabled = true
332
- @nerve_url = ENV['NERVE_URL'] || 'https://nerve.brainzlab.ai'
333
- @nerve_api_key = ENV.fetch('NERVE_API_KEY', nil)
334
- @nerve_master_key = ENV.fetch('NERVE_MASTER_KEY', nil)
335
- @nerve_auto_provision = true
336
-
337
- # Dendrite settings (AI documentation)
338
- @dendrite_enabled = true
339
- @dendrite_url = ENV['DENDRITE_URL'] || 'https://dendrite.brainzlab.ai'
340
- @dendrite_api_key = ENV.fetch('DENDRITE_API_KEY', nil)
341
- @dendrite_master_key = ENV.fetch('DENDRITE_MASTER_KEY', nil)
342
- @dendrite_auto_provision = true
343
-
344
- # Sentinel settings (host monitoring)
345
- @sentinel_enabled = true
346
- @sentinel_url = ENV['SENTINEL_URL'] || 'https://sentinel.brainzlab.ai'
347
- @sentinel_api_key = ENV.fetch('SENTINEL_API_KEY', nil)
348
- @sentinel_agent_key = ENV.fetch('SENTINEL_AGENT_KEY', nil)
349
- @sentinel_master_key = ENV.fetch('SENTINEL_MASTER_KEY', nil)
350
- @sentinel_auto_provision = true
351
-
352
- # Synapse settings (AI development orchestration)
353
- @synapse_enabled = true
354
- @synapse_url = ENV['SYNAPSE_URL'] || 'https://synapse.brainzlab.ai'
355
- @synapse_api_key = ENV.fetch('SYNAPSE_API_KEY', nil)
356
- @synapse_master_key = ENV.fetch('SYNAPSE_MASTER_KEY', nil)
357
- @synapse_auto_provision = true
358
-
359
- # Filtering
360
- @scrub_fields = %i[password password_confirmation token api_key secret]
361
-
362
- # Internal logger for debugging SDK issues
363
- @logger = nil
364
-
365
- # Debug callbacks
366
- # Called when an SDK error occurs (lambda/proc receiving error object and context hash)
367
- @on_error = nil
368
- # Called before each API request (lambda/proc receiving service, method, path, and payload)
369
- @on_send = nil
370
-
371
- # Instrumentation
372
- @instrument_http = true # Enable HTTP client instrumentation (Net::HTTP, Faraday, HTTParty)
373
- @instrument_active_record = true # AR breadcrumbs for Reflex
374
- @instrument_redis = true # Redis command instrumentation
375
- @instrument_sidekiq = true # Sidekiq job instrumentation
376
- @instrument_graphql = true # GraphQL query instrumentation
377
- @instrument_mongodb = true # MongoDB/Mongoid instrumentation
378
- @instrument_elasticsearch = true # Elasticsearch instrumentation
379
- @instrument_action_controller = true # ActionController instrumentation (requests, redirects, filters)
380
- @instrument_action_view = true # ActionView instrumentation (templates, partials, collections)
381
- @instrument_action_mailer = true # ActionMailer instrumentation
382
- @instrument_active_job = true # ActiveJob instrumentation (enqueue, perform, retry, discard)
383
- @instrument_active_support_cache = true # ActiveSupport::Cache instrumentation (read, write, fetch)
384
- @instrument_delayed_job = true # Delayed::Job instrumentation
385
- @instrument_grape = true # Grape API instrumentation
386
- @instrument_solid_queue = true # Solid Queue job instrumentation
387
- @instrument_good_job = true # GoodJob instrumentation
388
- @instrument_resque = true # Resque instrumentation
389
- @instrument_excon = true # Excon HTTP client instrumentation
390
- @instrument_typhoeus = true # Typhoeus HTTP client instrumentation
391
- @instrument_dalli = true # Dalli/Memcached instrumentation
392
- @instrument_aws = true # AWS SDK instrumentation
393
- @instrument_stripe = true # Stripe API instrumentation
394
- @instrument_active_storage = true # ActiveStorage instrumentation (uploads, downloads, transforms)
395
- @instrument_action_cable = true # ActionCable WebSocket instrumentation
396
- @instrument_action_dispatch = true # ActionDispatch instrumentation (middleware, redirects, requests)
397
- @instrument_rails_deprecation = true # Rails deprecation warnings tracking
398
- @instrument_action_mailbox = true # ActionMailbox inbound email processing instrumentation
399
- @instrument_railties = true # Railties config initializer loading instrumentation
400
- @http_ignore_hosts = %w[localhost 127.0.0.1]
401
- @redis_ignore_commands = %w[ping info] # Commands to skip tracking
402
-
403
- # ActiveRecord slow-query / N+1 detection — all tunable so a single chatty
404
- # default can't flood Recall. Slow-query duration is APM data (Pulse already
405
- # records it), so it defaults to a quiet level (:debug) and a higher threshold.
406
- @slow_query_threshold = (ENV['BRAINZLAB_SLOW_QUERY_THRESHOLD'] || 500).to_i # ms (was hardcoded 100)
407
-
408
- # Query plan capture. Off by default: it issues an extra (read-only)
409
- # EXPLAIN against the app's own database, so an app opts in deliberately.
410
- # Threshold defaults to the slow query threshold — a fast query has no
411
- # plan worth looking at.
412
- @pulse_explain_enabled = ENV['BRAINZLAB_PULSE_EXPLAIN_ENABLED'] == 'true'
413
- @pulse_explain_threshold = (ENV['BRAINZLAB_PULSE_EXPLAIN_THRESHOLD'] || @slow_query_threshold).to_i
414
- @pulse_explain_timeout = (ENV['BRAINZLAB_PULSE_EXPLAIN_TIMEOUT'] || 2).to_i
415
- @very_slow_query_threshold = (ENV['BRAINZLAB_VERY_SLOW_QUERY_THRESHOLD'] || 2000).to_i # ms (was hardcoded 1000)
416
- @slow_query_log_level = (ENV['BRAINZLAB_SLOW_QUERY_LOG_LEVEL'] || 'debug').to_sym # was :warn → floods
417
- @very_slow_query_log_level = (ENV['BRAINZLAB_VERY_SLOW_QUERY_LOG_LEVEL'] || 'warn').to_sym
418
- @n_plus_one_threshold = (ENV['BRAINZLAB_N_PLUS_ONE_THRESHOLD'] || 10).to_i # was hardcoded 5
419
- @n_plus_one_log_level = (ENV['BRAINZLAB_N_PLUS_ONE_LOG_LEVEL'] || 'debug').to_sym # was :warning
420
-
421
- # Per-instrumentation slow / very-slow thresholds (ms) — all env-tunable so a
422
- # single low default can't flood Recall or wrongly escalate normal latency to
423
- # :error. Defaults preserve prior behaviour EXCEPT cache, which is raised off
424
- # DB-backed Solid Cache's normal write latency (~50ms paid a DB round-trip and
425
- # tripped the old VERY_SLOW=50 → thousands of false :error logs/hour).
426
- @slow_request_threshold = (ENV['BRAINZLAB_SLOW_REQUEST_THRESHOLD'] || 500).to_i
427
- @very_slow_request_threshold = (ENV['BRAINZLAB_VERY_SLOW_REQUEST_THRESHOLD'] || 2000).to_i
428
- @slow_cache_threshold = (ENV['BRAINZLAB_SLOW_CACHE_THRESHOLD'] || 100).to_i # was 10
429
- @very_slow_cache_threshold = (ENV['BRAINZLAB_VERY_SLOW_CACHE_THRESHOLD'] || 500).to_i # was 50 → flooded :error
430
- @slow_render_threshold = (ENV['BRAINZLAB_SLOW_RENDER_THRESHOLD'] || 50).to_i
431
- @very_slow_render_threshold = (ENV['BRAINZLAB_VERY_SLOW_RENDER_THRESHOLD'] || 200).to_i
432
- @slow_storage_threshold = (ENV['BRAINZLAB_SLOW_STORAGE_THRESHOLD'] || 500).to_i
433
- @very_slow_storage_threshold = (ENV['BRAINZLAB_VERY_SLOW_STORAGE_THRESHOLD'] || 2000).to_i
434
- @slow_cable_threshold = (ENV['BRAINZLAB_SLOW_CABLE_THRESHOLD'] || 100).to_i
435
- @very_slow_cable_threshold = (ENV['BRAINZLAB_VERY_SLOW_CABLE_THRESHOLD'] || 500).to_i
436
- @slow_initializer_threshold = (ENV['BRAINZLAB_SLOW_INITIALIZER_THRESHOLD'] || 100).to_i
437
- @very_slow_initializer_threshold = (ENV['BRAINZLAB_VERY_SLOW_INITIALIZER_THRESHOLD'] || 500).to_i
438
- @slow_middleware_threshold = (ENV['BRAINZLAB_SLOW_MIDDLEWARE_THRESHOLD'] || 50).to_i
439
- @very_slow_middleware_threshold = (ENV['BRAINZLAB_VERY_SLOW_MIDDLEWARE_THRESHOLD'] || 200).to_i
440
- @slow_mailbox_threshold = (ENV['BRAINZLAB_SLOW_MAILBOX_THRESHOLD'] || 1000).to_i
441
- @very_slow_mailbox_threshold = (ENV['BRAINZLAB_VERY_SLOW_MAILBOX_THRESHOLD'] || 5000).to_i
442
- @slow_job_threshold = (ENV['BRAINZLAB_SLOW_JOB_THRESHOLD'] || 5000).to_i
443
- @very_slow_job_threshold = (ENV['BRAINZLAB_VERY_SLOW_JOB_THRESHOLD'] || 30_000).to_i
444
-
445
- # Log formatter settings
446
- @log_formatter_enabled = true
447
- @log_formatter_colors = nil # auto-detect TTY
448
- @log_formatter_hide_assets = false
449
- @log_formatter_compact_assets = true
450
- @log_formatter_show_params = true
451
-
452
- # DevTools settings (development error page and debug panel)
453
- @devtools_enabled = true
454
- @devtools_error_page_enabled = true
455
- @devtools_debug_panel_enabled = true
456
- @devtools_allowed_environments = %w[development test]
457
- @devtools_allowed_ips = ['127.0.0.1', '::1', '172.16.0.0/12', '192.168.0.0/16', '10.0.0.0/8']
458
- @devtools_asset_path = '/__brainzlab__'
459
- @devtools_panel_position = 'bottom-right'
460
- @devtools_expand_by_default = false
461
-
462
- # Rails instrumentation delegation
463
- # When true, brainzlab-rails gem handles Rails-specific instrumentation
464
- # SDK will only install non-Rails instrumentation (HTTP clients, Redis, etc.)
465
- @rails_instrumentation_handled_externally = false
202
+ init_core_defaults
203
+ init_observability_defaults
204
+ init_platform_defaults
205
+ init_runtime_defaults
206
+ init_instrumentation_defaults
207
+ init_threshold_defaults
208
+ init_presentation_defaults
466
209
  end
467
210
 
468
211
  def recall_min_level=(level)
@@ -542,9 +285,7 @@ module BrainzLab
542
285
  value = public_send(setting)
543
286
  next if value.nil? # nil means "fall back to the tier default"
544
287
 
545
- unless value.is_a?(Numeric) && value.between?(0, 1)
546
- problems << "#{setting} must be a number between 0 and 1 (got #{value.inspect})"
547
- end
288
+ problems << "#{setting} must be a number between 0 and 1 (got #{value.inspect})" unless value.is_a?(Numeric) && value.between?(0, 1)
548
289
  end
549
290
 
550
291
  POSITIVE_INTEGER_SETTINGS.each do |setting|
@@ -554,9 +295,7 @@ module BrainzLab
554
295
 
555
296
  NON_NEGATIVE_NUMBER_SETTINGS.each do |setting|
556
297
  value = public_send(setting)
557
- unless value.is_a?(Numeric) && !value.negative?
558
- problems << "#{setting} must be a non-negative number (got #{value.inspect})"
559
- end
298
+ problems << "#{setting} must be a non-negative number (got #{value.inspect})" unless value.is_a?(Numeric) && !value.negative?
560
299
  end
561
300
 
562
301
  return self if problems.empty?
@@ -598,7 +337,7 @@ module BrainzLab
598
337
  PULSE_TIER_SAMPLE_RATES = {
599
338
  'internal' => 0.1,
600
339
  'standard' => 0.5,
601
- 'client' => 1.0,
340
+ 'client' => 1.0,
602
341
  'critical' => 1.0
603
342
  }.freeze
604
343
 
@@ -714,7 +453,11 @@ module BrainzLab
714
453
  @synapse_url
715
454
  ].compact
716
455
 
717
- urls.filter_map { |url| URI.parse(url).host rescue nil }.uniq
456
+ urls.filter_map do |url|
457
+ URI.parse(url).host
458
+ rescue StandardError
459
+ nil
460
+ end.uniq
718
461
  end
719
462
  end
720
463
 
@@ -786,6 +529,292 @@ module BrainzLab
786
529
 
787
530
  private
788
531
 
532
+ # Core identity: enablement, auth, environment, git context, SDK mode.
533
+ def init_core_defaults
534
+ # SDK enabled flag — set BRAINZLAB_SDK_ENABLED=false to completely disable
535
+ @enabled = ENV.fetch('BRAINZLAB_SDK_ENABLED', 'true') != 'false'
536
+
537
+ # Authentication
538
+ @secret_key = ENV.fetch('BRAINZLAB_SECRET_KEY', nil)
539
+
540
+ # Environment
541
+ @environment = ENV['BRAINZLAB_ENVIRONMENT'] || detect_environment
542
+ @service = ENV.fetch('BRAINZLAB_SERVICE', nil)
543
+ @host = ENV['BRAINZLAB_HOST'] || detect_host
544
+
545
+ # App name for auto-provisioning
546
+ @app_name = ENV.fetch('BRAINZLAB_APP_NAME', nil)
547
+
548
+ # Git context
549
+ @commit = ENV['GIT_COMMIT'] || ENV['COMMIT_SHA'] || detect_git_commit
550
+ @branch = ENV['GIT_BRANCH'] || ENV['BRANCH_NAME'] || detect_git_branch
551
+
552
+ # Debug mode - enables verbose logging
553
+ @debug = ENV['BRAINZLAB_DEBUG'] == 'true'
554
+
555
+ # SDK mode - :production (default) or :development (offline, local storage)
556
+ @mode = ENV['BRAINZLAB_MODE']&.to_sym || :production
557
+
558
+ # Development mode settings
559
+ @development_db_path = ENV['BRAINZLAB_DEV_DB_PATH'] || 'tmp/brainzlab.sqlite3'
560
+ @development_log_output = $stdout
561
+
562
+ # Disable self-tracking - prevents services from tracking to themselves
563
+ # e.g., Recall won't log to itself, Reflex won't track errors to itself
564
+ @disable_self_tracking = ENV.fetch('BRAINZLAB_DISABLE_SELF_TRACKING', 'true') == 'true'
565
+ end
566
+
567
+ # The five observability products: Recall, Reflex, Pulse, Flux, Signal.
568
+ def init_observability_defaults
569
+ # Recall settings
570
+ @recall_enabled = true
571
+ @recall_url = ENV['RECALL_URL'] || detect_product_url('recall')
572
+ @recall_min_level = :debug
573
+ @recall_buffer_size = 50
574
+ @recall_flush_interval = 5
575
+ @recall_master_key = ENV.fetch('RECALL_MASTER_KEY', nil)
576
+ @recall_auto_provision = true
577
+
578
+ # Reflex settings
579
+ @reflex_enabled = true
580
+ @reflex_url = ENV['REFLEX_URL'] || detect_product_url('reflex')
581
+ @reflex_api_key = ENV.fetch('REFLEX_API_KEY', nil)
582
+ @reflex_master_key = ENV.fetch('REFLEX_MASTER_KEY', nil)
583
+ @reflex_auto_provision = true
584
+ @reflex_excluded_exceptions = []
585
+ @reflex_before_send = nil
586
+ @reflex_sample_rate = nil
587
+ @reflex_fingerprint = nil # Custom fingerprint callback
588
+
589
+ # Pulse settings
590
+ @pulse_enabled = true
591
+ @pulse_url = ENV['PULSE_URL'] || detect_product_url('pulse')
592
+ @pulse_api_key = ENV.fetch('PULSE_API_KEY', nil)
593
+ @pulse_master_key = ENV.fetch('PULSE_MASTER_KEY', nil)
594
+ @pulse_auto_provision = true
595
+ @pulse_buffer_size = 50
596
+ @pulse_flush_interval = 5
597
+ # Trace sampling. Explicit rate (0.0–1.0) wins; otherwise derived from the
598
+ # tier. Defaults to the "internal" tier so the Brainz fleet samples ~10%
599
+ # (error traces are always kept). Client-facing apps set
600
+ # BRAINZLAB_PULSE_TIER=client (or PULSE_SAMPLE_RATE) to keep everything.
601
+ @pulse_sample_rate = (v = ENV['PULSE_SAMPLE_RATE'] || ENV.fetch('BRAINZLAB_PULSE_SAMPLE_RATE', nil)).nil? || v.to_s.strip.empty? ? nil : v.to_f
602
+ @pulse_sample_tier = (ENV['BRAINZLAB_PULSE_TIER'] || 'internal').to_s.strip.downcase
603
+ # Traces at/above this duration (ms) bypass sampling and are ALWAYS kept,
604
+ # so slow requests / N+1s are never sampled away (APM's whole purpose).
605
+ # Set to 0 to disable. Default 1000ms.
606
+ @pulse_always_keep_slow_ms = (ENV['BRAINZLAB_PULSE_ALWAYS_KEEP_SLOW_MS'] || 1000).to_i
607
+ @pulse_excluded_paths = %w[/health /ping /up /assets]
608
+
609
+ # Flux settings
610
+ @flux_enabled = true
611
+ @flux_url = ENV['FLUX_URL'] || 'https://flux.brainzlab.ai'
612
+ @flux_api_key = ENV.fetch('FLUX_API_KEY', nil)
613
+ @flux_ingest_key = ENV.fetch('FLUX_INGEST_KEY', nil)
614
+ @flux_master_key = ENV.fetch('FLUX_MASTER_KEY', nil)
615
+ @flux_auto_provision = true
616
+ @flux_buffer_size = 100
617
+ @flux_flush_interval = 5
618
+
619
+ # Signal settings
620
+ @signal_enabled = true
621
+ @signal_url = ENV['SIGNAL_URL'] || detect_product_url('signal')
622
+ @signal_api_key = ENV.fetch('SIGNAL_API_KEY', nil)
623
+ @signal_ingest_key = ENV.fetch('SIGNAL_INGEST_KEY', nil)
624
+ @signal_master_key = ENV.fetch('SIGNAL_MASTER_KEY', nil)
625
+ @signal_auto_provision = true
626
+ end
627
+
628
+ # The platform services: Vault, Vision, Cortex, Beacon, Nerve, Dendrite, Sentinel, Synapse.
629
+ def init_platform_defaults
630
+ # Vault settings
631
+ @vault_enabled = true
632
+ @vault_url = ENV['VAULT_URL'] || 'https://vault.brainzlab.ai'
633
+ @vault_api_key = ENV.fetch('VAULT_API_KEY', nil)
634
+ @vault_master_key = ENV.fetch('VAULT_MASTER_KEY', nil)
635
+ @vault_auto_provision = true
636
+ @vault_cache_enabled = true
637
+ @vault_cache_ttl = 300 # 5 minutes
638
+ @vault_auto_load = ENV.fetch('VAULT_AUTO_LOAD', 'false') == 'true' # Auto-load secrets into ENV
639
+ @vault_load_provider_keys = true # Also load provider keys (OpenAI, etc.)
640
+
641
+ # Vision settings (AI browser automation)
642
+ @vision_enabled = true
643
+ @vision_url = ENV['VISION_URL'] || 'https://vision.brainzlab.ai'
644
+ @vision_api_key = ENV.fetch('VISION_API_KEY', nil)
645
+ @vision_ingest_key = ENV.fetch('VISION_INGEST_KEY', nil)
646
+ @vision_master_key = ENV.fetch('VISION_MASTER_KEY', nil)
647
+ @vision_auto_provision = true
648
+ @vision_default_model = ENV['VISION_DEFAULT_MODEL'] || 'claude-sonnet-4'
649
+ @vision_default_browser_provider = ENV['VISION_DEFAULT_BROWSER_PROVIDER'] || 'local'
650
+
651
+ # Cortex settings (feature flags)
652
+ @cortex_enabled = true
653
+ @cortex_url = ENV['CORTEX_URL'] || 'https://cortex.brainzlab.ai'
654
+ @cortex_api_key = ENV.fetch('CORTEX_API_KEY', nil)
655
+ @cortex_master_key = ENV.fetch('CORTEX_MASTER_KEY', nil)
656
+ @cortex_auto_provision = true
657
+ @cortex_cache_enabled = true
658
+ @cortex_cache_ttl = 60 # 1 minute
659
+ @cortex_default_context = {}
660
+
661
+ # Beacon settings (uptime monitoring)
662
+ @beacon_enabled = true
663
+ @beacon_url = ENV['BEACON_URL'] || 'https://beacon.brainzlab.ai'
664
+ @beacon_api_key = ENV.fetch('BEACON_API_KEY', nil)
665
+ @beacon_master_key = ENV.fetch('BEACON_MASTER_KEY', nil)
666
+ @beacon_auto_provision = true
667
+
668
+ # Nerve settings (job monitoring)
669
+ @nerve_enabled = true
670
+ @nerve_url = ENV['NERVE_URL'] || 'https://nerve.brainzlab.ai'
671
+ @nerve_api_key = ENV.fetch('NERVE_API_KEY', nil)
672
+ @nerve_master_key = ENV.fetch('NERVE_MASTER_KEY', nil)
673
+ @nerve_auto_provision = true
674
+
675
+ # Dendrite settings (AI documentation)
676
+ @dendrite_enabled = true
677
+ @dendrite_url = ENV['DENDRITE_URL'] || 'https://dendrite.brainzlab.ai'
678
+ @dendrite_api_key = ENV.fetch('DENDRITE_API_KEY', nil)
679
+ @dendrite_master_key = ENV.fetch('DENDRITE_MASTER_KEY', nil)
680
+ @dendrite_auto_provision = true
681
+
682
+ # Sentinel settings (host monitoring)
683
+ @sentinel_enabled = true
684
+ @sentinel_url = ENV['SENTINEL_URL'] || 'https://sentinel.brainzlab.ai'
685
+ @sentinel_api_key = ENV.fetch('SENTINEL_API_KEY', nil)
686
+ @sentinel_agent_key = ENV.fetch('SENTINEL_AGENT_KEY', nil)
687
+ @sentinel_master_key = ENV.fetch('SENTINEL_MASTER_KEY', nil)
688
+ @sentinel_auto_provision = true
689
+
690
+ # Synapse settings (AI development orchestration)
691
+ @synapse_enabled = true
692
+ @synapse_url = ENV['SYNAPSE_URL'] || 'https://synapse.brainzlab.ai'
693
+ @synapse_api_key = ENV.fetch('SYNAPSE_API_KEY', nil)
694
+ @synapse_master_key = ENV.fetch('SYNAPSE_MASTER_KEY', nil)
695
+ @synapse_auto_provision = true
696
+ end
697
+
698
+ # Scrubbing, internal logger and debug callbacks.
699
+ def init_runtime_defaults
700
+ # Filtering
701
+ @scrub_fields = %i[password password_confirmation token api_key secret]
702
+
703
+ # Internal logger for debugging SDK issues
704
+ @logger = nil
705
+
706
+ # Debug callbacks
707
+ # Called when an SDK error occurs (lambda/proc receiving error object and context hash)
708
+ @on_error = nil
709
+ # Called before each API request (lambda/proc receiving service, method, path, and payload)
710
+ @on_send = nil
711
+ end
712
+
713
+ # Which integrations auto-instrument, and what they ignore.
714
+ def init_instrumentation_defaults
715
+ # Instrumentation
716
+ @instrument_http = true # Enable HTTP client instrumentation (Net::HTTP, Faraday, HTTParty)
717
+ @instrument_active_record = true # AR breadcrumbs for Reflex
718
+ @instrument_redis = true # Redis command instrumentation
719
+ @instrument_sidekiq = true # Sidekiq job instrumentation
720
+ @instrument_graphql = true # GraphQL query instrumentation
721
+ @instrument_mongodb = true # MongoDB/Mongoid instrumentation
722
+ @instrument_elasticsearch = true # Elasticsearch instrumentation
723
+ @instrument_action_controller = true # ActionController instrumentation (requests, redirects, filters)
724
+ @instrument_action_view = true # ActionView instrumentation (templates, partials, collections)
725
+ @instrument_action_mailer = true # ActionMailer instrumentation
726
+ @instrument_active_job = true # ActiveJob instrumentation (enqueue, perform, retry, discard)
727
+ @instrument_active_support_cache = true # ActiveSupport::Cache instrumentation (read, write, fetch)
728
+ @instrument_delayed_job = true # Delayed::Job instrumentation
729
+ @instrument_grape = true # Grape API instrumentation
730
+ @instrument_solid_queue = true # Solid Queue job instrumentation
731
+ @instrument_good_job = true # GoodJob instrumentation
732
+ @instrument_resque = true # Resque instrumentation
733
+ @instrument_excon = true # Excon HTTP client instrumentation
734
+ @instrument_typhoeus = true # Typhoeus HTTP client instrumentation
735
+ @instrument_dalli = true # Dalli/Memcached instrumentation
736
+ @instrument_aws = true # AWS SDK instrumentation
737
+ @instrument_stripe = true # Stripe API instrumentation
738
+ @instrument_active_storage = true # ActiveStorage instrumentation (uploads, downloads, transforms)
739
+ @instrument_action_cable = true # ActionCable WebSocket instrumentation
740
+ @instrument_action_dispatch = true # ActionDispatch instrumentation (middleware, redirects, requests)
741
+ @instrument_rails_deprecation = true # Rails deprecation warnings tracking
742
+ @instrument_action_mailbox = true # ActionMailbox inbound email processing instrumentation
743
+ @instrument_railties = true # Railties config initializer loading instrumentation
744
+ @http_ignore_hosts = %w[localhost 127.0.0.1]
745
+ @redis_ignore_commands = %w[ping info] # Commands to skip tracking
746
+ end
747
+
748
+ # Slow / very-slow thresholds and N+1 detection, all env-tunable.
749
+ def init_threshold_defaults
750
+ # ActiveRecord slow-query / N+1 detection — all tunable so a single chatty
751
+ # default can't flood Recall. Slow-query duration is APM data (Pulse already
752
+ # records it), so it defaults to a quiet level (:debug) and a higher threshold.
753
+ @slow_query_threshold = (ENV['BRAINZLAB_SLOW_QUERY_THRESHOLD'] || 500).to_i # ms (was hardcoded 100)
754
+
755
+ # Query plan capture. Off by default: it issues an extra (read-only)
756
+ # EXPLAIN against the app's own database, so an app opts in deliberately.
757
+ # Threshold defaults to the slow query threshold — a fast query has no
758
+ # plan worth looking at.
759
+ @pulse_explain_enabled = ENV['BRAINZLAB_PULSE_EXPLAIN_ENABLED'] == 'true'
760
+ @pulse_explain_threshold = (ENV['BRAINZLAB_PULSE_EXPLAIN_THRESHOLD'] || @slow_query_threshold).to_i
761
+ @pulse_explain_timeout = (ENV['BRAINZLAB_PULSE_EXPLAIN_TIMEOUT'] || 2).to_i
762
+ @very_slow_query_threshold = (ENV['BRAINZLAB_VERY_SLOW_QUERY_THRESHOLD'] || 2000).to_i # ms (was hardcoded 1000)
763
+ @slow_query_log_level = (ENV['BRAINZLAB_SLOW_QUERY_LOG_LEVEL'] || 'debug').to_sym # was :warn → floods
764
+ @very_slow_query_log_level = (ENV['BRAINZLAB_VERY_SLOW_QUERY_LOG_LEVEL'] || 'warn').to_sym
765
+ @n_plus_one_threshold = (ENV['BRAINZLAB_N_PLUS_ONE_THRESHOLD'] || 10).to_i # was hardcoded 5
766
+ @n_plus_one_log_level = (ENV['BRAINZLAB_N_PLUS_ONE_LOG_LEVEL'] || 'debug').to_sym # was :warning
767
+
768
+ # Per-instrumentation slow / very-slow thresholds (ms) — all env-tunable so a
769
+ # single low default can't flood Recall or wrongly escalate normal latency to
770
+ # :error. Defaults preserve prior behaviour EXCEPT cache, which is raised off
771
+ # DB-backed Solid Cache's normal write latency (~50ms paid a DB round-trip and
772
+ # tripped the old VERY_SLOW=50 → thousands of false :error logs/hour).
773
+ @slow_request_threshold = (ENV['BRAINZLAB_SLOW_REQUEST_THRESHOLD'] || 500).to_i
774
+ @very_slow_request_threshold = (ENV['BRAINZLAB_VERY_SLOW_REQUEST_THRESHOLD'] || 2000).to_i
775
+ @slow_cache_threshold = (ENV['BRAINZLAB_SLOW_CACHE_THRESHOLD'] || 100).to_i # was 10
776
+ @very_slow_cache_threshold = (ENV['BRAINZLAB_VERY_SLOW_CACHE_THRESHOLD'] || 500).to_i # was 50 → flooded :error
777
+ @slow_render_threshold = (ENV['BRAINZLAB_SLOW_RENDER_THRESHOLD'] || 50).to_i
778
+ @very_slow_render_threshold = (ENV['BRAINZLAB_VERY_SLOW_RENDER_THRESHOLD'] || 200).to_i
779
+ @slow_storage_threshold = (ENV['BRAINZLAB_SLOW_STORAGE_THRESHOLD'] || 500).to_i
780
+ @very_slow_storage_threshold = (ENV['BRAINZLAB_VERY_SLOW_STORAGE_THRESHOLD'] || 2000).to_i
781
+ @slow_cable_threshold = (ENV['BRAINZLAB_SLOW_CABLE_THRESHOLD'] || 100).to_i
782
+ @very_slow_cable_threshold = (ENV['BRAINZLAB_VERY_SLOW_CABLE_THRESHOLD'] || 500).to_i
783
+ @slow_initializer_threshold = (ENV['BRAINZLAB_SLOW_INITIALIZER_THRESHOLD'] || 100).to_i
784
+ @very_slow_initializer_threshold = (ENV['BRAINZLAB_VERY_SLOW_INITIALIZER_THRESHOLD'] || 500).to_i
785
+ @slow_middleware_threshold = (ENV['BRAINZLAB_SLOW_MIDDLEWARE_THRESHOLD'] || 50).to_i
786
+ @very_slow_middleware_threshold = (ENV['BRAINZLAB_VERY_SLOW_MIDDLEWARE_THRESHOLD'] || 200).to_i
787
+ @slow_mailbox_threshold = (ENV['BRAINZLAB_SLOW_MAILBOX_THRESHOLD'] || 1000).to_i
788
+ @very_slow_mailbox_threshold = (ENV['BRAINZLAB_VERY_SLOW_MAILBOX_THRESHOLD'] || 5000).to_i
789
+ @slow_job_threshold = (ENV['BRAINZLAB_SLOW_JOB_THRESHOLD'] || 5000).to_i
790
+ @very_slow_job_threshold = (ENV['BRAINZLAB_VERY_SLOW_JOB_THRESHOLD'] || 30_000).to_i
791
+ end
792
+
793
+ # Log formatter, DevTools and Rails delegation.
794
+ def init_presentation_defaults
795
+ # Log formatter settings
796
+ @log_formatter_enabled = true
797
+ @log_formatter_colors = nil # auto-detect TTY
798
+ @log_formatter_hide_assets = false
799
+ @log_formatter_compact_assets = true
800
+ @log_formatter_show_params = true
801
+
802
+ # DevTools settings (development error page and debug panel)
803
+ @devtools_enabled = true
804
+ @devtools_error_page_enabled = true
805
+ @devtools_debug_panel_enabled = true
806
+ @devtools_allowed_environments = %w[development test]
807
+ @devtools_allowed_ips = ['127.0.0.1', '::1', '172.16.0.0/12', '192.168.0.0/16', '10.0.0.0/8']
808
+ @devtools_asset_path = '/__brainzlab__'
809
+ @devtools_panel_position = 'bottom-right'
810
+ @devtools_expand_by_default = false
811
+
812
+ # Rails instrumentation delegation
813
+ # When true, brainzlab-rails gem handles Rails-specific instrumentation
814
+ # SDK will only install non-Rails instrumentation (HTTP clients, Redis, etc.)
815
+ @rails_instrumentation_handled_externally = false
816
+ end
817
+
789
818
  def positive_number?(value)
790
819
  value.is_a?(Numeric) && value.positive?
791
820
  end