activerabbit-ai 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b8f8ac811420383d702d6afd1b06bea0266006e2f3e68b0fe0873ecd328f526
4
- data.tar.gz: 52a687ed3adcf2b019eb0755c9dfc25fec336e18635213b4172229bc9cc7480c
3
+ metadata.gz: c2dcb677ef3f8500c8a55c7a26a61a8dbab9e89f89c6b24119905de6c94ee26c
4
+ data.tar.gz: c3af1e41fc2f0265fa9c4ba5073539a41f1e8f2ec6ac59ff921c782b78ef246a
5
5
  SHA512:
6
- metadata.gz: 882c69b7b28eb76cb7665901c37d06d87251b619583becceb017a3f026cc56b36023b804246a86c19f178212c5e87fa1b0a071aac897759f1d2b8d05ddc3f27e
7
- data.tar.gz: d334d8e3a69efd944a8f987d0043ad6cde91482bb82fa7effca1570cc18b1fa3b903747b386161fc5b18c534cd4439da83b34c4553e226320f83727922e0dd87
6
+ metadata.gz: aba0d27c8cb0a82718c02c24d18f4ec6ddc9311d8788fb5eaa78d0306a009c6dc04ed5fd349e1277684815705e37b64264483da0117905263b8c387237cf0f72
7
+ data.tar.gz: 890245267f014b27cba6d1d132fdbb491f78d7c9147ee817d1430bbeebe7e7bd37d6d784fbff4446ff03fa9c9787634ad26600f66e22213444b98e7996b5572e
data/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2025-01-03
11
+
12
+ ### 🚨 Major Rails Integration Improvements
13
+
14
+ This release fixes critical Rails integration issues that prevented reliable exception tracking in production environments.
15
+
16
+ ### Fixed
17
+ - **Rails Autoload**: Fixed missing Railtie autoload in main entry point (`lib/active_rabbit.rb`)
18
+ - **Exception Middleware**: Properly positioned before `ActionDispatch::ShowExceptions` to catch raw exceptions
19
+ - **Rescued Exceptions**: Fixed `process_action.action_controller` subscriber to catch Rails-rescued exceptions
20
+ - **Thread Cleanup**: Improved `Thread.current` cleanup in middleware with proper nested request handling
21
+ - **Time Dependencies**: Added missing `require "time"` for `iso8601` method across all modules
22
+
23
+ ### Added
24
+ - **Shutdown Hooks**: Added `at_exit` and `SIGTERM` handlers for graceful shutdown and data flushing
25
+ - **Enhanced Context**: Added timing data, sanitized headers, and better request context
26
+ - **Error Isolation**: All tracking operations now fail gracefully without breaking the application
27
+ - **Rails 7+ Support**: Handles both `exception_object` (Rails 7+) and legacy `exception` formats
28
+ - **Production Resilience**: Never blocks web requests, background sending with automatic retries
29
+
30
+ ### Improved
31
+ - **PII Scrubbing**: Enhanced parameter and header sanitization
32
+ - **Exception Fingerprinting**: Better grouping of similar exceptions
33
+ - **Middleware Safety**: Robust error handling prevents tracking failures from affecting requests
34
+ - **Test Coverage**: Added comprehensive Rails integration test suite
35
+
36
+ ### Technical Details
37
+ - Rack middleware now placed **before** Rails exception handling (critical for production)
38
+ - ActiveSupport::Notifications properly subscribed to catch rescued exceptions
39
+ - Background queue with timer-based flushing ensures data delivery
40
+ - Thread-safe context management with proper cleanup
41
+
42
+ This release makes ActiveRabbit production-ready for Rails applications with reliable exception tracking that works even when Rails rescues exceptions and renders error pages.
43
+
44
+ ## [0.1.2] - 2024-12-20
45
+
10
46
  ### Added
11
47
  - Initial release of ActiveRabbit Ruby client
12
48
  - Error tracking with detailed context and stack traces
Binary file
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent"
4
+ require "time"
4
5
 
5
6
  module ActiveRabbit
6
7
  module Client
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest"
4
+ require "time"
4
5
 
5
6
  module ActiveRabbit
6
7
  module Client
@@ -4,6 +4,7 @@ require "faraday"
4
4
  require "faraday/retry"
5
5
  require "json"
6
6
  require "concurrent"
7
+ require "time"
7
8
 
8
9
  module ActiveRabbit
9
10
  module Client
@@ -38,6 +39,16 @@ module ActiveRabbit
38
39
  make_request(:post, "api/v1/events/batch", { events: batch_data })
39
40
  end
40
41
 
42
+ def test_connection
43
+ response = make_request(:post, "api/v1/test/connection", {
44
+ gem_version: ActiveRabbit::Client::VERSION,
45
+ timestamp: Time.now.iso8601
46
+ })
47
+ { success: true, data: response }
48
+ rescue => e
49
+ { success: false, error: e.message }
50
+ end
51
+
41
52
  def flush
42
53
  return if @request_queue.empty?
43
54
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "time"
4
5
 
5
6
  module ActiveRabbit
6
7
  module Client
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "time"
4
+
3
5
  module ActiveRabbit
4
6
  module Client
5
7
  class PerformanceMonitor
@@ -1,6 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/railtie"
3
+ begin
4
+ require "rails/railtie"
5
+ rescue LoadError
6
+ # Rails not available, define minimal structure for testing
7
+ module Rails
8
+ class Railtie; end
9
+ end
10
+ end
11
+
4
12
  require "securerandom"
5
13
 
6
14
  module ActiveRabbit
@@ -49,6 +57,34 @@ module ActiveRabbit
49
57
  app.middleware.insert_before ActionDispatch::ShowExceptions, ExceptionMiddleware
50
58
  end
51
59
 
60
+ initializer "active_rabbit.setup_shutdown_hooks" do
61
+ next unless ActiveRabbit::Client.configured?
62
+
63
+ # Ensure we flush pending data on shutdown
64
+ at_exit do
65
+ begin
66
+ ActiveRabbit::Client.flush
67
+ rescue => e
68
+ # Don't let shutdown hooks fail the process
69
+ Rails.logger.error "[ActiveRabbit] Error during shutdown flush: #{e.message}" if defined?(Rails)
70
+ end
71
+ end
72
+
73
+ # Also flush on SIGTERM (common in production deployments)
74
+ if Signal.list.include?('TERM')
75
+ Signal.trap('TERM') do
76
+ begin
77
+ ActiveRabbit::Client.flush
78
+ rescue => e
79
+ # Log but don't raise
80
+ Rails.logger.error "[ActiveRabbit] Error during SIGTERM flush: #{e.message}" if defined?(Rails)
81
+ end
82
+ # Continue with normal SIGTERM handling
83
+ exit(0)
84
+ end
85
+ end
86
+ end
87
+
52
88
  private
53
89
 
54
90
  def setup_exception_tracking(app)
@@ -173,25 +209,30 @@ module ActiveRabbit
173
209
  end
174
210
  end
175
211
 
176
- def subscribe_to_exception_notifications
177
- # Subscribe to Rails exception notifications
212
+ def subscribe_to_exception_notifications
213
+ # Subscribe to Rails exception notifications for rescued exceptions
178
214
  ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
179
215
  next unless ActiveRabbit::Client.configured?
180
- next unless data[:exception]
181
-
182
- exception_class, exception_message = data[:exception]
183
-
184
- puts "[ActiveRabbit] Exception notification received: #{exception_class}: #{exception_message}"
185
- puts "[ActiveRabbit] Available data keys: #{data.keys.inspect}"
186
-
187
- # Create exception with proper backtrace
188
- exception = exception_class.constantize.new(exception_message)
189
216
 
190
- # Try to get backtrace from the original exception if available
217
+ # Check for rescued exceptions in the payload
218
+ exception = nil
191
219
  if data[:exception_object]
192
- exception.set_backtrace(data[:exception_object].backtrace)
220
+ # Rails 7+ provides the actual exception object
221
+ exception = data[:exception_object]
222
+ elsif data[:exception]
223
+ # Fallback: reconstruct exception from class name and message
224
+ exception_class_name, exception_message = data[:exception]
225
+ begin
226
+ exception_class = exception_class_name.constantize
227
+ exception = exception_class.new(exception_message)
228
+ rescue NameError
229
+ # If we can't constantize the exception class, create a generic one
230
+ exception = StandardError.new("#{exception_class_name}: #{exception_message}")
231
+ end
193
232
  end
194
233
 
234
+ next unless exception
235
+
195
236
  ActiveRabbit::Client.track_exception(
196
237
  exception,
197
238
  context: {
@@ -201,7 +242,13 @@ module ActiveRabbit
201
242
  controller: data[:controller],
202
243
  action: data[:action],
203
244
  status: data[:status],
204
- format: data[:format]
245
+ format: data[:format],
246
+ params: scrub_sensitive_params(data[:params])
247
+ },
248
+ timing: {
249
+ duration_ms: ((finished - started) * 1000).round(2),
250
+ view_runtime: data[:view_runtime],
251
+ db_runtime: data[:db_runtime]
205
252
  }
206
253
  }
207
254
  )
@@ -225,6 +272,13 @@ module ActiveRabbit
225
272
  nil
226
273
  end
227
274
 
275
+ def scrub_sensitive_params(params)
276
+ return {} unless params
277
+ return params unless ActiveRabbit::Client.configuration.enable_pii_scrubbing
278
+
279
+ PiiScrubber.new(ActiveRabbit::Client.configuration).scrub(params)
280
+ end
281
+
228
282
  def n_plus_one_detector
229
283
  @n_plus_one_detector ||= NPlusOneDetector.new(ActiveRabbit::Client.configuration)
230
284
  end
@@ -244,18 +298,28 @@ module ActiveRabbit
244
298
 
245
299
  # Set request context
246
300
  request_context = build_request_context(request)
301
+ request_id = SecureRandom.uuid
302
+
303
+ # Store previous context to restore later (in case of nested requests)
304
+ previous_context = Thread.current[:active_rabbit_request_context]
247
305
  Thread.current[:active_rabbit_request_context] = request_context
248
306
 
249
307
  # Start N+1 detection for this request
250
- request_id = SecureRandom.uuid
251
308
  n_plus_one_detector.start_request(request_id)
252
309
 
253
310
  begin
254
311
  @app.call(env)
255
312
  ensure
256
- # Clean up request context
257
- Thread.current[:active_rabbit_request_context] = nil
258
- n_plus_one_detector.finish_request(request_id)
313
+ # Always clean up request context, even if an exception occurred
314
+ begin
315
+ n_plus_one_detector.finish_request(request_id)
316
+ rescue => e
317
+ # Log but don't raise - we don't want cleanup to fail the request
318
+ Rails.logger.error "[ActiveRabbit] Error finishing N+1 detection: #{e.message}" if defined?(Rails)
319
+ end
320
+
321
+ # Restore previous context (handles nested requests)
322
+ Thread.current[:active_rabbit_request_context] = previous_context
259
323
  end
260
324
  end
261
325
 
@@ -299,30 +363,54 @@ module ActiveRabbit
299
363
  end
300
364
 
301
365
  def call(env)
302
- puts "[ActiveRabbit] ExceptionMiddleware called for: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
303
366
  @app.call(env)
304
367
  rescue Exception => exception
305
- # Track the exception
306
- puts "[ActiveRabbit] ExceptionMiddleware caught: #{exception.class}: #{exception.message}"
307
- request = ActionDispatch::Request.new(env)
368
+ # Track the exception, but don't let tracking errors break the request
369
+ begin
370
+ request = ActionDispatch::Request.new(env)
308
371
 
309
- ActiveRabbit::Client.track_exception(
310
- exception,
311
- context: {
312
- request: {
313
- method: request.method,
314
- path: request.path,
315
- query_string: request.query_string,
316
- user_agent: request.headers["User-Agent"],
317
- ip_address: request.remote_ip,
318
- referer: request.referer
372
+ ActiveRabbit::Client.track_exception(
373
+ exception,
374
+ context: {
375
+ request: {
376
+ method: request.method,
377
+ path: request.path,
378
+ query_string: request.query_string,
379
+ user_agent: request.headers["User-Agent"],
380
+ ip_address: request.remote_ip,
381
+ referer: request.referer,
382
+ headers: sanitize_headers(request.headers)
383
+ },
384
+ middleware: {
385
+ caught_by: 'ExceptionMiddleware',
386
+ timestamp: Time.now.iso8601(3)
387
+ }
319
388
  }
320
- }
321
- )
389
+ )
390
+ rescue => tracking_error
391
+ # Log tracking errors but don't let them interfere with exception handling
392
+ Rails.logger.error "[ActiveRabbit] Error tracking exception: #{tracking_error.message}" if defined?(Rails)
393
+ end
322
394
 
323
- # Re-raise the exception so Rails can handle it normally
395
+ # Re-raise the original exception so Rails can handle it normally
324
396
  raise exception
325
397
  end
398
+
399
+ private
400
+
401
+ def sanitize_headers(headers)
402
+ # Only include safe headers to avoid PII
403
+ safe_headers = {}
404
+ headers.each do |key, value|
405
+ next unless key.is_a?(String)
406
+
407
+ # Include common safe headers
408
+ if key.match?(/^(HTTP_ACCEPT|HTTP_ACCEPT_ENCODING|HTTP_ACCEPT_LANGUAGE|HTTP_CACHE_CONTROL|HTTP_CONNECTION|HTTP_HOST|HTTP_UPGRADE_INSECURE_REQUESTS|HTTP_USER_AGENT|CONTENT_TYPE|REQUEST_METHOD|REQUEST_URI|SERVER_NAME|SERVER_PORT)$/i)
409
+ safe_headers[key] = value
410
+ end
411
+ end
412
+ safe_headers
413
+ end
326
414
  end
327
415
  end
328
416
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRabbit
4
4
  module Client
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -79,6 +79,17 @@ module ActiveRabbit
79
79
  )
80
80
  end
81
81
 
82
+ # Test connection to ActiveRabbit API
83
+ def test_connection
84
+ return { success: false, error: "ActiveRabbit not configured" } unless configured?
85
+
86
+ begin
87
+ http_client.test_connection
88
+ rescue => e
89
+ { success: false, error: e.message }
90
+ end
91
+ end
92
+
82
93
  # Flush any pending events
83
94
  def flush
84
95
  return unless configured?
data/lib/active_rabbit.rb CHANGED
@@ -1,3 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "active_rabbit/client"
4
+
5
+ # Require Rails integration if Rails is available
6
+ require_relative "active_rabbit/client/railtie" if defined?(Rails)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerabbit-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Shapalov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-29 00:00:00.000000000 Z
11
+ date: 2025-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -109,6 +109,7 @@ files:
109
109
  - README.md
110
110
  - Rakefile
111
111
  - TESTING_GUIDE.md
112
+ - activerabbit-ai-0.1.2.gem
112
113
  - examples/rails_app_testing.rb
113
114
  - examples/rails_integration.rb
114
115
  - examples/standalone_usage.rb