brainzlab 0.1.0

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +52 -0
  3. data/LICENSE +26 -0
  4. data/README.md +311 -0
  5. data/lib/brainzlab/configuration.rb +215 -0
  6. data/lib/brainzlab/context.rb +91 -0
  7. data/lib/brainzlab/instrumentation/action_mailer.rb +181 -0
  8. data/lib/brainzlab/instrumentation/active_record.rb +111 -0
  9. data/lib/brainzlab/instrumentation/delayed_job.rb +236 -0
  10. data/lib/brainzlab/instrumentation/elasticsearch.rb +210 -0
  11. data/lib/brainzlab/instrumentation/faraday.rb +182 -0
  12. data/lib/brainzlab/instrumentation/grape.rb +293 -0
  13. data/lib/brainzlab/instrumentation/graphql.rb +251 -0
  14. data/lib/brainzlab/instrumentation/httparty.rb +194 -0
  15. data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
  16. data/lib/brainzlab/instrumentation/net_http.rb +109 -0
  17. data/lib/brainzlab/instrumentation/redis.rb +331 -0
  18. data/lib/brainzlab/instrumentation/sidekiq.rb +264 -0
  19. data/lib/brainzlab/instrumentation.rb +132 -0
  20. data/lib/brainzlab/pulse/client.rb +132 -0
  21. data/lib/brainzlab/pulse/instrumentation.rb +364 -0
  22. data/lib/brainzlab/pulse/propagation.rb +241 -0
  23. data/lib/brainzlab/pulse/provisioner.rb +114 -0
  24. data/lib/brainzlab/pulse/tracer.rb +111 -0
  25. data/lib/brainzlab/pulse.rb +224 -0
  26. data/lib/brainzlab/rails/log_formatter.rb +801 -0
  27. data/lib/brainzlab/rails/log_subscriber.rb +341 -0
  28. data/lib/brainzlab/rails/railtie.rb +590 -0
  29. data/lib/brainzlab/recall/buffer.rb +64 -0
  30. data/lib/brainzlab/recall/client.rb +86 -0
  31. data/lib/brainzlab/recall/logger.rb +118 -0
  32. data/lib/brainzlab/recall/provisioner.rb +113 -0
  33. data/lib/brainzlab/recall.rb +155 -0
  34. data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
  35. data/lib/brainzlab/reflex/client.rb +85 -0
  36. data/lib/brainzlab/reflex/provisioner.rb +116 -0
  37. data/lib/brainzlab/reflex.rb +374 -0
  38. data/lib/brainzlab/version.rb +5 -0
  39. data/lib/brainzlab-sdk.rb +3 -0
  40. data/lib/brainzlab.rb +140 -0
  41. data/lib/generators/brainzlab/install/install_generator.rb +61 -0
  42. data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
  43. metadata +159 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dea1ca28ddfd1614e67dd24d5a8cb36663e4cf3b75d00dabfa9ff9e01d95e2cc
4
+ data.tar.gz: 753cf41c654d57476fdcd915e1bc088c0fe9a2242cf4464431b520c375012884
5
+ SHA512:
6
+ metadata.gz: 91382c001767b6f0ca913cb5e12956d7a3664d0a098c94540948796d89db44e6b08c12a5fa9bdf8666c5909822d97bfda68638aaf20de945614a0ee329ed20e5
7
+ data.tar.gz: ec17f7bf47291eb1cb00f0173972e866f61634d21d4608e432f7412c866273abb975cd6e9e32a50022377129c489f6819edd21fcbed3d6de6e753113fb79cd0d
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2025-01-01
6
+
7
+ ### Added
8
+
9
+ - **Recall** - Structured logging
10
+ - Log levels (debug, info, warn, error, fatal)
11
+ - Buffered batch sending
12
+ - Auto-provisioning support
13
+ - Rails integration
14
+
15
+ - **Reflex** - Error tracking
16
+ - Exception capturing with context
17
+ - Breadcrumbs support
18
+ - User context tracking
19
+ - Sample rate and before_send hooks
20
+ - Custom fingerprinting
21
+ - Auto-provisioning support
22
+
23
+ - **Pulse** - APM & Distributed Tracing
24
+ - Request tracing with breakdown
25
+ - Distributed tracing (W3C Trace Context + B3)
26
+ - Auto-provisioning support
27
+
28
+ - **Instrumentation** (13 libraries)
29
+ - Rails/Rack middleware
30
+ - Active Record (SQL queries)
31
+ - Net::HTTP
32
+ - Faraday
33
+ - HTTParty
34
+ - Redis (v4 and v5+)
35
+ - Sidekiq (server and client middleware)
36
+ - Delayed::Job
37
+ - GraphQL (query and field tracing)
38
+ - Grape API
39
+ - MongoDB/Mongoid
40
+ - Elasticsearch/OpenSearch
41
+ - ActionMailer
42
+
43
+ - **Configuration**
44
+ - Environment variable support
45
+ - Per-product enable/disable
46
+ - Sensitive field scrubbing
47
+ - Debug mode
48
+
49
+ - **Rails Integration**
50
+ - Automatic setup via Railtie
51
+ - Request context propagation
52
+ - User context from current_user
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Ossassy License
2
+
3
+ Copyright (c) 2025, Brainz Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ No licensee or downstream recipient may use the Software (including any modified
16
+ or derivative versions) to directly compete with the original Licensor by
17
+ offering it to third parties as a hosted, managed, or Software-as-a-Service
18
+ (SaaS) product or cloud service where the primary value of the service is the
19
+ functionality of the Software itself.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
23
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
24
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,311 @@
1
+ # BrainzLab Ruby SDK
2
+
3
+ Official Ruby SDK for [BrainzLab](https://brainzlab.ai) - the complete observability platform.
4
+
5
+ - **Recall** - Structured logging
6
+ - **Reflex** - Error tracking
7
+ - **Pulse** - APM & distributed tracing
8
+
9
+ ## Installation
10
+
11
+ Add to your Gemfile:
12
+
13
+ ```ruby
14
+ gem 'brainzlab'
15
+ ```
16
+
17
+ Then run:
18
+
19
+ ```bash
20
+ bundle install
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Configuration
26
+
27
+ ```ruby
28
+ # config/initializers/brainzlab.rb
29
+ BrainzLab.configure do |config|
30
+ # Authentication (required)
31
+ config.secret_key = ENV['BRAINZLAB_SECRET_KEY']
32
+
33
+ # Environment
34
+ config.environment = Rails.env
35
+ config.service = 'my-app'
36
+
37
+ # Enable/disable products
38
+ config.recall_enabled = true # Logging
39
+ config.reflex_enabled = true # Error tracking
40
+ config.pulse_enabled = true # APM
41
+
42
+ # Auto-provisioning (creates projects automatically)
43
+ config.recall_auto_provision = true
44
+ config.reflex_auto_provision = true
45
+ config.pulse_auto_provision = true
46
+ end
47
+ ```
48
+
49
+ ## Recall - Structured Logging
50
+
51
+ ```ruby
52
+ # Log levels
53
+ BrainzLab::Recall.debug("Debug message", details: "...")
54
+ BrainzLab::Recall.info("User signed up", user_id: user.id)
55
+ BrainzLab::Recall.warn("Rate limit approaching", current: 95, limit: 100)
56
+ BrainzLab::Recall.error("Payment failed", error: e.message, amount: 99.99)
57
+ BrainzLab::Recall.fatal("Database connection lost")
58
+
59
+ # With context
60
+ BrainzLab::Recall.info("Order created",
61
+ order_id: order.id,
62
+ user_id: user.id,
63
+ total: order.total,
64
+ items: order.items.count
65
+ )
66
+ ```
67
+
68
+ ### Configuration Options
69
+
70
+ ```ruby
71
+ config.recall_min_level = :info # Minimum log level (:debug, :info, :warn, :error, :fatal)
72
+ config.recall_buffer_size = 50 # Batch size before flush
73
+ config.recall_flush_interval = 5 # Seconds between flushes
74
+ ```
75
+
76
+ ## Reflex - Error Tracking
77
+
78
+ ```ruby
79
+ # Capture exceptions
80
+ begin
81
+ risky_operation
82
+ rescue => e
83
+ BrainzLab::Reflex.capture(e,
84
+ user_id: current_user.id,
85
+ order_id: order.id
86
+ )
87
+ end
88
+
89
+ # Add breadcrumbs for context
90
+ BrainzLab::Reflex.add_breadcrumb("User clicked checkout",
91
+ category: "ui.click",
92
+ data: { button: "checkout" }
93
+ )
94
+
95
+ # Set user context
96
+ BrainzLab::Reflex.set_user(
97
+ id: user.id,
98
+ email: user.email,
99
+ plan: user.plan
100
+ )
101
+
102
+ # Add tags
103
+ BrainzLab::Reflex.set_tags(
104
+ environment: "production",
105
+ region: "us-east-1"
106
+ )
107
+ ```
108
+
109
+ ### Configuration Options
110
+
111
+ ```ruby
112
+ config.reflex_excluded_exceptions = ['ActiveRecord::RecordNotFound']
113
+ config.reflex_sample_rate = 1.0 # 1.0 = 100%, 0.5 = 50%
114
+ config.reflex_before_send = ->(event) {
115
+ # Modify or filter events
116
+ event[:tags][:custom] = 'value'
117
+ event # Return nil to drop the event
118
+ }
119
+ ```
120
+
121
+ ## Pulse - APM & Distributed Tracing
122
+
123
+ Pulse automatically instruments your application to track performance.
124
+
125
+ ### Automatic Instrumentation
126
+
127
+ The SDK automatically instruments:
128
+
129
+ | Library | Description |
130
+ |---------|-------------|
131
+ | Rails/Rack | Request tracing with breakdown |
132
+ | Active Record | SQL queries with timing |
133
+ | Net::HTTP | Outbound HTTP calls |
134
+ | Faraday | HTTP client requests |
135
+ | HTTParty | HTTP client requests |
136
+ | Redis | Redis commands |
137
+ | Sidekiq | Background job processing |
138
+ | Delayed::Job | Background job processing |
139
+ | GraphQL | Query and field resolution |
140
+ | Grape | API endpoint tracing |
141
+ | MongoDB | Database operations |
142
+ | Elasticsearch | Search operations |
143
+ | ActionMailer | Email delivery |
144
+
145
+ ### Configuration Options
146
+
147
+ ```ruby
148
+ # Enable/disable specific instrumentations
149
+ config.instrument_http = true # Net::HTTP, Faraday, HTTParty
150
+ config.instrument_active_record = true # SQL queries
151
+ config.instrument_redis = true # Redis commands
152
+ config.instrument_sidekiq = true # Sidekiq jobs
153
+ config.instrument_graphql = true # GraphQL queries
154
+ config.instrument_mongodb = true # MongoDB operations
155
+ config.instrument_elasticsearch = true # Elasticsearch queries
156
+ config.instrument_action_mailer = true # Email delivery
157
+ config.instrument_delayed_job = true # Delayed::Job
158
+ config.instrument_grape = true # Grape API
159
+
160
+ # Filtering
161
+ config.http_ignore_hosts = ['localhost', '127.0.0.1']
162
+ config.redis_ignore_commands = ['ping', 'info']
163
+ config.pulse_excluded_paths = ['/health', '/ping', '/up', '/assets']
164
+ config.pulse_sample_rate = 1.0 # 1.0 = 100%
165
+ ```
166
+
167
+ ### Distributed Tracing
168
+
169
+ Pulse supports distributed tracing across services using W3C Trace Context and B3 propagation.
170
+
171
+ ```ruby
172
+ # Extracting trace context from incoming requests (automatic in Rails)
173
+ context = BrainzLab::Pulse.extract!(request.headers)
174
+
175
+ # Injecting trace context into outgoing requests (automatic with instrumentation)
176
+ BrainzLab::Pulse.inject!(headers)
177
+ ```
178
+
179
+ ### Custom Spans
180
+
181
+ ```ruby
182
+ BrainzLab::Pulse.trace("process_payment", kind: "payment") do |span|
183
+ span[:data] = { amount: 99.99, currency: "USD" }
184
+ process_payment(order)
185
+ end
186
+ ```
187
+
188
+ ## Rails Integration
189
+
190
+ The SDK automatically integrates with Rails when loaded:
191
+
192
+ - Request context (request_id, path, method, params)
193
+ - Exception reporting to Reflex
194
+ - Performance tracing with Pulse
195
+ - User context from `current_user`
196
+
197
+ ### Setting User Context
198
+
199
+ ```ruby
200
+ class ApplicationController < ActionController::Base
201
+ before_action :set_brainzlab_context
202
+
203
+ private
204
+
205
+ def set_brainzlab_context
206
+ if current_user
207
+ BrainzLab.set_user(
208
+ id: current_user.id,
209
+ email: current_user.email,
210
+ name: current_user.name
211
+ )
212
+ end
213
+ end
214
+ end
215
+ ```
216
+
217
+ ## Sidekiq Integration
218
+
219
+ For Sidekiq, the SDK automatically:
220
+
221
+ - Traces job execution with queue wait time
222
+ - Propagates trace context between web and worker
223
+ - Captures job failures to Reflex
224
+
225
+ ```ruby
226
+ # config/initializers/sidekiq.rb
227
+ # Instrumentation is automatic, but you can configure:
228
+
229
+ BrainzLab.configure do |config|
230
+ config.instrument_sidekiq = true
231
+ end
232
+ ```
233
+
234
+ ## Grape API Integration
235
+
236
+ For Grape APIs, you can use the middleware:
237
+
238
+ ```ruby
239
+ class API < Grape::API
240
+ use BrainzLab::Instrumentation::GrapeInstrumentation::Middleware
241
+
242
+ # Your API endpoints...
243
+ end
244
+ ```
245
+
246
+ ## GraphQL Integration
247
+
248
+ For GraphQL-Ruby 2.0+, add the tracer:
249
+
250
+ ```ruby
251
+ class MySchema < GraphQL::Schema
252
+ trace_with BrainzLab::Instrumentation::GraphQLInstrumentation::Tracer
253
+
254
+ # Your schema...
255
+ end
256
+ ```
257
+
258
+ ## Environment Variables
259
+
260
+ | Variable | Description |
261
+ |----------|-------------|
262
+ | `BRAINZLAB_SECRET_KEY` | API key for authentication |
263
+ | `BRAINZLAB_ENVIRONMENT` | Environment name (default: auto-detect) |
264
+ | `BRAINZLAB_SERVICE` | Service name |
265
+ | `BRAINZLAB_APP_NAME` | App name for auto-provisioning |
266
+ | `BRAINZLAB_DEBUG` | Enable debug logging (`true`/`false`) |
267
+ | `RECALL_URL` | Custom Recall endpoint |
268
+ | `REFLEX_URL` | Custom Reflex endpoint |
269
+ | `PULSE_URL` | Custom Pulse endpoint |
270
+
271
+ ## Scrubbing Sensitive Data
272
+
273
+ The SDK automatically scrubs sensitive fields:
274
+
275
+ ```ruby
276
+ config.scrub_fields = [:password, :password_confirmation, :token, :api_key, :secret]
277
+ ```
278
+
279
+ ## Debug Mode
280
+
281
+ Enable debug mode to see SDK activity:
282
+
283
+ ```ruby
284
+ config.debug = true
285
+ # Or set BRAINZLAB_DEBUG=true
286
+ ```
287
+
288
+ ## Self-Hosted
289
+
290
+ For self-hosted BrainzLab installations:
291
+
292
+ ```ruby
293
+ BrainzLab.configure do |config|
294
+ config.recall_url = 'https://recall.your-domain.com'
295
+ config.reflex_url = 'https://reflex.your-domain.com'
296
+ config.pulse_url = 'https://pulse.your-domain.com'
297
+ end
298
+ ```
299
+
300
+ ## Documentation
301
+
302
+ Full documentation: [docs.brainzlab.ai](https://docs.brainzlab.ai)
303
+
304
+ - [Installation Guide](https://docs.brainzlab.ai/sdk/ruby/installation)
305
+ - [Recall (Logging)](https://docs.brainzlab.ai/sdk/ruby/recall)
306
+ - [Reflex (Errors)](https://docs.brainzlab.ai/sdk/ruby/reflex)
307
+ - [Pulse (APM)](https://docs.brainzlab.ai/sdk/ruby/pulse)
308
+
309
+ ## License
310
+
311
+ Ossassy License - see [LICENSE](LICENSE) for details.
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ class Configuration
5
+ LEVELS = %i[debug info warn error fatal].freeze
6
+
7
+ attr_accessor :secret_key,
8
+ :environment,
9
+ :service,
10
+ :host,
11
+ :commit,
12
+ :branch,
13
+ :app_name,
14
+ :debug,
15
+ :recall_enabled,
16
+ :recall_url,
17
+ :recall_min_level,
18
+ :recall_buffer_size,
19
+ :recall_flush_interval,
20
+ :recall_master_key,
21
+ :recall_auto_provision,
22
+ :reflex_enabled,
23
+ :reflex_url,
24
+ :reflex_api_key,
25
+ :reflex_master_key,
26
+ :reflex_auto_provision,
27
+ :reflex_excluded_exceptions,
28
+ :reflex_before_send,
29
+ :reflex_sample_rate,
30
+ :reflex_fingerprint,
31
+ :pulse_enabled,
32
+ :pulse_url,
33
+ :pulse_api_key,
34
+ :pulse_master_key,
35
+ :pulse_auto_provision,
36
+ :pulse_buffer_size,
37
+ :pulse_flush_interval,
38
+ :pulse_sample_rate,
39
+ :pulse_excluded_paths,
40
+ :scrub_fields,
41
+ :logger,
42
+ :instrument_http,
43
+ :instrument_active_record,
44
+ :instrument_redis,
45
+ :instrument_sidekiq,
46
+ :instrument_graphql,
47
+ :instrument_mongodb,
48
+ :instrument_elasticsearch,
49
+ :instrument_action_mailer,
50
+ :instrument_delayed_job,
51
+ :instrument_grape,
52
+ :http_ignore_hosts,
53
+ :redis_ignore_commands,
54
+ :log_formatter_enabled,
55
+ :log_formatter_colors,
56
+ :log_formatter_hide_assets,
57
+ :log_formatter_compact_assets,
58
+ :log_formatter_show_params
59
+
60
+ def initialize
61
+ # Authentication
62
+ @secret_key = ENV["BRAINZLAB_SECRET_KEY"]
63
+
64
+ # Environment
65
+ @environment = ENV["BRAINZLAB_ENVIRONMENT"] || detect_environment
66
+ @service = ENV["BRAINZLAB_SERVICE"]
67
+ @host = ENV["BRAINZLAB_HOST"] || detect_host
68
+
69
+ # App name for auto-provisioning
70
+ @app_name = ENV["BRAINZLAB_APP_NAME"]
71
+
72
+ # Git context
73
+ @commit = ENV["GIT_COMMIT"] || ENV["COMMIT_SHA"] || detect_git_commit
74
+ @branch = ENV["GIT_BRANCH"] || ENV["BRANCH_NAME"] || detect_git_branch
75
+
76
+ # Debug mode - enables verbose logging
77
+ @debug = ENV["BRAINZLAB_DEBUG"] == "true"
78
+
79
+ # Recall settings
80
+ @recall_enabled = true
81
+ @recall_url = ENV["RECALL_URL"] || "https://recall.brainzlab.ai"
82
+ @recall_min_level = :debug
83
+ @recall_buffer_size = 50
84
+ @recall_flush_interval = 5
85
+ @recall_master_key = ENV["RECALL_MASTER_KEY"]
86
+ @recall_auto_provision = true
87
+
88
+ # Reflex settings
89
+ @reflex_enabled = true
90
+ @reflex_url = ENV["REFLEX_URL"] || "https://reflex.brainzlab.ai"
91
+ @reflex_api_key = ENV["REFLEX_API_KEY"]
92
+ @reflex_master_key = ENV["REFLEX_MASTER_KEY"]
93
+ @reflex_auto_provision = true
94
+ @reflex_excluded_exceptions = []
95
+ @reflex_before_send = nil
96
+ @reflex_sample_rate = nil
97
+ @reflex_fingerprint = nil # Custom fingerprint callback
98
+
99
+ # Pulse settings
100
+ @pulse_enabled = true
101
+ @pulse_url = ENV["PULSE_URL"] || "https://pulse.brainzlab.ai"
102
+ @pulse_api_key = ENV["PULSE_API_KEY"]
103
+ @pulse_master_key = ENV["PULSE_MASTER_KEY"]
104
+ @pulse_auto_provision = true
105
+ @pulse_buffer_size = 50
106
+ @pulse_flush_interval = 5
107
+ @pulse_sample_rate = nil
108
+ @pulse_excluded_paths = %w[/health /ping /up /assets]
109
+
110
+ # Filtering
111
+ @scrub_fields = %i[password password_confirmation token api_key secret]
112
+
113
+ # Internal logger for debugging SDK issues
114
+ @logger = nil
115
+
116
+ # Instrumentation
117
+ @instrument_http = true # Enable HTTP client instrumentation (Net::HTTP, Faraday, HTTParty)
118
+ @instrument_active_record = true # AR breadcrumbs for Reflex
119
+ @instrument_redis = true # Redis command instrumentation
120
+ @instrument_sidekiq = true # Sidekiq job instrumentation
121
+ @instrument_graphql = true # GraphQL query instrumentation
122
+ @instrument_mongodb = true # MongoDB/Mongoid instrumentation
123
+ @instrument_elasticsearch = true # Elasticsearch instrumentation
124
+ @instrument_action_mailer = true # ActionMailer instrumentation
125
+ @instrument_delayed_job = true # Delayed::Job instrumentation
126
+ @instrument_grape = true # Grape API instrumentation
127
+ @http_ignore_hosts = %w[localhost 127.0.0.1]
128
+ @redis_ignore_commands = %w[ping info] # Commands to skip tracking
129
+
130
+ # Log formatter settings
131
+ @log_formatter_enabled = true
132
+ @log_formatter_colors = nil # auto-detect TTY
133
+ @log_formatter_hide_assets = false
134
+ @log_formatter_compact_assets = true
135
+ @log_formatter_show_params = true
136
+ end
137
+
138
+ def recall_min_level=(level)
139
+ level = level.to_sym
140
+ raise ArgumentError, "Invalid level: #{level}" unless LEVELS.include?(level)
141
+
142
+ @recall_min_level = level
143
+ end
144
+
145
+ def level_enabled?(level)
146
+ LEVELS.index(level.to_sym) >= LEVELS.index(@recall_min_level)
147
+ end
148
+
149
+ def valid?
150
+ !@secret_key.nil? && !@secret_key.empty?
151
+ end
152
+
153
+ def reflex_valid?
154
+ key = reflex_api_key || secret_key
155
+ !key.nil? && !key.empty?
156
+ end
157
+
158
+ def reflex_auth_key
159
+ reflex_api_key || secret_key
160
+ end
161
+
162
+ def pulse_valid?
163
+ key = pulse_api_key || secret_key
164
+ !key.nil? && !key.empty?
165
+ end
166
+
167
+ def pulse_auth_key
168
+ pulse_api_key || secret_key
169
+ end
170
+
171
+ def debug?
172
+ @debug == true
173
+ end
174
+
175
+ def debug_log(message)
176
+ return unless debug?
177
+
178
+ log_message = "[BrainzLab::Debug] #{message}"
179
+ if logger
180
+ logger.debug(log_message)
181
+ else
182
+ $stderr.puts(log_message)
183
+ end
184
+ end
185
+
186
+ private
187
+
188
+ def detect_environment
189
+ return ::Rails.env.to_s if defined?(::Rails) && ::Rails.respond_to?(:env)
190
+ return ENV["RACK_ENV"] if ENV["RACK_ENV"]
191
+ return ENV["RUBY_ENV"] if ENV["RUBY_ENV"]
192
+
193
+ "development"
194
+ end
195
+
196
+ def detect_host
197
+ require "socket"
198
+ Socket.gethostname
199
+ rescue StandardError
200
+ nil
201
+ end
202
+
203
+ def detect_git_commit
204
+ `git rev-parse HEAD 2>/dev/null`.strip.presence
205
+ rescue StandardError
206
+ nil
207
+ end
208
+
209
+ def detect_git_branch
210
+ `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip.presence
211
+ rescue StandardError
212
+ nil
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ class Context
5
+ THREAD_KEY = :brainzlab_context
6
+
7
+ class << self
8
+ def current
9
+ Thread.current[THREAD_KEY] ||= new
10
+ end
11
+
12
+ def clear!
13
+ Thread.current[THREAD_KEY] = nil
14
+ end
15
+ end
16
+
17
+ attr_accessor :user, :request_id, :session_id
18
+ attr_accessor :request_method, :request_path, :request_url, :request_params, :request_headers
19
+ attr_accessor :controller, :action
20
+ attr_reader :extra, :tags, :breadcrumbs
21
+
22
+ def initialize
23
+ @user = {}
24
+ @extra = {}
25
+ @tags = {}
26
+ @request_id = nil
27
+ @session_id = nil
28
+ @request_method = nil
29
+ @request_path = nil
30
+ @request_url = nil
31
+ @request_params = nil
32
+ @request_headers = nil
33
+ @controller = nil
34
+ @action = nil
35
+ @stack = []
36
+ @breadcrumbs = Reflex::Breadcrumbs.new
37
+ end
38
+
39
+ def set_user(id: nil, email: nil, name: nil, **extra)
40
+ @user = { id: id, email: email, name: name }.compact.merge(extra)
41
+ end
42
+
43
+ def set_context(**data)
44
+ @extra.merge!(data)
45
+ end
46
+
47
+ def set_tags(**data)
48
+ @tags.merge!(data)
49
+ end
50
+
51
+ def with_context(**data)
52
+ push_context(data)
53
+ yield
54
+ ensure
55
+ pop_context
56
+ end
57
+
58
+ def to_hash
59
+ result = {}
60
+ result[:request_id] = @request_id if @request_id
61
+ result[:session_id] = @session_id if @session_id
62
+
63
+ merged_extra = @extra.dup
64
+ @stack.each { |ctx| merged_extra.merge!(ctx) }
65
+
66
+ result[:user] = @user unless @user.empty?
67
+ result[:tags] = @tags unless @tags.empty?
68
+ result[:context] = merged_extra unless merged_extra.empty?
69
+
70
+ result
71
+ end
72
+
73
+ def data_hash
74
+ merged = @extra.dup
75
+ @stack.each { |ctx| merged.merge!(ctx) }
76
+ merged[:user] = @user unless @user.empty?
77
+ merged[:tags] = @tags unless @tags.empty?
78
+ merged
79
+ end
80
+
81
+ private
82
+
83
+ def push_context(data)
84
+ @stack.push(data)
85
+ end
86
+
87
+ def pop_context
88
+ @stack.pop
89
+ end
90
+ end
91
+ end