activerabbit-ai 0.4.8 โ 0.5.1
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 +4 -4
- data/CHANGELOG.md +12 -0
- data/TESTING_GUIDE.md +5 -5
- data/examples/rails_integration.rb +3 -3
- data/examples/standalone_usage.rb +4 -4
- data/lib/active_rabbit/client/configuration.rb +23 -5
- data/lib/active_rabbit/client/exception_tracker.rb +27 -11
- data/lib/active_rabbit/client/http_client.rb +97 -63
- data/lib/active_rabbit/client/railtie.rb +170 -104
- data/lib/active_rabbit/client/version.rb +1 -3
- data/lib/active_rabbit/client.rb +24 -11
- data/lib/active_rabbit/middleware/error_capture_middleware.rb +1 -1
- data/lib/active_rabbit/reporting.rb +1 -1
- data/lib/active_rabbit/routing/not_found_app.rb +1 -1
- data/script/test_production_readiness.rb +4 -4
- metadata +2 -3
- data/lib/active_rabbit-client.gemspec +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ca3568f93b7ec8211e47cf961fdf459f297a2d674fac08fe7c06a4001d09cdc
|
|
4
|
+
data.tar.gz: 6b3caffa37d7687c1d9038360c6787f7846e64bd30b722e4694b5d826b149a39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b6eda18a09800a68a7b159c39af0398e1017c5fb97a6dd83bc6956c0b6acbcbe1f424fcbbcc6ab70d79a830e8a15458ba62027f9b3b4fa23d095ffb9cdd0de6
|
|
7
|
+
data.tar.gz: 48e942cb18f611f795944be031ad0d4ac1637e103357f33cab5dd2ecc4229bd15476bd91291094a5d0fd8780b78744620b9a28c9f25245e3c71690c1dff6a1a4
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2025-12-12
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **Minor release**: Bumped version to 0.5.0 to reflect latest stable changes and internal improvements
|
|
9
|
+
|
|
10
|
+
## [0.4.9] - 2025-11-30
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Rails Integration**: Fixed `LocalJumpError: unexpected return` when `ActiveRabbit` is installed but not configured
|
|
14
|
+
- This error occurred when `subscribed` callbacks tried to return early if configuration was missing
|
|
15
|
+
- Changed `return` to `next` in Railtie subscription blocks
|
|
16
|
+
|
|
5
17
|
## [0.4.8] - 2025-11-27
|
|
6
18
|
|
|
7
19
|
### Added
|
data/TESTING_GUIDE.md
CHANGED
|
@@ -69,7 +69,7 @@ end
|
|
|
69
69
|
RSpec.describe ActiveRabbit::Client::Configuration do
|
|
70
70
|
describe "environment variable loading" do
|
|
71
71
|
it "loads API key from environment" do
|
|
72
|
-
allow(ENV).to receive(:[]).with("
|
|
72
|
+
allow(ENV).to receive(:[]).with("ACTIVERABBIT_API_KEY").and_return("test-key")
|
|
73
73
|
config = described_class.new
|
|
74
74
|
expect(config.api_key).to eq("test-key")
|
|
75
75
|
end
|
|
@@ -486,8 +486,8 @@ puts "๐งช Testing ActiveRabbit Integration..."
|
|
|
486
486
|
# Test 1: Configuration
|
|
487
487
|
puts "\n1. Testing Configuration..."
|
|
488
488
|
ActiveRabbit::Client.configure do |config|
|
|
489
|
-
config.api_key = ENV['
|
|
490
|
-
config.project_id = ENV['
|
|
489
|
+
config.api_key = ENV['ACTIVERABBIT_API_KEY'] || 'test-key'
|
|
490
|
+
config.project_id = ENV['ACTIVERABBIT_PROJECT_ID'] || 'test-project'
|
|
491
491
|
config.environment = 'test'
|
|
492
492
|
end
|
|
493
493
|
|
|
@@ -578,8 +578,8 @@ jobs:
|
|
|
578
578
|
bundle exec rspec
|
|
579
579
|
ruby script/test_activerabbit.rb
|
|
580
580
|
env:
|
|
581
|
-
|
|
582
|
-
|
|
581
|
+
ACTIVERABBIT_API_KEY: ${{ secrets.ACTIVERABBIT_API_KEY }}
|
|
582
|
+
ACTIVERABBIT_PROJECT_ID: ${{ secrets.ACTIVERABBIT_PROJECT_ID }}
|
|
583
583
|
```
|
|
584
584
|
|
|
585
585
|
This comprehensive testing approach ensures your Rails application and ActiveRabbit gem integration is thoroughly tested before production deployment, covering functionality, performance, and resilience scenarios.
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
# config/initializers/active_rabbit.rb
|
|
6
6
|
ActiveRabbit::Client.configure do |config|
|
|
7
7
|
# Required configuration
|
|
8
|
-
config.api_key = ENV['
|
|
9
|
-
config.project_id = ENV['
|
|
8
|
+
config.api_key = ENV['ACTIVERABBIT_API_KEY']
|
|
9
|
+
config.project_id = ENV['ACTIVERABBIT_PROJECT_ID']
|
|
10
10
|
config.environment = Rails.env
|
|
11
11
|
|
|
12
12
|
# Optional configuration
|
|
13
|
-
config.api_url = ENV.fetch('
|
|
13
|
+
config.api_url = ENV.fetch('ACTIVERABBIT_API_URL', 'https://api.activerabbit.com')
|
|
14
14
|
config.release = ENV['HEROKU_SLUG_COMMIT'] || `git rev-parse HEAD`.chomp
|
|
15
15
|
|
|
16
16
|
# Performance settings
|
|
@@ -140,8 +140,8 @@ class BackgroundWorker
|
|
|
140
140
|
def initialize
|
|
141
141
|
# Configure ActiveRabbit for background processes
|
|
142
142
|
ActiveRabbit::Client.configure do |config|
|
|
143
|
-
config.api_key = ENV['
|
|
144
|
-
config.project_id = ENV['
|
|
143
|
+
config.api_key = ENV['ACTIVERABBIT_API_KEY']
|
|
144
|
+
config.project_id = ENV['ACTIVERABBIT_PROJECT_ID']
|
|
145
145
|
config.environment = ENV.fetch('ENVIRONMENT', 'development')
|
|
146
146
|
config.server_name = "worker-#{Socket.gethostname}"
|
|
147
147
|
end
|
|
@@ -249,8 +249,8 @@ namespace :data do
|
|
|
249
249
|
desc "Migrate user data"
|
|
250
250
|
task migrate_users: :environment do
|
|
251
251
|
ActiveRabbit::Client.configure do |config|
|
|
252
|
-
config.api_key = ENV['
|
|
253
|
-
config.project_id = ENV['
|
|
252
|
+
config.api_key = ENV['ACTIVERABBIT_API_KEY']
|
|
253
|
+
config.project_id = ENV['ACTIVERABBIT_PROJECT_ID']
|
|
254
254
|
config.environment = 'migration'
|
|
255
255
|
end
|
|
256
256
|
|
|
@@ -16,12 +16,14 @@ module ActiveRabbit
|
|
|
16
16
|
attr_accessor :before_send_event, :before_send_exception
|
|
17
17
|
attr_accessor :dedupe_window # Time window in seconds for error deduplication (0 = disabled)
|
|
18
18
|
attr_accessor :revision
|
|
19
|
+
attr_accessor :auto_release_tracking
|
|
20
|
+
attr_accessor :disable_console_logs
|
|
19
21
|
|
|
20
22
|
def initialize
|
|
21
|
-
@api_url = ENV.fetch("
|
|
22
|
-
@api_key = ENV["
|
|
23
|
-
@project_id = ENV["
|
|
24
|
-
@environment = ENV
|
|
23
|
+
@api_url = ENV.fetch("ACTIVERABBIT_API_URL", "https://api.activerabbit.ai")
|
|
24
|
+
@api_key = ENV["ACTIVERABBIT_API_KEY"]
|
|
25
|
+
@project_id = ENV["ACTIVERABBIT_PROJECT_ID"]
|
|
26
|
+
@environment = ENV["ACTIVERABBIT_ENVIRONMENT"] || detect_environment
|
|
25
27
|
|
|
26
28
|
# HTTP settings
|
|
27
29
|
@timeout = 30
|
|
@@ -38,6 +40,7 @@ module ActiveRabbit
|
|
|
38
40
|
@enable_performance_monitoring = true
|
|
39
41
|
@enable_n_plus_one_detection = true
|
|
40
42
|
@enable_pii_scrubbing = true
|
|
43
|
+
@disable_console_logs = true
|
|
41
44
|
|
|
42
45
|
# PII scrubbing
|
|
43
46
|
@pii_fields = %w[
|
|
@@ -66,9 +69,10 @@ module ActiveRabbit
|
|
|
66
69
|
@dedupe_window = 300 # 5 minutes by default
|
|
67
70
|
|
|
68
71
|
# Metadata
|
|
69
|
-
@release = detect_release
|
|
70
72
|
@server_name = detect_server_name
|
|
71
73
|
@logger = detect_logger
|
|
74
|
+
@auto_release_tracking = default_auto_release_tracking
|
|
75
|
+
@revision = detect_release
|
|
72
76
|
|
|
73
77
|
# Callbacks
|
|
74
78
|
@before_send_event = nil
|
|
@@ -136,6 +140,20 @@ module ActiveRabbit
|
|
|
136
140
|
ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
|
|
137
141
|
end
|
|
138
142
|
|
|
143
|
+
def default_auto_release_tracking
|
|
144
|
+
# Allow explicit override via env var
|
|
145
|
+
env_value = ENV["ACTIVERABBIT_AUTO_RELEASE_TRACKING"]
|
|
146
|
+
if env_value
|
|
147
|
+
normalized = env_value.to_s.strip.downcase
|
|
148
|
+
return true if %w[1 true yes y on].include?(normalized)
|
|
149
|
+
return false if %w[0 false no n off].include?(normalized)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Default: enabled outside dev/test
|
|
153
|
+
env_name = @environment.to_s
|
|
154
|
+
!%w[development test].include?(env_name)
|
|
155
|
+
end
|
|
156
|
+
|
|
139
157
|
def detect_release
|
|
140
158
|
# Try to detect from common CI/deployment environment variables
|
|
141
159
|
ENV["HEROKU_SLUG_COMMIT"] ||
|
|
@@ -32,25 +32,25 @@ module ActiveRabbit
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Send exception to API and return response
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
log(:info, "[ActiveRabbit] Preparing to send exception: #{exception.class.name}")
|
|
36
|
+
log(:debug, "[ActiveRabbit] Exception data: #{exception_data.inspect}")
|
|
37
37
|
|
|
38
38
|
# Ensure we have required fields
|
|
39
39
|
unless exception_data[:exception_class] && exception_data[:message] && exception_data[:backtrace]
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
log(:error, "[ActiveRabbit] Missing required fields in exception data")
|
|
41
|
+
log(:debug, "[ActiveRabbit] Available fields: #{exception_data.keys.inspect}")
|
|
42
42
|
return nil
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
response = http_client.post_exception(exception_data)
|
|
46
46
|
|
|
47
47
|
if response.nil?
|
|
48
|
-
|
|
48
|
+
log(:error, "[ActiveRabbit] Failed to send exception - both primary and fallback endpoints failed")
|
|
49
49
|
return nil
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
log(:info, "[ActiveRabbit] Exception successfully sent to API")
|
|
53
|
+
log(:debug, "[ActiveRabbit] API Response: #{response.inspect}")
|
|
54
54
|
response
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -60,6 +60,22 @@ module ActiveRabbit
|
|
|
60
60
|
|
|
61
61
|
private
|
|
62
62
|
|
|
63
|
+
def log(level, message)
|
|
64
|
+
cfg = configuration
|
|
65
|
+
return if cfg&.disable_console_logs
|
|
66
|
+
|
|
67
|
+
logger = cfg&.logger
|
|
68
|
+
return unless logger
|
|
69
|
+
|
|
70
|
+
case level
|
|
71
|
+
when :info then logger.info(message)
|
|
72
|
+
when :debug then logger.debug(message)
|
|
73
|
+
when :error then logger.error(message)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
rescue
|
|
77
|
+
end
|
|
78
|
+
|
|
63
79
|
def build_exception_data(exception:, context:, user_id:, tags:, handled: nil)
|
|
64
80
|
parsed_bt = parse_backtrace(exception.backtrace || [])
|
|
65
81
|
backtrace_lines = parsed_bt.map { |frame| frame[:line] }
|
|
@@ -132,10 +148,10 @@ module ActiveRabbit
|
|
|
132
148
|
end
|
|
133
149
|
|
|
134
150
|
# Log what we're sending
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
log(:debug, "[ActiveRabbit] Built exception data:")
|
|
152
|
+
log(:debug, "[ActiveRabbit] - Required fields: class=#{data[:exception_class]}, message=#{data[:message]}, backtrace=#{data[:backtrace]&.first}")
|
|
153
|
+
log(:debug, "[ActiveRabbit] - Error details: type=#{data[:error_type]}, source=#{data[:error_source]}, component=#{data[:error_component]}")
|
|
154
|
+
log(:debug, "[ActiveRabbit] - Request info: path=#{data[:request_path]}, method=#{data[:request_method]}, action=#{data[:controller_action]}")
|
|
139
155
|
|
|
140
156
|
data
|
|
141
157
|
end
|
|
@@ -37,33 +37,33 @@ module ActiveRabbit
|
|
|
37
37
|
# Primary endpoint path
|
|
38
38
|
path = "/api/v1/events/errors"
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
log(:info, "[ActiveRabbit] Sending exception to API...")
|
|
41
|
+
log(:debug, "[ActiveRabbit] Exception payload (pre-JSON): #{safe_preview(exception_data_with_type)}")
|
|
42
|
+
log(:debug, "[ActiveRabbit] Target path: #{path}")
|
|
43
43
|
|
|
44
44
|
begin
|
|
45
45
|
# Primary endpoint attempt
|
|
46
|
-
|
|
46
|
+
log(:info, "[ActiveRabbit] Making request to primary endpoint: POST #{path}")
|
|
47
47
|
response = enqueue_request(:post, path, exception_data_with_type)
|
|
48
|
-
|
|
48
|
+
log(:info, "[ActiveRabbit] Exception sent successfully (errors endpoint)")
|
|
49
49
|
return response
|
|
50
50
|
rescue => e
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
log(:error, "[ActiveRabbit] Primary send failed: #{e.class}: #{e.message}")
|
|
52
|
+
log(:error, "[ActiveRabbit] Primary error backtrace: #{e.backtrace&.first(3)}")
|
|
53
|
+
log(:debug, "[ActiveRabbit] Falling back to /api/v1/events with type=error")
|
|
54
54
|
|
|
55
55
|
begin
|
|
56
56
|
# Fallback to generic events endpoint
|
|
57
57
|
fallback_path = "/api/v1/events"
|
|
58
58
|
fallback_body = { type: "error", data: exception_data_with_type }
|
|
59
|
-
|
|
59
|
+
log(:info, "[ActiveRabbit] Making request to fallback endpoint: POST #{fallback_path}")
|
|
60
60
|
response = enqueue_request(:post, fallback_path, fallback_body)
|
|
61
|
-
|
|
61
|
+
log(:info, "[ActiveRabbit] Exception sent via fallback endpoint")
|
|
62
62
|
return response
|
|
63
63
|
rescue => e2
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
log(:error, "[ActiveRabbit] Fallback send failed: #{e2.class}: #{e2.message}")
|
|
65
|
+
log(:error, "[ActiveRabbit] Fallback error backtrace: #{e2.backtrace&.first(3)}")
|
|
66
|
+
log(:error, "[ActiveRabbit] All exception sending attempts failed")
|
|
67
67
|
nil
|
|
68
68
|
end
|
|
69
69
|
end
|
|
@@ -85,31 +85,36 @@ module ActiveRabbit
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
# Send batch to API
|
|
88
|
-
|
|
88
|
+
log(:info, "[ActiveRabbit] Sending batch of #{events.length} events...")
|
|
89
89
|
response = make_request(:post, "/api/v1/events/batch", { events: events })
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
log(:info, "[ActiveRabbit] Batch sent successfully")
|
|
91
|
+
log(:debug, "[ActiveRabbit] Batch response: #{response.inspect}")
|
|
92
92
|
response
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
req.body = payload.to_json
|
|
95
|
+
# Create (or confirm) a release in the ActiveRabbit API.
|
|
96
|
+
# Treats 409 conflict (already exists) as success to support multiple servers/dynos.
|
|
97
|
+
def post_release(release_data)
|
|
98
|
+
payload = stringify_and_sanitize(release_data)
|
|
99
|
+
uri = build_uri("/api/v1/releases")
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
end
|
|
101
|
+
log(:info, "[ActiveRabbit] Pinging release to API...")
|
|
102
|
+
log(:debug, "[ActiveRabbit] Release payload: #{safe_preview(payload)}")
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
end
|
|
104
|
+
response = perform_request(uri, :post, payload)
|
|
105
|
+
code = response.code.to_i
|
|
109
106
|
|
|
110
|
-
|
|
107
|
+
if (200..299).include?(code)
|
|
108
|
+
parse_json_or_empty(response.body)
|
|
109
|
+
elsif code == 409
|
|
110
|
+
# Already exists; return parsed body (usually includes id/version)
|
|
111
|
+
parse_json_or_empty(response.body)
|
|
112
|
+
else
|
|
113
|
+
# Use shared error handling (raises)
|
|
114
|
+
handle_response(response)
|
|
115
|
+
end
|
|
111
116
|
rescue => e
|
|
112
|
-
|
|
117
|
+
log(:error, "[ActiveRabbit] Release ping failed: #{e.class}: #{e.message}")
|
|
113
118
|
nil
|
|
114
119
|
end
|
|
115
120
|
|
|
@@ -126,18 +131,18 @@ module ActiveRabbit
|
|
|
126
131
|
def flush
|
|
127
132
|
return if @request_queue.empty?
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
log(:info, "[ActiveRabbit] Starting flush with #{@request_queue.length} items")
|
|
130
135
|
batch = @request_queue.shift(@request_queue.length)
|
|
131
136
|
return if batch.empty?
|
|
132
137
|
|
|
133
138
|
begin
|
|
134
|
-
|
|
139
|
+
log(:info, "[ActiveRabbit] Sending batch of #{batch.length} items")
|
|
135
140
|
response = post_batch(batch)
|
|
136
|
-
|
|
141
|
+
log(:info, "[ActiveRabbit] Batch sent successfully: #{response.inspect}")
|
|
137
142
|
response
|
|
138
143
|
rescue => e
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
log(:error, "[ActiveRabbit] Failed to send batch: #{e.message}")
|
|
145
|
+
log(:error, "[ActiveRabbit] Backtrace: #{e.backtrace&.first(3)}")
|
|
141
146
|
raise APIError, "Failed to send batch: #{e.message}"
|
|
142
147
|
end
|
|
143
148
|
end
|
|
@@ -150,6 +155,20 @@ module ActiveRabbit
|
|
|
150
155
|
|
|
151
156
|
private
|
|
152
157
|
|
|
158
|
+
def build_uri(path)
|
|
159
|
+
current_base = URI(configuration.api_url)
|
|
160
|
+
normalized_path = path.start_with?("/") ? path : "/#{path}"
|
|
161
|
+
URI.join(current_base, normalized_path)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def parse_json_or_empty(body)
|
|
165
|
+
return {} if body.nil? || body.empty?
|
|
166
|
+
|
|
167
|
+
JSON.parse(body)
|
|
168
|
+
rescue JSON::ParserError
|
|
169
|
+
body
|
|
170
|
+
end
|
|
171
|
+
|
|
153
172
|
def enqueue_request(method, path, data)
|
|
154
173
|
return if @shutdown
|
|
155
174
|
|
|
@@ -161,8 +180,8 @@ module ActiveRabbit
|
|
|
161
180
|
timestamp: Time.now.to_f
|
|
162
181
|
}
|
|
163
182
|
|
|
164
|
-
|
|
165
|
-
|
|
183
|
+
log(:info, "[ActiveRabbit] Enqueueing request: #{method} #{path}")
|
|
184
|
+
log(:debug, "[ActiveRabbit] Request data: #{data.inspect}")
|
|
166
185
|
|
|
167
186
|
@request_queue << formatted_data
|
|
168
187
|
|
|
@@ -190,9 +209,9 @@ module ActiveRabbit
|
|
|
190
209
|
# Ensure path starts with a single leading slash
|
|
191
210
|
normalized_path = path.start_with?("/") ? path : "/#{path}"
|
|
192
211
|
uri = URI.join(current_base, normalized_path)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
212
|
+
log(:info, "[ActiveRabbit] Making request: #{method.upcase} #{uri}")
|
|
213
|
+
log(:debug, "[ActiveRabbit] Request headers: X-Project-Token=#{configuration.api_key}, X-Project-ID=#{configuration.project_id}")
|
|
214
|
+
log(:debug, "[ActiveRabbit] Request body: #{safe_preview(data)}")
|
|
196
215
|
|
|
197
216
|
# Retry logic with exponential backoff
|
|
198
217
|
retries = 0
|
|
@@ -200,17 +219,17 @@ module ActiveRabbit
|
|
|
200
219
|
|
|
201
220
|
begin
|
|
202
221
|
response = perform_request(uri, method, data)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
222
|
+
log(:info, "[ActiveRabbit] Response status: #{response.code}")
|
|
223
|
+
log(:debug, "[ActiveRabbit] Response headers: #{response.to_hash.inspect}")
|
|
224
|
+
log(:debug, "[ActiveRabbit] Response body: #{response.body}")
|
|
206
225
|
|
|
207
226
|
result = handle_response(response)
|
|
208
|
-
|
|
227
|
+
log(:debug, "[ActiveRabbit] Parsed response: #{result.inspect}")
|
|
209
228
|
result
|
|
210
229
|
rescue RetryableError => e
|
|
211
230
|
if retries < max_retries
|
|
212
231
|
retries += 1
|
|
213
|
-
|
|
232
|
+
log(:info, "[ActiveRabbit] Retrying request (#{retries}/#{max_retries})")
|
|
214
233
|
sleep(configuration.retry_delay * (2 ** (retries - 1)))
|
|
215
234
|
retry
|
|
216
235
|
end
|
|
@@ -218,7 +237,7 @@ module ActiveRabbit
|
|
|
218
237
|
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
219
238
|
if retries < max_retries && should_retry_error?(e)
|
|
220
239
|
retries += 1
|
|
221
|
-
|
|
240
|
+
log(:info, "[ActiveRabbit] Retrying request after timeout (#{retries}/#{max_retries})")
|
|
222
241
|
sleep(configuration.retry_delay * (2 ** (retries - 1))) # Exponential backoff
|
|
223
242
|
retry
|
|
224
243
|
end
|
|
@@ -226,24 +245,24 @@ module ActiveRabbit
|
|
|
226
245
|
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
|
|
227
246
|
if retries < max_retries
|
|
228
247
|
retries += 1
|
|
229
|
-
|
|
248
|
+
log(:info, "[ActiveRabbit] Retrying request after connection error (#{retries}/#{max_retries})")
|
|
230
249
|
sleep(configuration.retry_delay * (2 ** (retries - 1)))
|
|
231
250
|
retry
|
|
232
251
|
end
|
|
233
252
|
raise APIError, "Connection failed after #{retries} retries: #{e.message}"
|
|
234
253
|
rescue APIError, RateLimitError => e
|
|
235
254
|
# Re-raise API errors as-is
|
|
236
|
-
|
|
255
|
+
log(:error, "[ActiveRabbit] API error: #{e.class}: #{e.message}")
|
|
237
256
|
raise e
|
|
238
257
|
rescue => e
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
log(:error, "[ActiveRabbit] Request failed: #{e.class}: #{e.message}")
|
|
259
|
+
log(:error, "[ActiveRabbit] Backtrace: #{e.backtrace&.first(3)}")
|
|
241
260
|
raise APIError, "Request failed: #{e.message}"
|
|
242
261
|
end
|
|
243
262
|
end
|
|
244
263
|
|
|
245
264
|
def perform_request(uri, method, data)
|
|
246
|
-
|
|
265
|
+
log(:debug, "[ActiveRabbit] Making HTTP request: #{method.upcase} #{uri}")
|
|
247
266
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
248
267
|
|
|
249
268
|
# Configure SSL if HTTPS
|
|
@@ -270,7 +289,7 @@ module ActiveRabbit
|
|
|
270
289
|
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
|
271
290
|
end
|
|
272
291
|
|
|
273
|
-
|
|
292
|
+
log(:debug, "[ActiveRabbit] Request path: #{uri.request_uri}")
|
|
274
293
|
|
|
275
294
|
# Set headers
|
|
276
295
|
request['Content-Type'] = 'application/json'
|
|
@@ -291,8 +310,8 @@ module ActiveRabbit
|
|
|
291
310
|
end
|
|
292
311
|
|
|
293
312
|
def handle_response(response)
|
|
294
|
-
|
|
295
|
-
|
|
313
|
+
log(:debug, "[ActiveRabbit] Response code: #{response.code}")
|
|
314
|
+
log(:debug, "[ActiveRabbit] Response body: #{response.body}")
|
|
296
315
|
|
|
297
316
|
case response.code.to_i
|
|
298
317
|
when 200..299
|
|
@@ -300,23 +319,23 @@ module ActiveRabbit
|
|
|
300
319
|
if response.body && !response.body.empty?
|
|
301
320
|
begin
|
|
302
321
|
parsed = JSON.parse(response.body)
|
|
303
|
-
|
|
322
|
+
log(:debug, "[ActiveRabbit] Parsed response: #{parsed.inspect}")
|
|
304
323
|
parsed
|
|
305
324
|
rescue JSON::ParserError => e
|
|
306
|
-
|
|
307
|
-
|
|
325
|
+
log(:error, "[ActiveRabbit] Failed to parse response: #{e.message}")
|
|
326
|
+
log(:error, "[ActiveRabbit] Raw response: #{response.body}")
|
|
308
327
|
response.body
|
|
309
328
|
end
|
|
310
329
|
else
|
|
311
|
-
|
|
330
|
+
log(:debug, "[ActiveRabbit] Empty response body")
|
|
312
331
|
{}
|
|
313
332
|
end
|
|
314
333
|
when 429
|
|
315
|
-
|
|
334
|
+
log(:error, "[ActiveRabbit] Rate limit exceeded")
|
|
316
335
|
raise RateLimitError, "Rate limit exceeded"
|
|
317
336
|
when 400..499
|
|
318
337
|
error_message = extract_error_message(response)
|
|
319
|
-
|
|
338
|
+
log(:error, "[ActiveRabbit] Client error (#{response.code}): #{error_message}")
|
|
320
339
|
raise APIError, "Client error (#{response.code}): #{error_message}"
|
|
321
340
|
when 500..599
|
|
322
341
|
error_message = extract_error_message(response)
|
|
@@ -324,11 +343,11 @@ module ActiveRabbit
|
|
|
324
343
|
configuration.logger&.warn("[ActiveRabbit] Retryable server error (#{response.code}): #{error_message}")
|
|
325
344
|
raise RetryableError, "Server error (#{response.code}): #{error_message}"
|
|
326
345
|
else
|
|
327
|
-
|
|
346
|
+
log(:error, "[ActiveRabbit] Server error (#{response.code}): #{error_message}")
|
|
328
347
|
raise APIError, "Server error (#{response.code}): #{error_message}"
|
|
329
348
|
end
|
|
330
349
|
else
|
|
331
|
-
|
|
350
|
+
log(:error, "[ActiveRabbit] Unexpected response code: #{response.code}")
|
|
332
351
|
raise APIError, "Unexpected response code: #{response.code}"
|
|
333
352
|
end
|
|
334
353
|
end
|
|
@@ -383,7 +402,22 @@ module ActiveRabbit
|
|
|
383
402
|
s = obj.inspect
|
|
384
403
|
s.length > 2000 ? s[0,2000] + "...(truncated)" : s
|
|
385
404
|
end
|
|
386
|
-
end
|
|
387
405
|
|
|
406
|
+
def log(level, message)
|
|
407
|
+
return if configuration.disable_console_logs
|
|
408
|
+
|
|
409
|
+
logger = configuration.logger
|
|
410
|
+
return unless logger
|
|
411
|
+
|
|
412
|
+
case level
|
|
413
|
+
when :info
|
|
414
|
+
logger.info(message)
|
|
415
|
+
when :debug
|
|
416
|
+
logger.debug(message)
|
|
417
|
+
when :error
|
|
418
|
+
logger.error(message)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
388
422
|
end
|
|
389
423
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "logger"
|
|
4
|
+
require "concurrent"
|
|
4
5
|
|
|
5
6
|
begin
|
|
6
7
|
require "rails/railtie"
|
|
@@ -21,40 +22,76 @@ module ActiveRabbit
|
|
|
21
22
|
config.active_rabbit = ActiveSupport::OrderedOptions.new
|
|
22
23
|
|
|
23
24
|
initializer "active_rabbit.configure", after: :initialize_logger do |app|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
apply_rails_configuration(app.config.active_rabbit)
|
|
26
|
+
|
|
27
|
+
if ActiveRabbit::Client.configuration && !ActiveRabbit::Client.configuration.disable_console_logs
|
|
28
|
+
if Rails.env.development?
|
|
29
|
+
ar_puts "\n=== ActiveRabbit Configure ==="
|
|
30
|
+
ar_puts "Environment: #{Rails.env}"
|
|
31
|
+
ar_puts "Already configured? #{ActiveRabbit::Client.configured?}"
|
|
32
|
+
ar_puts "================================\n"
|
|
33
|
+
end
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
# Configure ActiveRabbit from Rails configuration
|
|
32
37
|
ActiveRabbit::Client.configure do |config|
|
|
33
|
-
config.environment
|
|
34
|
-
config.logger
|
|
35
|
-
config.release
|
|
38
|
+
config.environment ||= Rails.env
|
|
39
|
+
config.logger ||= Rails.logger rescue Logger.new(STDOUT)
|
|
40
|
+
config.release ||= detect_release(app)
|
|
36
41
|
end
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
app.config.after_initialize do
|
|
44
|
+
begin
|
|
45
|
+
cfg = ActiveRabbit::Client.configuration
|
|
46
|
+
next unless cfg
|
|
47
|
+
next unless ActiveRabbit::Client.configured?
|
|
48
|
+
next unless cfg.auto_release_tracking
|
|
49
|
+
|
|
50
|
+
version = cfg.revision || cfg.release
|
|
51
|
+
next if version.nil? || version.to_s.strip.empty?
|
|
52
|
+
|
|
53
|
+
metadata = {
|
|
54
|
+
revision: cfg.revision,
|
|
55
|
+
release: cfg.release,
|
|
56
|
+
server_name: cfg.server_name,
|
|
57
|
+
environment: cfg.environment,
|
|
58
|
+
gem_version: ActiveRabbit::Client::VERSION
|
|
59
|
+
}.compact
|
|
60
|
+
|
|
61
|
+
Concurrent::Future.execute do
|
|
62
|
+
ActiveRabbit::Client.notify_release(
|
|
63
|
+
version: version,
|
|
64
|
+
environment: cfg.environment,
|
|
65
|
+
metadata: metadata
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
rescue => e
|
|
69
|
+
Rails.logger.debug "[ActiveRabbit] auto release tracking failed: #{e.class}: #{e.message}" if defined?(Rails) && Rails.logger
|
|
70
|
+
end
|
|
43
71
|
end
|
|
44
72
|
|
|
73
|
+
# if ActiveRabbit::Client.configuration && !ActiveRabbit::Client.configuration.disable_console_logs
|
|
74
|
+
# if Rails.env.development?
|
|
75
|
+
# ar_puts "\n=== ActiveRabbit Post-Configure ==="
|
|
76
|
+
# ar_puts "Now configured? #{ActiveRabbit::Client.configured?}"
|
|
77
|
+
# ar_puts "Configuration: #{ActiveRabbit::Client.configuration.inspect}"
|
|
78
|
+
# ar_puts "================================\n"
|
|
79
|
+
# end
|
|
80
|
+
# end
|
|
81
|
+
|
|
45
82
|
# Set up exception tracking
|
|
46
83
|
setup_exception_tracking(app) if ActiveRabbit::Client.configured?
|
|
47
84
|
end
|
|
48
85
|
|
|
49
86
|
initializer "active_rabbit.subscribe_to_notifications", after: :load_config_initializers do |app|
|
|
50
|
-
|
|
87
|
+
ar_log(:info, "[ActiveRabbit] Setting up performance notifications subscriptions")
|
|
51
88
|
# Subscribe regardless; each handler guards on configured?
|
|
52
89
|
subscribe_to_controller_events
|
|
53
90
|
subscribe_to_active_record_events
|
|
54
91
|
subscribe_to_action_view_events
|
|
55
92
|
subscribe_to_action_mailer_events if defined?(ActionMailer)
|
|
56
93
|
subscribe_to_exception_notifications
|
|
57
|
-
|
|
94
|
+
ar_log(:info, "[ActiveRabbit] Subscriptions setup complete")
|
|
58
95
|
|
|
59
96
|
# Defer complex subscriptions until after initialization
|
|
60
97
|
app.config.after_initialize do
|
|
@@ -85,83 +122,83 @@ module ActiveRabbit
|
|
|
85
122
|
# Configure middleware after logger is initialized to avoid init cycles
|
|
86
123
|
initializer "active_rabbit.add_middleware", after: :initialize_logger do |app|
|
|
87
124
|
if Rails.env.development?
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
ar_puts "\n=== ActiveRabbit Railtie Loading ==="
|
|
126
|
+
ar_puts "Rails Environment: #{Rails.env}"
|
|
127
|
+
ar_puts "Rails Middleware Stack Phase: #{app.middleware.respond_to?(:middlewares) ? 'Ready' : 'Not Ready'}"
|
|
128
|
+
ar_puts "================================\n"
|
|
129
|
+
ar_puts "\n=== Initial Middleware Stack ==="
|
|
130
|
+
ar_puts "(not available at this boot phase)"
|
|
131
|
+
ar_puts "=======================\n"
|
|
95
132
|
end
|
|
96
133
|
|
|
97
|
-
|
|
134
|
+
ar_puts "\n=== Adding ActiveRabbit Middleware ===" if Rails.env.development?
|
|
98
135
|
# Handle both development (DebugExceptions) and production (ShowExceptions)
|
|
99
136
|
if defined?(ActionDispatch::DebugExceptions)
|
|
100
|
-
|
|
137
|
+
ar_puts "[ActiveRabbit] Found DebugExceptions, configuring middleware..." if Rails.env.development?
|
|
101
138
|
|
|
102
139
|
# First remove any existing middleware to avoid duplicates
|
|
103
140
|
begin
|
|
104
141
|
app.config.middleware.delete(ActiveRabbit::Client::ExceptionMiddleware)
|
|
105
142
|
app.config.middleware.delete(ActiveRabbit::Client::RequestContextMiddleware)
|
|
106
143
|
app.config.middleware.delete(ActiveRabbit::Client::RoutingErrorCatcher)
|
|
107
|
-
|
|
144
|
+
ar_puts "[ActiveRabbit] Cleaned up existing middleware" if Rails.env.development?
|
|
108
145
|
rescue => e
|
|
109
|
-
|
|
146
|
+
ar_puts "[ActiveRabbit] Error cleaning middleware: #{e.message}"
|
|
110
147
|
end
|
|
111
148
|
|
|
112
149
|
# Insert middleware in the correct order
|
|
113
|
-
|
|
150
|
+
ar_puts "[ActiveRabbit] Inserting middleware..." if Rails.env.development?
|
|
114
151
|
|
|
115
152
|
# Insert ErrorCaptureMiddleware after DebugExceptions to rely on rescue path
|
|
116
153
|
app.config.middleware.insert_after(ActionDispatch::DebugExceptions, ActiveRabbit::Middleware::ErrorCaptureMiddleware)
|
|
117
154
|
|
|
118
155
|
# Insert RequestContextMiddleware early in the stack
|
|
119
|
-
|
|
156
|
+
ar_puts "[ActiveRabbit] Inserting RequestContextMiddleware before RequestId" if Rails.env.development?
|
|
120
157
|
app.config.middleware.insert_before(ActionDispatch::RequestId, ActiveRabbit::Client::RequestContextMiddleware)
|
|
121
158
|
|
|
122
159
|
# Insert ExceptionMiddleware before Rails' exception handlers (kept for env-based reporting)
|
|
123
|
-
|
|
160
|
+
ar_puts "[ActiveRabbit] Inserting ExceptionMiddleware before DebugExceptions" if Rails.env.development?
|
|
124
161
|
app.config.middleware.insert_before(ActionDispatch::DebugExceptions, ActiveRabbit::Client::ExceptionMiddleware)
|
|
125
162
|
|
|
126
163
|
# Insert RoutingErrorCatcher after Rails' exception handlers
|
|
127
|
-
|
|
164
|
+
ar_puts "[ActiveRabbit] Inserting RoutingErrorCatcher after DebugExceptions" if Rails.env.development?
|
|
128
165
|
app.config.middleware.insert_after(ActionDispatch::DebugExceptions, ActiveRabbit::Client::RoutingErrorCatcher)
|
|
129
166
|
|
|
130
|
-
|
|
167
|
+
ar_puts "[ActiveRabbit] Middleware insertion complete" if Rails.env.development?
|
|
131
168
|
|
|
132
169
|
elsif defined?(ActionDispatch::ShowExceptions)
|
|
133
|
-
|
|
170
|
+
ar_puts "[ActiveRabbit] Found ShowExceptions, configuring middleware..." if Rails.env.development?
|
|
134
171
|
|
|
135
172
|
# First remove any existing middleware to avoid duplicates
|
|
136
173
|
begin
|
|
137
174
|
app.config.middleware.delete(ActiveRabbit::Client::ExceptionMiddleware)
|
|
138
175
|
app.config.middleware.delete(ActiveRabbit::Client::RequestContextMiddleware)
|
|
139
176
|
app.config.middleware.delete(ActiveRabbit::Client::RoutingErrorCatcher)
|
|
140
|
-
|
|
177
|
+
ar_puts "[ActiveRabbit] Cleaned up existing middleware" if Rails.env.development?
|
|
141
178
|
rescue => e
|
|
142
|
-
|
|
179
|
+
ar_puts "[ActiveRabbit] Error cleaning middleware: #{e.message}"
|
|
143
180
|
end
|
|
144
181
|
|
|
145
182
|
# Insert middleware in the correct order
|
|
146
|
-
|
|
183
|
+
ar_puts "[ActiveRabbit] Inserting middleware..." if Rails.env.development?
|
|
147
184
|
|
|
148
185
|
# Insert ErrorCaptureMiddleware after ShowExceptions
|
|
149
186
|
app.config.middleware.insert_after(ActionDispatch::ShowExceptions, ActiveRabbit::Middleware::ErrorCaptureMiddleware)
|
|
150
187
|
|
|
151
188
|
# Insert RequestContextMiddleware early in the stack
|
|
152
|
-
|
|
189
|
+
ar_puts "[ActiveRabbit] Inserting RequestContextMiddleware before RequestId" if Rails.env.development?
|
|
153
190
|
app.config.middleware.insert_before(ActionDispatch::RequestId, ActiveRabbit::Client::RequestContextMiddleware)
|
|
154
191
|
|
|
155
192
|
# Insert ExceptionMiddleware before Rails' exception handlers
|
|
156
|
-
|
|
193
|
+
ar_puts "[ActiveRabbit] Inserting ExceptionMiddleware before ShowExceptions" if Rails.env.development?
|
|
157
194
|
app.config.middleware.insert_before(ActionDispatch::ShowExceptions, ActiveRabbit::Client::ExceptionMiddleware)
|
|
158
195
|
|
|
159
196
|
# Insert RoutingErrorCatcher after Rails' exception handlers
|
|
160
|
-
|
|
197
|
+
ar_puts "[ActiveRabbit] Inserting RoutingErrorCatcher after ShowExceptions" if Rails.env.development?
|
|
161
198
|
app.config.middleware.insert_after(ActionDispatch::ShowExceptions, ActiveRabbit::Client::RoutingErrorCatcher)
|
|
162
199
|
|
|
163
200
|
else
|
|
164
|
-
|
|
201
|
+
ar_puts "[ActiveRabbit] No exception handlers found, using fallback configuration" if Rails.env.development?
|
|
165
202
|
app.config.middleware.use(ActiveRabbit::Middleware::ErrorCaptureMiddleware)
|
|
166
203
|
app.config.middleware.use(ActiveRabbit::Client::RequestContextMiddleware)
|
|
167
204
|
app.config.middleware.use(ActiveRabbit::Client::ExceptionMiddleware)
|
|
@@ -169,9 +206,9 @@ module ActiveRabbit
|
|
|
169
206
|
end
|
|
170
207
|
|
|
171
208
|
if Rails.env.development?
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
209
|
+
ar_puts "\n=== Final Middleware Stack ==="
|
|
210
|
+
ar_puts "(will be printed after initialize)"
|
|
211
|
+
ar_puts "=======================\n"
|
|
175
212
|
end
|
|
176
213
|
|
|
177
214
|
# Add debug wrappers in development
|
|
@@ -180,41 +217,42 @@ module ActiveRabbit
|
|
|
180
217
|
ActiveRabbit::Client::ExceptionMiddleware.class_eval do
|
|
181
218
|
alias_method :__ar_original_call, :call unless method_defined?(:__ar_original_call)
|
|
182
219
|
def call(env)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
220
|
+
cfg = ActiveRabbit::Client.configuration
|
|
221
|
+
ar_puts "\n=== ExceptionMiddleware Enter ==="
|
|
222
|
+
ar_puts "Path: #{env['PATH_INFO']}"
|
|
223
|
+
ar_puts "Method: #{env['REQUEST_METHOD']}"
|
|
224
|
+
ar_puts "Current Exception: #{env['action_dispatch.exception']&.class} - #{env['action_dispatch.exception']&.message}"
|
|
225
|
+
ar_puts "Current Error: #{env['action_dispatch.error']&.class} - #{env['action_dispatch.error']&.message}"
|
|
226
|
+
ar_puts "Rack Exception: #{env['rack.exception']&.class} - #{env['rack.exception']&.message}"
|
|
227
|
+
ar_puts "Exception Backtrace: #{env['action_dispatch.exception']&.backtrace&.first(3)&.join("\n ")}"
|
|
228
|
+
ar_puts "Error Backtrace: #{env['action_dispatch.error']&.backtrace&.first(3)&.join("\n ")}"
|
|
229
|
+
ar_puts "Rack Backtrace: #{env['rack.exception']&.backtrace&.first(3)&.join("\n ")}"
|
|
230
|
+
ar_puts "============================\n"
|
|
193
231
|
|
|
194
232
|
begin
|
|
195
233
|
status, headers, body = __ar_original_call(env)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
234
|
+
ar_puts "\n=== ExceptionMiddleware Exit (Success) ==="
|
|
235
|
+
ar_puts "Status: #{status}"
|
|
236
|
+
ar_puts "Headers: #{headers.inspect}"
|
|
237
|
+
ar_puts "Final Exception: #{env['action_dispatch.exception']&.class} - #{env['action_dispatch.exception']&.message}"
|
|
238
|
+
ar_puts "Final Error: #{env['action_dispatch.error']&.class} - #{env['action_dispatch.error']&.message}"
|
|
239
|
+
ar_puts "Final Rack Exception: #{env['rack.exception']&.class} - #{env['rack.exception']&.message}"
|
|
240
|
+
ar_puts "Final Exception Backtrace: #{env['action_dispatch.exception']&.backtrace&.first(3)&.join("\n ")}"
|
|
241
|
+
ar_puts "Final Error Backtrace: #{env['action_dispatch.error']&.backtrace&.first(3)&.join("\n ")}"
|
|
242
|
+
ar_puts "Final Rack Backtrace: #{env['rack.exception']&.backtrace&.first(3)&.join("\n ")}"
|
|
243
|
+
ar_puts "===========================\n"
|
|
206
244
|
[status, headers, body]
|
|
207
245
|
rescue => e
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
246
|
+
ar_puts "\n=== ExceptionMiddleware Exit (Error) ==="
|
|
247
|
+
ar_puts "Error: #{e.class} - #{e.message}"
|
|
248
|
+
ar_puts "Error Backtrace: #{e.backtrace&.first(3)&.join("\n ")}"
|
|
249
|
+
ar_puts "Original Exception: #{env['action_dispatch.exception']&.class} - #{env['action_dispatch.exception']&.message}"
|
|
250
|
+
ar_puts "Original Error: #{env['action_dispatch.error']&.class} - #{env['action_dispatch.error']&.message}"
|
|
251
|
+
ar_puts "Original Rack Exception: #{env['rack.exception']&.class} - #{env['rack.exception']&.message}"
|
|
252
|
+
ar_puts "Original Exception Backtrace: #{env['action_dispatch.exception']&.backtrace&.first(3)&.join("\n ")}"
|
|
253
|
+
ar_puts "Original Error Backtrace: #{env['action_dispatch.error']&.backtrace&.first(3)&.join("\n ")}"
|
|
254
|
+
ar_puts "Original Rack Backtrace: #{env['rack.exception']&.backtrace&.first(3)&.join("\n ")}"
|
|
255
|
+
ar_puts "===========================\n"
|
|
218
256
|
raise
|
|
219
257
|
end
|
|
220
258
|
end
|
|
@@ -224,23 +262,23 @@ module ActiveRabbit
|
|
|
224
262
|
ActiveRabbit::Client::RoutingErrorCatcher.class_eval do
|
|
225
263
|
alias_method :__ar_routing_original_call, :call unless method_defined?(:__ar_routing_original_call)
|
|
226
264
|
def call(env)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
265
|
+
ar_puts "\n=== RoutingErrorCatcher Enter ==="
|
|
266
|
+
ar_puts "Path: #{env['PATH_INFO']}"
|
|
267
|
+
ar_puts "Method: #{env['REQUEST_METHOD']}"
|
|
268
|
+
ar_puts "Status: #{env['action_dispatch.exception']&.class}"
|
|
269
|
+
ar_puts "============================\n"
|
|
232
270
|
|
|
233
271
|
begin
|
|
234
272
|
status, headers, body = __ar_routing_original_call(env)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
273
|
+
ar_puts "\n=== RoutingErrorCatcher Exit (Success) ==="
|
|
274
|
+
ar_puts "Status: #{status}"
|
|
275
|
+
ar_puts "===========================\n"
|
|
238
276
|
[status, headers, body]
|
|
239
277
|
rescue => e
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
278
|
+
ar_puts "\n=== RoutingErrorCatcher Exit (Error) ==="
|
|
279
|
+
ar_puts "Error: #{e.class} - #{e.message}"
|
|
280
|
+
ar_puts "Backtrace: #{e.backtrace&.first(3)&.join("\n ")}"
|
|
281
|
+
ar_puts "===========================\n"
|
|
244
282
|
raise
|
|
245
283
|
end
|
|
246
284
|
end
|
|
@@ -250,18 +288,18 @@ module ActiveRabbit
|
|
|
250
288
|
# In development, add a hook to verify middleware after initialization
|
|
251
289
|
if Rails.env.development?
|
|
252
290
|
app.config.after_initialize do
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
291
|
+
ar_log(:info, "\n=== ActiveRabbit Configuration ===")
|
|
292
|
+
ar_log(:info, "Version: #{ActiveRabbit::Client::VERSION}")
|
|
293
|
+
ar_log(:info, "Environment: #{Rails.env}")
|
|
294
|
+
ar_log(:info, "API URL: #{ActiveRabbit::Client.configuration.api_url}")
|
|
295
|
+
ar_log(:info, "================================")
|
|
258
296
|
|
|
259
|
-
|
|
297
|
+
ar_log(:info, "\n=== Middleware Stack ===")
|
|
260
298
|
(Rails.application.middleware.middlewares rescue []).each do |mw|
|
|
261
299
|
klass = (mw.respond_to?(:klass) ? mw.klass.name : mw.to_s) rescue mw.inspect
|
|
262
|
-
|
|
300
|
+
ar_log(:info, " #{klass}")
|
|
263
301
|
end
|
|
264
|
-
|
|
302
|
+
ar_log(:info, "=======================")
|
|
265
303
|
|
|
266
304
|
# Skip missing-middleware warnings in development since we may inject via alternate paths
|
|
267
305
|
unless Rails.env.development?
|
|
@@ -284,7 +322,7 @@ module ActiveRabbit
|
|
|
284
322
|
end
|
|
285
323
|
end
|
|
286
324
|
|
|
287
|
-
|
|
325
|
+
ar_log(:info, "[ActiveRabbit] Middleware configured successfully")
|
|
288
326
|
end
|
|
289
327
|
|
|
290
328
|
initializer "active_rabbit.error_reporter" do |app|
|
|
@@ -374,8 +412,8 @@ module ActiveRabbit
|
|
|
374
412
|
begin
|
|
375
413
|
reporting_file, reporting_line = ActiveRabbit::Reporting.method(:report_exception).source_location
|
|
376
414
|
http_file, http_line = ActiveRabbit::Client::HttpClient.instance_method(:post_exception).source_location
|
|
377
|
-
|
|
378
|
-
|
|
415
|
+
ar_log(:info, "[ActiveRabbit] Reporting loaded from #{reporting_file}:#{reporting_line}") if defined?(Rails)
|
|
416
|
+
ar_log(:info, "[ActiveRabbit] HttpClient#post_exception from #{http_file}:#{http_line}") if defined?(Rails)
|
|
379
417
|
rescue => e
|
|
380
418
|
Rails.logger.debug "[ActiveRabbit] boot diagnostics failed: #{e.message}" if defined?(Rails)
|
|
381
419
|
end
|
|
@@ -384,6 +422,34 @@ module ActiveRabbit
|
|
|
384
422
|
|
|
385
423
|
private
|
|
386
424
|
|
|
425
|
+
def ar_puts(msg)
|
|
426
|
+
cfg = ActiveRabbit::Client.configuration
|
|
427
|
+
return if cfg && cfg.disable_console_logs
|
|
428
|
+
puts msg
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def ar_log(level, msg)
|
|
432
|
+
cfg = ActiveRabbit::Client.configuration
|
|
433
|
+
return if cfg && cfg.disable_console_logs
|
|
434
|
+
Rails.logger.public_send(level, msg) if Rails.logger
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def apply_rails_configuration(rails_config)
|
|
438
|
+
return unless rails_config
|
|
439
|
+
|
|
440
|
+
options = rails_config.respond_to?(:to_h) ? rails_config.to_h : rails_config
|
|
441
|
+
return if options.nil? || options.empty?
|
|
442
|
+
|
|
443
|
+
ActiveRabbit::Client.configure do |config|
|
|
444
|
+
options.each do |key, value|
|
|
445
|
+
next if value.nil?
|
|
446
|
+
|
|
447
|
+
writer = "#{key}="
|
|
448
|
+
config.public_send(writer, value) if config.respond_to?(writer)
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
387
453
|
def setup_exception_tracking(app)
|
|
388
454
|
# Handle uncaught exceptions in development
|
|
389
455
|
if Rails.env.development? || Rails.env.test?
|
|
@@ -392,19 +458,19 @@ module ActiveRabbit
|
|
|
392
458
|
end
|
|
393
459
|
|
|
394
460
|
def subscribe_to_controller_events
|
|
395
|
-
|
|
461
|
+
ar_log(:info, "[ActiveRabbit] Subscribing to controller events (configured=#{ActiveRabbit::Client.configured?})")
|
|
396
462
|
|
|
397
463
|
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, payload|
|
|
398
464
|
begin
|
|
399
465
|
unless ActiveRabbit::Client.configured?
|
|
400
466
|
Rails.logger.debug "[ActiveRabbit] Skipping performance tracking - not configured"
|
|
401
|
-
|
|
467
|
+
next
|
|
402
468
|
end
|
|
403
469
|
|
|
404
470
|
duration_ms = ((finished - started) * 1000).round(2)
|
|
405
471
|
|
|
406
|
-
|
|
407
|
-
|
|
472
|
+
ar_log(:info, "[ActiveRabbit] ๐ Controller action: #{payload[:controller]}##{payload[:action]} - #{duration_ms}ms")
|
|
473
|
+
ar_log(:info, "[ActiveRabbit] ๐ DB runtime: #{payload[:db_runtime]}, View runtime: #{payload[:view_runtime]}")
|
|
408
474
|
|
|
409
475
|
ActiveRabbit::Client.track_performance(
|
|
410
476
|
"controller.action",
|
|
@@ -672,7 +738,7 @@ module ActiveRabbit
|
|
|
672
738
|
|
|
673
739
|
def call(env)
|
|
674
740
|
# debug start - using Rails.logger to ensure it appears in development.log
|
|
675
|
-
|
|
741
|
+
ar_log(:info, "[AR] ExceptionMiddleware ENTER path=#{env['PATH_INFO']}") if defined?(Rails)
|
|
676
742
|
warn "[AR] ExceptionMiddleware ENTER path=#{env['PATH_INFO']}"
|
|
677
743
|
warn "[AR] Current exceptions in env:"
|
|
678
744
|
warn " - action_dispatch.exception: #{env['action_dispatch.exception']&.class}"
|
|
@@ -686,12 +752,12 @@ module ActiveRabbit
|
|
|
686
752
|
|
|
687
753
|
# Check for exceptions in env after app call
|
|
688
754
|
if (ex = env["action_dispatch.exception"] || env["rack.exception"] || env["action_dispatch.error"])
|
|
689
|
-
|
|
755
|
+
ar_log(:info, "[AR] env exception present: #{ex.class}: #{ex.message}") if defined?(Rails)
|
|
690
756
|
warn "[AR] env exception present: #{ex.class}: #{ex.message}"
|
|
691
757
|
warn "[AR] Exception backtrace: #{ex.backtrace&.first(3)&.join("\n ")}"
|
|
692
758
|
safe_report(ex, env, 'Rails rescued exception')
|
|
693
759
|
else
|
|
694
|
-
|
|
760
|
+
ar_log(:info, "[AR] env exception NOT present") if defined?(Rails)
|
|
695
761
|
warn "[AR] env exception NOT present"
|
|
696
762
|
warn "[AR] Final env check:"
|
|
697
763
|
warn " - action_dispatch.exception: #{env['action_dispatch.exception']&.class}"
|
|
@@ -703,7 +769,7 @@ module ActiveRabbit
|
|
|
703
769
|
[status, headers, body]
|
|
704
770
|
rescue => e
|
|
705
771
|
# Primary path: catch raw exceptions before Rails rescuers
|
|
706
|
-
|
|
772
|
+
ar_log(:info, "[AR] RESCUE caught: #{e.class}: #{e.message}") if defined?(Rails)
|
|
707
773
|
warn "[AR] RESCUE caught: #{e.class}: #{e.message}"
|
|
708
774
|
warn "[AR] Rescue backtrace: #{e.backtrace&.first(3)&.join("\n ")}"
|
|
709
775
|
|
|
@@ -748,7 +814,7 @@ module ActiveRabbit
|
|
|
748
814
|
result = ActiveRabbit::Client.track_exception(exception, context: context)
|
|
749
815
|
warn "[AR] Track result: #{result.inspect}"
|
|
750
816
|
|
|
751
|
-
|
|
817
|
+
ar_log(:info, "[ActiveRabbit] Tracked #{source}: #{exception.class.name} - #{exception.message}") if defined?(Rails)
|
|
752
818
|
rescue => tracking_error
|
|
753
819
|
# Log tracking errors but don't let them interfere with exception handling
|
|
754
820
|
warn "[AR] Error in safe_report: #{tracking_error.class} - #{tracking_error.message}"
|
data/lib/active_rabbit/client.rb
CHANGED
|
@@ -77,8 +77,8 @@ module ActiveRabbit
|
|
|
77
77
|
result = exception_tracker.track_exception(**args)
|
|
78
78
|
|
|
79
79
|
# Log the result
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
ActiveRabbit::Client.log(:info, "[ActiveRabbit] Exception tracked: #{exception.class.name}")
|
|
81
|
+
ActiveRabbit::Client.log(:debug, "[ActiveRabbit] Exception tracking result: #{result.inspect}")
|
|
82
82
|
|
|
83
83
|
result
|
|
84
84
|
end
|
|
@@ -127,19 +127,32 @@ module ActiveRabbit
|
|
|
127
127
|
track_exception(exception, context: context, user_id: user_id, tags: tags)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
def
|
|
130
|
+
def notify_release(version: nil, environment: nil, metadata: {})
|
|
131
|
+
return unless configured?
|
|
132
|
+
|
|
133
|
+
cfg = configuration
|
|
134
|
+
version ||= cfg.revision || cfg.release
|
|
135
|
+
environment ||= cfg.environment
|
|
136
|
+
return if version.nil? || version.to_s.strip.empty?
|
|
137
|
+
|
|
131
138
|
payload = {
|
|
132
|
-
revision: Client.configuration.revision,
|
|
133
|
-
environment: Client.configuration.environment,
|
|
134
|
-
project_slug: project_slug,
|
|
135
139
|
version: version,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
started_at: started_at,
|
|
139
|
-
finished_at: finished_at
|
|
140
|
+
environment: environment,
|
|
141
|
+
metadata: metadata || {}
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
http_client.
|
|
144
|
+
http_client.post_release(payload)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def log(level, message)
|
|
148
|
+
cfg = configuration
|
|
149
|
+
return if cfg.nil? || cfg.disable_console_logs
|
|
150
|
+
|
|
151
|
+
case level
|
|
152
|
+
when :info then cfg.logger&.info(message)
|
|
153
|
+
when :debug then cfg.logger&.debug(message)
|
|
154
|
+
when :error then cfg.logger&.error(message)
|
|
155
|
+
end
|
|
143
156
|
end
|
|
144
157
|
|
|
145
158
|
private
|
|
@@ -13,7 +13,7 @@ module ActiveRabbit
|
|
|
13
13
|
begin
|
|
14
14
|
ActiveRabbit::Reporting.report_exception(e, env: env, handled: false, source: "middleware", force: true)
|
|
15
15
|
rescue => inner
|
|
16
|
-
|
|
16
|
+
ActiveRabbit::Client.log(:error, "[ActiveRabbit] ErrorCaptureMiddleware failed: #{inner.class}: #{inner.message}")
|
|
17
17
|
end
|
|
18
18
|
raise
|
|
19
19
|
end
|
|
@@ -40,7 +40,7 @@ module ActiveRabbit
|
|
|
40
40
|
force: force)
|
|
41
41
|
rescue => e
|
|
42
42
|
if defined?(Rails)
|
|
43
|
-
|
|
43
|
+
ActiveRabbit::Client.log(:error, "[ActiveRabbit] report_exception failed: #{e.class}: #{e.message}")
|
|
44
44
|
end
|
|
45
45
|
nil
|
|
46
46
|
end
|
|
@@ -8,7 +8,7 @@ module ActiveRabbit
|
|
|
8
8
|
begin
|
|
9
9
|
ActiveRabbit::Reporting.report_exception(error, env: env, handled: true, source: "router", force: true)
|
|
10
10
|
rescue => e
|
|
11
|
-
|
|
11
|
+
ActiveRabbit::Client.log(:error, "[ActiveRabbit] NotFoundApp failed to report: #{e.class}: #{e.message}")
|
|
12
12
|
end
|
|
13
13
|
[404, { "Content-Type" => "text/plain" }, ["Not Found"]]
|
|
14
14
|
end
|
|
@@ -14,9 +14,9 @@ puts "๐ ActiveRabbit Production Readiness Test"
|
|
|
14
14
|
puts "=" * 50
|
|
15
15
|
|
|
16
16
|
# Test configuration
|
|
17
|
-
TEST_API_KEY = ENV['
|
|
18
|
-
TEST_PROJECT_ID = ENV['
|
|
19
|
-
TEST_API_URL = ENV['
|
|
17
|
+
TEST_API_KEY = ENV['ACTIVERABBIT_API_KEY'] || 'test-key-for-validation'
|
|
18
|
+
TEST_PROJECT_ID = ENV['ACTIVERABBIT_PROJECT_ID'] || 'test-project'
|
|
19
|
+
TEST_API_URL = ENV['ACTIVERABBIT_API_URL'] || 'https://api.activerabbit.com'
|
|
20
20
|
|
|
21
21
|
# Load the gem
|
|
22
22
|
begin
|
|
@@ -374,7 +374,7 @@ puts "=" * 50
|
|
|
374
374
|
|
|
375
375
|
if TEST_API_KEY == 'test-key-for-validation'
|
|
376
376
|
puts "โ ๏ธ Note: Tests run with mock configuration"
|
|
377
|
-
puts " Set
|
|
377
|
+
puts " Set ACTIVERABBIT_API_KEY and ACTIVERABBIT_PROJECT_ID"
|
|
378
378
|
puts " environment variables for full integration testing"
|
|
379
379
|
else
|
|
380
380
|
puts "โ
Full integration test completed with real API"
|
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.
|
|
4
|
+
version: 0.5.1
|
|
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-
|
|
11
|
+
date: 2025-12-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|
|
@@ -86,7 +86,6 @@ files:
|
|
|
86
86
|
- examples/rails_app_testing.rb
|
|
87
87
|
- examples/rails_integration.rb
|
|
88
88
|
- examples/standalone_usage.rb
|
|
89
|
-
- lib/active_rabbit-client.gemspec
|
|
90
89
|
- lib/active_rabbit.rb
|
|
91
90
|
- lib/active_rabbit/client.rb
|
|
92
91
|
- lib/active_rabbit/client/action_mailer_patch.rb
|
|
File without changes
|