brainzlab 0.1.5 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 612fc535ff22f4fb1edb3d986ca4200f6fbbcb32e82718619a6f711d11e0d98b
4
- data.tar.gz: 665fefbcba13cfb7c14395b37dc8157c0938c3cc52935fe5fd7767b7cff3a3f5
3
+ metadata.gz: '0816189a1797239a3394e13fb0e08c33eddc061e146d4a1d3874b5b67777c967'
4
+ data.tar.gz: 3ff73e1d6babbd0b4f54f53eab270267be9e822d305cafeb59748b02f88f9b45
5
5
  SHA512:
6
- metadata.gz: 06e2a33185951e80bd102e6aee4d4e8417750be993949822983ceb66fd807394924e0546a3769ab6ffd0f30d1055c0f213fdc7458a66304bea89b9d304ad34a0
7
- data.tar.gz: 4675a166a98ed230885fae6926d7949c235ca1ab75c129702a643955089a130dddbe445351bfac0331247204083b0ce7223c7b1f1b9c4759ca2e73482f2c06b6
6
+ metadata.gz: e4733d1814297ebfd9cfe85aa909eddb3607e88efc024e07f55ea23ae5ee31d815538688a2fca1a67bba7a89ceb7b02a73aaa521f3114142539291c055dfff7a
7
+ data.tar.gz: 43a1826589f522ffdf678515798124729500e265c29f7860322db9a0a831e3b9433f738545024421c2667b029d96dfe2dcc7fd13dbd22cf2a990807585c57ceb
@@ -148,7 +148,8 @@ module BrainzLab
148
148
  :devtools_allowed_ips,
149
149
  :devtools_asset_path,
150
150
  :devtools_panel_position,
151
- :devtools_expand_by_default
151
+ :devtools_expand_by_default,
152
+ :rails_instrumentation_handled_externally
152
153
 
153
154
  # Services that should not track themselves to avoid circular dependencies
154
155
  SELF_TRACKING_SERVICES = {
@@ -184,7 +185,7 @@ module BrainzLab
184
185
 
185
186
  # Recall settings
186
187
  @recall_enabled = true
187
- @recall_url = ENV['RECALL_URL'] || 'https://recall.brainzlab.ai'
188
+ @recall_url = ENV['RECALL_URL'] || detect_product_url('recall')
188
189
  @recall_min_level = :debug
189
190
  @recall_buffer_size = 50
190
191
  @recall_flush_interval = 5
@@ -193,7 +194,7 @@ module BrainzLab
193
194
 
194
195
  # Reflex settings
195
196
  @reflex_enabled = true
196
- @reflex_url = ENV['REFLEX_URL'] || 'https://reflex.brainzlab.ai'
197
+ @reflex_url = ENV['REFLEX_URL'] || detect_product_url('reflex')
197
198
  @reflex_api_key = ENV.fetch('REFLEX_API_KEY', nil)
198
199
  @reflex_master_key = ENV.fetch('REFLEX_MASTER_KEY', nil)
199
200
  @reflex_auto_provision = true
@@ -204,7 +205,7 @@ module BrainzLab
204
205
 
205
206
  # Pulse settings
206
207
  @pulse_enabled = true
207
- @pulse_url = ENV['PULSE_URL'] || 'https://pulse.brainzlab.ai'
208
+ @pulse_url = ENV['PULSE_URL'] || detect_product_url('pulse')
208
209
  @pulse_api_key = ENV.fetch('PULSE_API_KEY', nil)
209
210
  @pulse_master_key = ENV.fetch('PULSE_MASTER_KEY', nil)
210
211
  @pulse_auto_provision = true
@@ -225,7 +226,7 @@ module BrainzLab
225
226
 
226
227
  # Signal settings
227
228
  @signal_enabled = true
228
- @signal_url = ENV['SIGNAL_URL'] || 'https://signal.brainzlab.ai'
229
+ @signal_url = ENV['SIGNAL_URL'] || detect_product_url('signal')
229
230
  @signal_api_key = ENV.fetch('SIGNAL_API_KEY', nil)
230
231
  @signal_master_key = ENV.fetch('SIGNAL_MASTER_KEY', nil)
231
232
  @signal_auto_provision = true
@@ -351,6 +352,11 @@ module BrainzLab
351
352
  @devtools_asset_path = '/__brainzlab__'
352
353
  @devtools_panel_position = 'bottom-right'
353
354
  @devtools_expand_by_default = false
355
+
356
+ # Rails instrumentation delegation
357
+ # When true, brainzlab-rails gem handles Rails-specific instrumentation
358
+ # SDK will only install non-Rails instrumentation (HTTP clients, Redis, etc.)
359
+ @rails_instrumentation_handled_externally = false
354
360
  end
355
361
 
356
362
  def recall_min_level=(level)
@@ -571,5 +577,21 @@ module BrainzLab
571
577
  rescue StandardError
572
578
  nil
573
579
  end
580
+
581
+ def detect_product_url(product)
582
+ # In development, use .localhost domains (works with Traefik)
583
+ # In production, use the real brainzlab.ai domains
584
+ if development?
585
+ "http://#{product}.localhost"
586
+ else
587
+ "https://#{product}.brainzlab.ai"
588
+ end
589
+ end
590
+
591
+ def development?
592
+ @environment == 'development' ||
593
+ ENV['RAILS_ENV'] == 'development' ||
594
+ ENV['RACK_ENV'] == 'development'
595
+ end
574
596
  end
575
597
  end
@@ -3,54 +3,121 @@
3
3
  require 'net/http'
4
4
  require 'json'
5
5
  require 'uri'
6
+ require 'fileutils'
6
7
 
7
8
  module BrainzLab
8
9
  module Flux
9
10
  class Provisioner
11
+ CACHE_DIR = ENV.fetch('BRAINZLAB_CACHE_DIR') { File.join(Dir.home, '.brainzlab') }
12
+
10
13
  def initialize(config)
11
14
  @config = config
12
15
  end
13
16
 
14
17
  def ensure_project!
15
- return if (@config.flux_ingest_key && !@config.flux_ingest_key.to_s.empty?) ||
16
- (@config.flux_api_key && !@config.flux_api_key.to_s.empty?)
17
- return unless @config.flux_url && !@config.flux_url.to_s.empty?
18
- return unless @config.secret_key && !@config.secret_key.to_s.empty?
18
+ return unless should_provision?
19
19
 
20
- BrainzLab.debug_log('[Flux] Auto-provisioning project...')
21
- provision_project
20
+ # Try cached credentials first
21
+ if (cached = load_cached_credentials)
22
+ apply_credentials(cached)
23
+ return cached
24
+ end
25
+
26
+ # Provision new project
27
+ project = provision_project
28
+ return unless project
29
+
30
+ # Cache and apply credentials
31
+ cache_credentials(project)
32
+ apply_credentials(project)
33
+
34
+ project
22
35
  end
23
36
 
24
37
  private
25
38
 
39
+ def should_provision?
40
+ # Already have credentials
41
+ return false if @config.flux_ingest_key.to_s.strip.length.positive?
42
+ return false if @config.flux_api_key.to_s.strip.length.positive?
43
+
44
+ # Need auto_provision enabled
45
+ return false unless @config.flux_auto_provision
46
+
47
+ # Need app_name for project name
48
+ return false unless @config.app_name.to_s.strip.length.positive?
49
+
50
+ # Need master key for provisioning
51
+ return false unless @config.flux_master_key.to_s.strip.length.positive?
52
+
53
+ # Need flux_url
54
+ return false unless @config.flux_url.to_s.strip.length.positive?
55
+
56
+ true
57
+ end
58
+
26
59
  def provision_project
60
+ BrainzLab.debug_log('[Flux] Auto-provisioning project...')
61
+
27
62
  uri = URI.parse("#{@config.flux_url}/api/v1/projects/provision")
28
63
  http = Net::HTTP.new(uri.host, uri.port)
29
64
  http.use_ssl = uri.scheme == 'https'
30
- http.open_timeout = 10
31
- http.read_timeout = 30
65
+ http.open_timeout = 5
66
+ http.read_timeout = 10
32
67
 
33
68
  request = Net::HTTP::Post.new(uri.path)
34
69
  request['Content-Type'] = 'application/json'
35
- request['Authorization'] = "Bearer #{@config.secret_key}"
70
+ request['X-Master-Key'] = @config.flux_master_key
36
71
  request['User-Agent'] = "brainzlab-sdk/#{BrainzLab::VERSION}"
37
72
  request.body = {
38
- name: @config.service || 'default',
73
+ name: @config.app_name,
39
74
  environment: @config.environment
40
75
  }.to_json
41
76
 
42
77
  response = http.request(request)
43
78
 
44
79
  if response.is_a?(Net::HTTPSuccess)
45
- data = JSON.parse(response.body)
46
- @config.flux_ingest_key = data['ingest_key']
47
- @config.flux_api_key = data['api_key']
80
+ data = JSON.parse(response.body, symbolize_names: true)
48
81
  BrainzLab.debug_log('[Flux] Project provisioned successfully')
82
+ data
49
83
  else
50
84
  BrainzLab.debug_log("[Flux] Provisioning failed: #{response.code} - #{response.body}")
85
+ nil
51
86
  end
52
87
  rescue StandardError => e
53
88
  BrainzLab.debug_log("[Flux] Provisioning error: #{e.message}")
89
+ nil
90
+ end
91
+
92
+ def load_cached_credentials
93
+ path = cache_file_path
94
+ return nil unless File.exist?(path)
95
+
96
+ data = JSON.parse(File.read(path), symbolize_names: true)
97
+
98
+ # Validate cached data has required keys
99
+ return nil unless data[:ingest_key]
100
+
101
+ data
102
+ rescue StandardError => e
103
+ BrainzLab.debug_log("[Flux] Failed to load cached credentials: #{e.message}")
104
+ nil
105
+ end
106
+
107
+ def cache_credentials(project)
108
+ FileUtils.mkdir_p(CACHE_DIR)
109
+ File.write(cache_file_path, JSON.generate(project))
110
+ rescue StandardError => e
111
+ BrainzLab.debug_log("[Flux] Failed to cache credentials: #{e.message}")
112
+ end
113
+
114
+ def cache_file_path
115
+ File.join(CACHE_DIR, "#{@config.app_name}.flux.json")
116
+ end
117
+
118
+ def apply_credentials(project)
119
+ @config.flux_ingest_key = project[:ingest_key]
120
+ @config.flux_api_key = project[:api_key]
54
121
  end
55
122
  end
56
123
  end
@@ -6,6 +6,14 @@ module BrainzLab
6
6
  def install!
7
7
  config = BrainzLab.configuration
8
8
 
9
+ # Skip Rails-specific instrumentation if brainzlab-rails gem is handling it
10
+ # This prevents double-tracking of events
11
+ if config.rails_instrumentation_handled_externally
12
+ BrainzLab.debug_log('[Instrumentation] Rails instrumentation handled by brainzlab-rails gem, skipping SDK instrumentation')
13
+ install_non_rails_instrumentation!(config)
14
+ return
15
+ end
16
+
9
17
  # HTTP client instrumentation
10
18
  if config.instrument_http
11
19
  install_net_http!
@@ -289,6 +297,58 @@ module BrainzLab
289
297
  install_faraday!
290
298
  install_httparty!
291
299
  end
300
+
301
+ private
302
+
303
+ # Install only non-Rails-specific instrumentation
304
+ # Used when brainzlab-rails gem handles Rails events via ActiveSupport::Notifications
305
+ def install_non_rails_instrumentation!(config)
306
+ # HTTP client instrumentation (not Rails-specific)
307
+ if config.instrument_http
308
+ install_net_http!
309
+ install_faraday!
310
+ install_httparty!
311
+ end
312
+
313
+ # Redis instrumentation (not Rails-specific)
314
+ install_redis! if config.instrument_redis
315
+
316
+ # Sidekiq instrumentation (not Rails-specific, has its own hooks)
317
+ install_sidekiq! if config.instrument_sidekiq
318
+
319
+ # GraphQL instrumentation (not Rails-specific)
320
+ install_graphql! if config.instrument_graphql
321
+
322
+ # MongoDB instrumentation (not Rails-specific)
323
+ install_mongodb! if config.instrument_mongodb
324
+
325
+ # Elasticsearch instrumentation (not Rails-specific)
326
+ install_elasticsearch! if config.instrument_elasticsearch
327
+
328
+ # Delayed::Job instrumentation (not Rails-specific, has its own hooks)
329
+ install_delayed_job! if config.instrument_delayed_job
330
+
331
+ # Grape API instrumentation (not Rails-specific)
332
+ install_grape! if config.instrument_grape
333
+
334
+ # Modern job queue instrumentation (have their own hooks)
335
+ install_solid_queue! if config.instrument_solid_queue
336
+ install_good_job! if config.instrument_good_job
337
+ install_resque! if config.instrument_resque
338
+
339
+ # Additional HTTP clients (not Rails-specific)
340
+ install_excon! if config.instrument_excon
341
+ install_typhoeus! if config.instrument_typhoeus
342
+
343
+ # Dalli/Memcached (not Rails-specific, has its own hooks)
344
+ install_dalli! if config.instrument_dalli
345
+
346
+ # Cloud & Payment (not Rails-specific)
347
+ install_aws! if config.instrument_aws
348
+ install_stripe! if config.instrument_stripe
349
+
350
+ BrainzLab.debug_log('[Instrumentation] Non-Rails instrumentation installed (HTTP, Redis, Sidekiq, GraphQL, etc.)')
351
+ end
292
352
  end
293
353
  end
294
354
  end
@@ -97,19 +97,43 @@ module BrainzLab
97
97
  ensure_provisioned!
98
98
  return unless BrainzLab.configuration.pulse_valid?
99
99
 
100
+ # Parse timestamp or use current time
101
+ started_at = if timestamp
102
+ Time.parse(timestamp) rescue Time.now.utc
103
+ else
104
+ Time.now.utc
105
+ end
106
+
100
107
  span_data = {
108
+ span_id: SecureRandom.uuid,
101
109
  name: name,
102
- category: category,
110
+ kind: category,
111
+ started_at: started_at,
112
+ ended_at: started_at, # Same as started_at since we only have duration
103
113
  duration_ms: duration_ms,
104
- timestamp: timestamp || Time.now.utc.iso8601(3),
105
- attributes: attributes,
106
- environment: BrainzLab.configuration.environment,
107
- service: BrainzLab.configuration.service,
108
- host: BrainzLab.configuration.host,
109
- request_id: Context.current.request_id
110
- }.compact
114
+ error: false,
115
+ data: attributes
116
+ }
111
117
 
112
- client.send_span(span_data)
118
+ # If there's an active trace, add the span to it (will be sent with finish_trace)
119
+ # Otherwise, send it directly to the API as a standalone span
120
+ if tracer.current_trace
121
+ tracer.current_spans << span_data
122
+ else
123
+ # Send as standalone span (backward compatibility)
124
+ api_span_data = {
125
+ name: name,
126
+ category: category,
127
+ duration_ms: duration_ms,
128
+ timestamp: timestamp || Time.now.utc.iso8601(3),
129
+ attributes: attributes,
130
+ environment: BrainzLab.configuration.environment,
131
+ service: BrainzLab.configuration.service,
132
+ host: BrainzLab.configuration.host,
133
+ request_id: Context.current.request_id
134
+ }.compact
135
+ client.send_span(api_span_data)
136
+ end
113
137
  end
114
138
 
115
139
  def ensure_provisioned!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BrainzLab
4
- VERSION = '0.1.5'
4
+ VERSION = '0.1.7'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainzlab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brainz Lab