fluyenta-ruby 0.1.14

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 (121) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +11 -0
  4. data/README.md +571 -0
  5. data/lib/brainzlab/beacon/client.rb +227 -0
  6. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  7. data/lib/brainzlab/beacon.rb +215 -0
  8. data/lib/brainzlab/configuration.rb +676 -0
  9. data/lib/brainzlab/context.rb +90 -0
  10. data/lib/brainzlab/cortex/cache.rb +59 -0
  11. data/lib/brainzlab/cortex/client.rb +159 -0
  12. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  13. data/lib/brainzlab/cortex.rb +223 -0
  14. data/lib/brainzlab/debug.rb +305 -0
  15. data/lib/brainzlab/dendrite/client.rb +250 -0
  16. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  17. data/lib/brainzlab/dendrite.rb +195 -0
  18. data/lib/brainzlab/development/logger.rb +150 -0
  19. data/lib/brainzlab/development/store.rb +121 -0
  20. data/lib/brainzlab/development.rb +72 -0
  21. data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
  22. data/lib/brainzlab/devtools/assets/devtools.js +396 -0
  23. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  24. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -0
  25. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  26. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  27. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  28. data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
  29. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  30. data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
  31. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
  32. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
  33. data/lib/brainzlab/devtools.rb +75 -0
  34. data/lib/brainzlab/errors.rb +490 -0
  35. data/lib/brainzlab/flux/buffer.rb +96 -0
  36. data/lib/brainzlab/flux/client.rb +68 -0
  37. data/lib/brainzlab/flux/provisioner.rb +124 -0
  38. data/lib/brainzlab/flux.rb +184 -0
  39. data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
  40. data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
  41. data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
  42. data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
  43. data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
  44. data/lib/brainzlab/instrumentation/action_view.rb +380 -0
  45. data/lib/brainzlab/instrumentation/active_job.rb +569 -0
  46. data/lib/brainzlab/instrumentation/active_record.rb +559 -0
  47. data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
  48. data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
  49. data/lib/brainzlab/instrumentation/aws.rb +183 -0
  50. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  51. data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
  52. data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
  53. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  54. data/lib/brainzlab/instrumentation/faraday.rb +181 -0
  55. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  56. data/lib/brainzlab/instrumentation/grape.rb +293 -0
  57. data/lib/brainzlab/instrumentation/graphql.rb +252 -0
  58. data/lib/brainzlab/instrumentation/httparty.rb +193 -0
  59. data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
  60. data/lib/brainzlab/instrumentation/net_http.rb +114 -0
  61. data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
  62. data/lib/brainzlab/instrumentation/railties.rb +134 -0
  63. data/lib/brainzlab/instrumentation/redis.rb +324 -0
  64. data/lib/brainzlab/instrumentation/resque.rb +114 -0
  65. data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
  66. data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
  67. data/lib/brainzlab/instrumentation/stripe.rb +163 -0
  68. data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
  69. data/lib/brainzlab/instrumentation.rb +360 -0
  70. data/lib/brainzlab/nerve/client.rb +235 -0
  71. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  72. data/lib/brainzlab/nerve.rb +219 -0
  73. data/lib/brainzlab/pulse/client.rb +203 -0
  74. data/lib/brainzlab/pulse/instrumentation.rb +401 -0
  75. data/lib/brainzlab/pulse/propagation.rb +241 -0
  76. data/lib/brainzlab/pulse/provisioner.rb +114 -0
  77. data/lib/brainzlab/pulse/tracer.rb +111 -0
  78. data/lib/brainzlab/pulse.rb +294 -0
  79. data/lib/brainzlab/rails/log_formatter.rb +807 -0
  80. data/lib/brainzlab/rails/log_subscriber.rb +334 -0
  81. data/lib/brainzlab/rails/railtie.rb +606 -0
  82. data/lib/brainzlab/recall/buffer.rb +66 -0
  83. data/lib/brainzlab/recall/client.rb +158 -0
  84. data/lib/brainzlab/recall/logger.rb +116 -0
  85. data/lib/brainzlab/recall/provisioner.rb +130 -0
  86. data/lib/brainzlab/recall.rb +175 -0
  87. data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
  88. data/lib/brainzlab/reflex/client.rb +150 -0
  89. data/lib/brainzlab/reflex/provisioner.rb +116 -0
  90. data/lib/brainzlab/reflex.rb +421 -0
  91. data/lib/brainzlab/sentinel/client.rb +236 -0
  92. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  93. data/lib/brainzlab/sentinel.rb +165 -0
  94. data/lib/brainzlab/signal/client.rb +60 -0
  95. data/lib/brainzlab/signal/provisioner.rb +115 -0
  96. data/lib/brainzlab/signal.rb +136 -0
  97. data/lib/brainzlab/synapse/client.rb +308 -0
  98. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  99. data/lib/brainzlab/synapse.rb +270 -0
  100. data/lib/brainzlab/testing/event_store.rb +377 -0
  101. data/lib/brainzlab/testing/helpers.rb +650 -0
  102. data/lib/brainzlab/testing/matchers.rb +391 -0
  103. data/lib/brainzlab/testing.rb +327 -0
  104. data/lib/brainzlab/utilities/circuit_breaker.rb +290 -0
  105. data/lib/brainzlab/utilities/health_check.rb +294 -0
  106. data/lib/brainzlab/utilities/log_formatter.rb +254 -0
  107. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  108. data/lib/brainzlab/utilities.rb +17 -0
  109. data/lib/brainzlab/vault/cache.rb +80 -0
  110. data/lib/brainzlab/vault/client.rb +216 -0
  111. data/lib/brainzlab/vault/provisioner.rb +49 -0
  112. data/lib/brainzlab/vault.rb +262 -0
  113. data/lib/brainzlab/version.rb +5 -0
  114. data/lib/brainzlab/vision/client.rb +175 -0
  115. data/lib/brainzlab/vision/provisioner.rb +136 -0
  116. data/lib/brainzlab/vision.rb +155 -0
  117. data/lib/brainzlab-sdk.rb +3 -0
  118. data/lib/brainzlab.rb +306 -0
  119. data/lib/generators/brainzlab/install/install_generator.rb +63 -0
  120. data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
  121. metadata +251 -0
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Nerve
5
+ class Provisioner
6
+ def initialize(config)
7
+ @config = config
8
+ @provisioned = false
9
+ end
10
+
11
+ def ensure_project!
12
+ return if @provisioned
13
+ return unless @config.nerve_auto_provision
14
+ return unless valid_auth?
15
+
16
+ @provisioned = true
17
+
18
+ project_id = detect_project_id
19
+ return unless project_id
20
+
21
+ client = Client.new(@config)
22
+ client.provision(
23
+ project_id: project_id,
24
+ app_name: @config.app_name || @config.service
25
+ )
26
+
27
+ BrainzLab.debug_log("[Nerve::Provisioner] Project provisioned: #{project_id}")
28
+ rescue StandardError => e
29
+ BrainzLab.debug_log("[Nerve::Provisioner] Provisioning failed: #{e.message}")
30
+ end
31
+
32
+ private
33
+
34
+ def valid_auth?
35
+ key = @config.nerve_api_key || @config.nerve_master_key || @config.secret_key
36
+ !key.nil? && !key.empty?
37
+ end
38
+
39
+ def detect_project_id
40
+ ENV.fetch('BRAINZLAB_PROJECT_ID', nil)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -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
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+
7
+ module BrainzLab
8
+ module Pulse
9
+ class Client
10
+ MAX_RETRIES = 3
11
+ RETRY_DELAY = 0.5
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ @buffer = []
16
+ @mutex = Mutex.new
17
+ @flush_thread = nil
18
+ end
19
+
20
+ def send_trace(payload)
21
+ return unless @config.pulse_enabled && @config.pulse_valid?
22
+
23
+ if @config.pulse_buffer_size > 1
24
+ buffer_trace(payload)
25
+ else
26
+ post('/api/v1/traces', payload)
27
+ end
28
+ end
29
+
30
+ def send_batch(payloads)
31
+ return unless @config.pulse_enabled && @config.pulse_valid?
32
+ return if payloads.empty?
33
+
34
+ post('/api/v1/traces/batch', { traces: payloads })
35
+ end
36
+
37
+ def send_metric(payload)
38
+ return unless @config.pulse_enabled && @config.pulse_valid?
39
+
40
+ post('/api/v1/metrics', payload)
41
+ end
42
+
43
+ def send_span(payload)
44
+ return unless @config.pulse_enabled && @config.pulse_valid?
45
+
46
+ post('/api/v1/spans', payload)
47
+ end
48
+
49
+ def flush
50
+ traces_to_send = nil
51
+
52
+ @mutex.synchronize do
53
+ return if @buffer.empty?
54
+
55
+ traces_to_send = @buffer.dup
56
+ @buffer.clear
57
+ end
58
+
59
+ send_batch(traces_to_send) if traces_to_send&.any?
60
+ end
61
+
62
+ private
63
+
64
+ def buffer_trace(payload)
65
+ should_flush = false
66
+
67
+ @mutex.synchronize do
68
+ @buffer << payload
69
+ should_flush = @buffer.size >= @config.pulse_buffer_size
70
+ end
71
+
72
+ start_flush_timer unless @flush_thread&.alive?
73
+ flush if should_flush
74
+ end
75
+
76
+ def start_flush_timer
77
+ @flush_thread = Thread.new do
78
+ loop do
79
+ sleep(@config.pulse_flush_interval)
80
+ flush
81
+ end
82
+ end
83
+ end
84
+
85
+ def post(path, body)
86
+ uri = URI.join(@config.pulse_url, path)
87
+
88
+ # Call on_send callback if configured
89
+ invoke_on_send(:pulse, :post, path, body)
90
+
91
+ # Log debug output for request
92
+ log_debug_request(path, body)
93
+
94
+ request = Net::HTTP::Post.new(uri)
95
+ request['Content-Type'] = 'application/json'
96
+ request['Authorization'] = "Bearer #{@config.pulse_auth_key}"
97
+ request['User-Agent'] = "brainzlab-sdk-ruby/#{BrainzLab::VERSION}"
98
+ request.body = JSON.generate(body)
99
+
100
+ execute_with_retry(uri, request, path)
101
+ rescue StandardError => e
102
+ handle_error(e, context: { path: path, body_size: body.to_s.length })
103
+ nil
104
+ end
105
+
106
+ def execute_with_retry(uri, request, path)
107
+ retries = 0
108
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
109
+
110
+ begin
111
+ http = Net::HTTP.new(uri.host, uri.port)
112
+ http.use_ssl = uri.scheme == 'https'
113
+ http.open_timeout = 5
114
+ http.read_timeout = 10
115
+
116
+ response = http.request(request)
117
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
118
+
119
+ # Log debug output for response
120
+ log_debug_response(response.code.to_i, duration_ms)
121
+
122
+ case response.code.to_i
123
+ when 200..299
124
+ begin
125
+ JSON.parse(response.body)
126
+ rescue StandardError
127
+ {}
128
+ end
129
+ when 429, 500..599
130
+ raise RetryableError, "Server error: #{response.code}"
131
+ else
132
+ handle_error(
133
+ StandardError.new("Pulse API error: #{response.code}"),
134
+ context: { path: path, status: response.code, body: response.body }
135
+ )
136
+ nil
137
+ end
138
+ rescue RetryableError, Net::OpenTimeout, Net::ReadTimeout => e
139
+ retries += 1
140
+ if retries <= MAX_RETRIES
141
+ sleep(RETRY_DELAY * retries)
142
+ retry
143
+ end
144
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
145
+ log_debug_response(0, duration_ms, error: e.message)
146
+ handle_error(e, context: { path: path, retries: retries })
147
+ nil
148
+ end
149
+ end
150
+
151
+ def log_debug_request(path, body)
152
+ return unless BrainzLab::Debug.enabled?
153
+
154
+ data = if body.is_a?(Hash) && body[:traces]
155
+ { count: body[:traces].size }
156
+ elsif body.is_a?(Hash) && body[:name]
157
+ { name: body[:name] }
158
+ else
159
+ {}
160
+ end
161
+
162
+ BrainzLab::Debug.log_request(:pulse, 'POST', path, data: data)
163
+ end
164
+
165
+ def log_debug_response(status, duration_ms, error: nil)
166
+ return unless BrainzLab::Debug.enabled?
167
+
168
+ BrainzLab::Debug.log_response(:pulse, status, duration_ms, error: error)
169
+ end
170
+
171
+ def invoke_on_send(service, method, path, payload)
172
+ return unless @config.on_send
173
+
174
+ @config.on_send.call(service, method, path, payload)
175
+ rescue StandardError => e
176
+ # Don't let callback errors break the SDK
177
+ log_error("on_send callback error: #{e.message}")
178
+ end
179
+
180
+ def handle_error(error, context: {})
181
+ log_error("#{error.message}")
182
+
183
+ # Call on_error callback if configured
184
+ return unless @config.on_error
185
+
186
+ @config.on_error.call(error, context.merge(service: :pulse))
187
+ rescue StandardError => e
188
+ # Don't let callback errors break the SDK
189
+ log_error("on_error callback error: #{e.message}")
190
+ end
191
+
192
+ def log_error(message)
193
+ BrainzLab::Debug.log(message, level: :error) if BrainzLab::Debug.enabled?
194
+
195
+ return unless @config.logger
196
+
197
+ @config.logger.error("[BrainzLab::Pulse] #{message}")
198
+ end
199
+
200
+ class RetryableError < StandardError; end
201
+ end
202
+ end
203
+ end