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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 954a33d564508a6af9e73defded58f6b4ea9f0b359da045d7566a91f7bfc846d
4
- data.tar.gz: 4123b66a3933827a2bca1a78e2a235aa3a76abac2db5e209d60e6626edbdbc2f
3
+ metadata.gz: 7ca3568f93b7ec8211e47cf961fdf459f297a2d674fac08fe7c06a4001d09cdc
4
+ data.tar.gz: 6b3caffa37d7687c1d9038360c6787f7846e64bd30b722e4694b5d826b149a39
5
5
  SHA512:
6
- metadata.gz: 4a4d4f10428dc2e7bf4b96606b1d0ddf809244f046bf5c4ec880a043d9a9436777851146b03e1882c8e306f512e9e83bef09148377e237348ee43246f35399a0
7
- data.tar.gz: ca02b6426b33d2117455ae68d99fd20e47bb9ba792f8b18bd02b55caf09de6227a5442da53b4baa29221d81f3148dc19d34c67a578eced741f6f1c75629dc7af
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("active_rabbit_API_KEY").and_return("test-key")
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['active_rabbit_API_KEY'] || 'test-key'
490
- config.project_id = ENV['active_rabbit_PROJECT_ID'] || 'test-project'
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
- active_rabbit_API_KEY: ${{ secrets.active_rabbit_API_KEY }}
582
- active_rabbit_PROJECT_ID: ${{ secrets.active_rabbit_PROJECT_ID }}
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['active_rabbit_API_KEY']
9
- config.project_id = ENV['active_rabbit_PROJECT_ID']
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('active_rabbit_API_URL', 'https://api.activerabbit.com')
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['active_rabbit_API_KEY']
144
- config.project_id = ENV['active_rabbit_PROJECT_ID']
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['active_rabbit_API_KEY']
253
- config.project_id = ENV['active_rabbit_PROJECT_ID']
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("active_rabbit_API_URL", "https://api.activerabbit.ai")
22
- @api_key = ENV["active_rabbit_API_KEY"]
23
- @project_id = ENV["active_rabbit_PROJECT_ID"]
24
- @environment = ENV.fetch("active_rabbit_ENVIRONMENT", detect_environment)
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
- configuration.logger&.info("[ActiveRabbit] Preparing to send exception: #{exception.class.name}")
36
- configuration.logger&.debug("[ActiveRabbit] Exception data: #{exception_data.inspect}")
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
- configuration.logger&.error("[ActiveRabbit] Missing required fields in exception data")
41
- configuration.logger&.debug("[ActiveRabbit] Available fields: #{exception_data.keys.inspect}")
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
- configuration.logger&.error("[ActiveRabbit] Failed to send exception - both primary and fallback endpoints failed")
48
+ log(:error, "[ActiveRabbit] Failed to send exception - both primary and fallback endpoints failed")
49
49
  return nil
50
50
  end
51
51
 
52
- configuration.logger&.info("[ActiveRabbit] Exception successfully sent to API")
53
- configuration.logger&.debug("[ActiveRabbit] API Response: #{response.inspect}")
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
- configuration.logger&.debug("[ActiveRabbit] Built exception data:")
136
- configuration.logger&.debug("[ActiveRabbit] - Required fields: class=#{data[:exception_class]}, message=#{data[:message]}, backtrace=#{data[:backtrace]&.first}")
137
- configuration.logger&.debug("[ActiveRabbit] - Error details: type=#{data[:error_type]}, source=#{data[:error_source]}, component=#{data[:error_component]}")
138
- configuration.logger&.debug("[ActiveRabbit] - Request info: path=#{data[:request_path]}, method=#{data[:request_method]}, action=#{data[:controller_action]}")
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
- configuration.logger&.info("[ActiveRabbit] Sending exception to API...")
41
- configuration.logger&.debug("[ActiveRabbit] Exception payload (pre-JSON): #{safe_preview(exception_data_with_type)}")
42
- configuration.logger&.debug("[ActiveRabbit] Target path: #{path}")
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
- configuration.logger&.info("[ActiveRabbit] Making request to primary endpoint: POST #{path}")
46
+ log(:info, "[ActiveRabbit] Making request to primary endpoint: POST #{path}")
47
47
  response = enqueue_request(:post, path, exception_data_with_type)
48
- configuration.logger&.info("[ActiveRabbit] Exception sent successfully (errors endpoint)")
48
+ log(:info, "[ActiveRabbit] Exception sent successfully (errors endpoint)")
49
49
  return response
50
50
  rescue => e
51
- configuration.logger&.error("[ActiveRabbit] Primary send failed: #{e.class}: #{e.message}")
52
- configuration.logger&.error("[ActiveRabbit] Primary error backtrace: #{e.backtrace&.first(3)}")
53
- configuration.logger&.debug("[ActiveRabbit] Falling back to /api/v1/events with type=error")
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
- configuration.logger&.info("[ActiveRabbit] Making request to fallback endpoint: POST #{fallback_path}")
59
+ log(:info, "[ActiveRabbit] Making request to fallback endpoint: POST #{fallback_path}")
60
60
  response = enqueue_request(:post, fallback_path, fallback_body)
61
- configuration.logger&.info("[ActiveRabbit] Exception sent via fallback endpoint")
61
+ log(:info, "[ActiveRabbit] Exception sent via fallback endpoint")
62
62
  return response
63
63
  rescue => e2
64
- configuration.logger&.error("[ActiveRabbit] Fallback send failed: #{e2.class}: #{e2.message}")
65
- configuration.logger&.error("[ActiveRabbit] Fallback error backtrace: #{e2.backtrace&.first(3)}")
66
- configuration.logger&.error("[ActiveRabbit] All exception sending attempts failed")
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
- configuration.logger&.info("[ActiveRabbit] Sending batch of #{events.length} events...")
88
+ log(:info, "[ActiveRabbit] Sending batch of #{events.length} events...")
89
89
  response = make_request(:post, "/api/v1/events/batch", { events: events })
90
- configuration.logger&.info("[ActiveRabbit] Batch sent successfully")
91
- configuration.logger&.debug("[ActiveRabbit] Batch response: #{response.inspect}")
90
+ log(:info, "[ActiveRabbit] Batch sent successfully")
91
+ log(:debug, "[ActiveRabbit] Batch response: #{response.inspect}")
92
92
  response
93
93
  end
94
94
 
95
- def post(path, payload)
96
- uri = URI.join(@base_uri.to_s, path)
97
- req = Net::HTTP::Post.new(uri)
98
- req['Content-Type'] = 'application/json'
99
- req["X-Project-Token"] = configuration.api_key
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
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
103
- http.request(req)
104
- end
101
+ log(:info, "[ActiveRabbit] Pinging release to API...")
102
+ log(:debug, "[ActiveRabbit] Release payload: #{safe_preview(payload)}")
105
103
 
106
- unless res.is_a?(Net::HTTPSuccess)
107
- raise APIError, "ActiveRabbit API request failed: #{res.code} #{res.body}"
108
- end
104
+ response = perform_request(uri, :post, payload)
105
+ code = response.code.to_i
109
106
 
110
- JSON.parse(res.body)
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
- @configuration.logger&.error("[ActiveRabbit] HTTP POST failed: #{e.class}: #{e.message}")
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
- configuration.logger&.info("[ActiveRabbit] Starting flush with #{@request_queue.length} items")
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
- configuration.logger&.info("[ActiveRabbit] Sending batch of #{batch.length} items")
139
+ log(:info, "[ActiveRabbit] Sending batch of #{batch.length} items")
135
140
  response = post_batch(batch)
136
- configuration.logger&.info("[ActiveRabbit] Batch sent successfully: #{response.inspect}")
141
+ log(:info, "[ActiveRabbit] Batch sent successfully: #{response.inspect}")
137
142
  response
138
143
  rescue => e
139
- configuration.logger&.error("[ActiveRabbit] Failed to send batch: #{e.message}")
140
- configuration.logger&.error("[ActiveRabbit] Backtrace: #{e.backtrace&.first(3)}")
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
- configuration.logger&.info("[ActiveRabbit] Enqueueing request: #{method} #{path}")
165
- configuration.logger&.debug("[ActiveRabbit] Request data: #{data.inspect}")
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
- configuration.logger&.info("[ActiveRabbit] Making request: #{method.upcase} #{uri}")
194
- configuration.logger&.debug("[ActiveRabbit] Request headers: X-Project-Token=#{configuration.api_key}, X-Project-ID=#{configuration.project_id}")
195
- configuration.logger&.debug("[ActiveRabbit] Request body: #{safe_preview(data)}")
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
- configuration.logger&.info("[ActiveRabbit] Response status: #{response.code}")
204
- configuration.logger&.debug("[ActiveRabbit] Response headers: #{response.to_hash.inspect}")
205
- configuration.logger&.debug("[ActiveRabbit] Response body: #{response.body}")
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
- configuration.logger&.debug("[ActiveRabbit] Parsed response: #{result.inspect}")
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
- configuration.logger&.info("[ActiveRabbit] Retrying request (#{retries}/#{max_retries})")
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
- configuration.logger&.info("[ActiveRabbit] Retrying request after timeout (#{retries}/#{max_retries})")
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
- configuration.logger&.info("[ActiveRabbit] Retrying request after connection error (#{retries}/#{max_retries})")
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
- configuration.logger&.error("[ActiveRabbit] API error: #{e.class}: #{e.message}")
255
+ log(:error, "[ActiveRabbit] API error: #{e.class}: #{e.message}")
237
256
  raise e
238
257
  rescue => e
239
- configuration.logger&.error("[ActiveRabbit] Request failed: #{e.class}: #{e.message}")
240
- configuration.logger&.error("[ActiveRabbit] Backtrace: #{e.backtrace&.first(3)}")
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
- configuration.logger&.debug("[ActiveRabbit] Making HTTP request: #{method.upcase} #{uri}")
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
- configuration.logger&.debug("[ActiveRabbit] Request path: #{uri.request_uri}")
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
- configuration.logger&.debug("[ActiveRabbit] Response code: #{response.code}")
295
- configuration.logger&.debug("[ActiveRabbit] Response body: #{response.body}")
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
- configuration.logger&.debug("[ActiveRabbit] Parsed response: #{parsed.inspect}")
322
+ log(:debug, "[ActiveRabbit] Parsed response: #{parsed.inspect}")
304
323
  parsed
305
324
  rescue JSON::ParserError => e
306
- configuration.logger&.error("[ActiveRabbit] Failed to parse response: #{e.message}")
307
- configuration.logger&.error("[ActiveRabbit] Raw response: #{response.body}")
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
- configuration.logger&.debug("[ActiveRabbit] Empty response body")
330
+ log(:debug, "[ActiveRabbit] Empty response body")
312
331
  {}
313
332
  end
314
333
  when 429
315
- configuration.logger&.error("[ActiveRabbit] Rate limit exceeded")
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
- configuration.logger&.error("[ActiveRabbit] Client error (#{response.code}): #{error_message}")
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
- configuration.logger&.error("[ActiveRabbit] Server error (#{response.code}): #{error_message}")
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
- configuration.logger&.error("[ActiveRabbit] Unexpected response code: #{response.code}")
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
- if Rails.env.development?
25
- puts "\n=== ActiveRabbit Configure ==="
26
- puts "Environment: #{Rails.env}"
27
- puts "Already configured? #{ActiveRabbit::Client.configured?}"
28
- puts "================================\n"
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 = Rails.env
34
- config.logger = Rails.logger rescue Logger.new(STDOUT)
35
- config.release = detect_release(app)
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
- if Rails.env.development?
39
- puts "\n=== ActiveRabbit Post-Configure ==="
40
- puts "Now configured? #{ActiveRabbit::Client.configured?}"
41
- puts "Configuration: #{ActiveRabbit::Client.configuration.inspect}"
42
- puts "================================\n"
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
- Rails.logger.info "[ActiveRabbit] Setting up performance notifications subscriptions"
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
- Rails.logger.info "[ActiveRabbit] Subscriptions setup complete"
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
- puts "\n=== ActiveRabbit Railtie Loading ==="
89
- puts "Rails Environment: #{Rails.env}"
90
- puts "Rails Middleware Stack Phase: #{app.middleware.respond_to?(:middlewares) ? 'Ready' : 'Not Ready'}"
91
- puts "================================\n"
92
- puts "\n=== Initial Middleware Stack ==="
93
- puts "(not available at this boot phase)"
94
- puts "=======================\n"
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
- puts "\n=== Adding ActiveRabbit Middleware ===" if Rails.env.development?
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
- puts "[ActiveRabbit] Found DebugExceptions, configuring middleware..." if Rails.env.development?
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
- puts "[ActiveRabbit] Cleaned up existing middleware" if Rails.env.development?
144
+ ar_puts "[ActiveRabbit] Cleaned up existing middleware" if Rails.env.development?
108
145
  rescue => e
109
- puts "[ActiveRabbit] Error cleaning middleware: #{e.message}"
146
+ ar_puts "[ActiveRabbit] Error cleaning middleware: #{e.message}"
110
147
  end
111
148
 
112
149
  # Insert middleware in the correct order
113
- puts "[ActiveRabbit] Inserting middleware..." if Rails.env.development?
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
- puts "[ActiveRabbit] Inserting RequestContextMiddleware before RequestId" if Rails.env.development?
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
- puts "[ActiveRabbit] Inserting ExceptionMiddleware before DebugExceptions" if Rails.env.development?
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
- puts "[ActiveRabbit] Inserting RoutingErrorCatcher after DebugExceptions" if Rails.env.development?
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
- puts "[ActiveRabbit] Middleware insertion complete" if Rails.env.development?
167
+ ar_puts "[ActiveRabbit] Middleware insertion complete" if Rails.env.development?
131
168
 
132
169
  elsif defined?(ActionDispatch::ShowExceptions)
133
- puts "[ActiveRabbit] Found ShowExceptions, configuring middleware..." if Rails.env.development?
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
- puts "[ActiveRabbit] Cleaned up existing middleware" if Rails.env.development?
177
+ ar_puts "[ActiveRabbit] Cleaned up existing middleware" if Rails.env.development?
141
178
  rescue => e
142
- puts "[ActiveRabbit] Error cleaning middleware: #{e.message}"
179
+ ar_puts "[ActiveRabbit] Error cleaning middleware: #{e.message}"
143
180
  end
144
181
 
145
182
  # Insert middleware in the correct order
146
- puts "[ActiveRabbit] Inserting middleware..." if Rails.env.development?
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
- puts "[ActiveRabbit] Inserting RequestContextMiddleware before RequestId" if Rails.env.development?
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
- puts "[ActiveRabbit] Inserting ExceptionMiddleware before ShowExceptions" if Rails.env.development?
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
- puts "[ActiveRabbit] Inserting RoutingErrorCatcher after ShowExceptions" if Rails.env.development?
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
- puts "[ActiveRabbit] No exception handlers found, using fallback configuration" if Rails.env.development?
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
- puts "\n=== Final Middleware Stack ==="
173
- puts "(will be printed after initialize)"
174
- puts "=======================\n"
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
- puts "\n=== ExceptionMiddleware Enter ==="
184
- puts "Path: #{env['PATH_INFO']}"
185
- puts "Method: #{env['REQUEST_METHOD']}"
186
- puts "Current Exception: #{env['action_dispatch.exception']&.class} - #{env['action_dispatch.exception']&.message}"
187
- puts "Current Error: #{env['action_dispatch.error']&.class} - #{env['action_dispatch.error']&.message}"
188
- puts "Rack Exception: #{env['rack.exception']&.class} - #{env['rack.exception']&.message}"
189
- puts "Exception Backtrace: #{env['action_dispatch.exception']&.backtrace&.first(3)&.join("\n ")}"
190
- puts "Error Backtrace: #{env['action_dispatch.error']&.backtrace&.first(3)&.join("\n ")}"
191
- puts "Rack Backtrace: #{env['rack.exception']&.backtrace&.first(3)&.join("\n ")}"
192
- puts "============================\n"
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
- puts "\n=== ExceptionMiddleware Exit (Success) ==="
197
- puts "Status: #{status}"
198
- puts "Headers: #{headers.inspect}"
199
- puts "Final Exception: #{env['action_dispatch.exception']&.class} - #{env['action_dispatch.exception']&.message}"
200
- puts "Final Error: #{env['action_dispatch.error']&.class} - #{env['action_dispatch.error']&.message}"
201
- puts "Final Rack Exception: #{env['rack.exception']&.class} - #{env['rack.exception']&.message}"
202
- puts "Final Exception Backtrace: #{env['action_dispatch.exception']&.backtrace&.first(3)&.join("\n ")}"
203
- puts "Final Error Backtrace: #{env['action_dispatch.error']&.backtrace&.first(3)&.join("\n ")}"
204
- puts "Final Rack Backtrace: #{env['rack.exception']&.backtrace&.first(3)&.join("\n ")}"
205
- puts "===========================\n"
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
- puts "\n=== ExceptionMiddleware Exit (Error) ==="
209
- puts "Error: #{e.class} - #{e.message}"
210
- puts "Error Backtrace: #{e.backtrace&.first(3)&.join("\n ")}"
211
- puts "Original Exception: #{env['action_dispatch.exception']&.class} - #{env['action_dispatch.exception']&.message}"
212
- puts "Original Error: #{env['action_dispatch.error']&.class} - #{env['action_dispatch.error']&.message}"
213
- puts "Original Rack Exception: #{env['rack.exception']&.class} - #{env['rack.exception']&.message}"
214
- puts "Original Exception Backtrace: #{env['action_dispatch.exception']&.backtrace&.first(3)&.join("\n ")}"
215
- puts "Original Error Backtrace: #{env['action_dispatch.error']&.backtrace&.first(3)&.join("\n ")}"
216
- puts "Original Rack Backtrace: #{env['rack.exception']&.backtrace&.first(3)&.join("\n ")}"
217
- puts "===========================\n"
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
- puts "\n=== RoutingErrorCatcher Enter ==="
228
- puts "Path: #{env['PATH_INFO']}"
229
- puts "Method: #{env['REQUEST_METHOD']}"
230
- puts "Status: #{env['action_dispatch.exception']&.class}"
231
- puts "============================\n"
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
- puts "\n=== RoutingErrorCatcher Exit (Success) ==="
236
- puts "Status: #{status}"
237
- puts "===========================\n"
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
- puts "\n=== RoutingErrorCatcher Exit (Error) ==="
241
- puts "Error: #{e.class} - #{e.message}"
242
- puts "Backtrace: #{e.backtrace&.first(3)&.join("\n ")}"
243
- puts "===========================\n"
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
- Rails.logger.info "\n=== ActiveRabbit Configuration ==="
254
- Rails.logger.info "Version: #{ActiveRabbit::Client::VERSION}"
255
- Rails.logger.info "Environment: #{Rails.env}"
256
- Rails.logger.info "API URL: #{ActiveRabbit::Client.configuration.api_url}"
257
- Rails.logger.info "================================"
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
- Rails.logger.info "\n=== Middleware Stack ==="
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
- Rails.logger.info " #{klass}"
300
+ ar_log(:info, " #{klass}")
263
301
  end
264
- Rails.logger.info "======================="
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
- Rails.logger.info "[ActiveRabbit] Middleware configured successfully"
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
- Rails.logger.info "[ActiveRabbit] Reporting loaded from #{reporting_file}:#{reporting_line}" if defined?(Rails)
378
- Rails.logger.info "[ActiveRabbit] HttpClient#post_exception from #{http_file}:#{http_line}" if defined?(Rails)
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
- Rails.logger.info "[ActiveRabbit] Subscribing to controller events (configured=#{ActiveRabbit::Client.configured?})"
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
- return
467
+ next
402
468
  end
403
469
 
404
470
  duration_ms = ((finished - started) * 1000).round(2)
405
471
 
406
- Rails.logger.info "[ActiveRabbit] ๐Ÿ“Š Controller action: #{payload[:controller]}##{payload[:action]} - #{duration_ms}ms"
407
- Rails.logger.info "[ActiveRabbit] ๐Ÿ“Š DB runtime: #{payload[:db_runtime]}, View runtime: #{payload[:view_runtime]}"
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
- Rails.logger.info "[AR] ExceptionMiddleware ENTER path=#{env['PATH_INFO']}" if defined?(Rails)
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
- Rails.logger.info "[AR] env exception present: #{ex.class}: #{ex.message}" if defined?(Rails)
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
- Rails.logger.info "[AR] env exception NOT present" if defined?(Rails)
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
- Rails.logger.info "[AR] RESCUE caught: #{e.class}: #{e.message}" if defined?(Rails)
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
- Rails.logger.info "[ActiveRabbit] Tracked #{source}: #{exception.class.name} - #{exception.message}" if defined?(Rails)
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}"
@@ -1,7 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
1
  module ActiveRabbit
4
2
  module Client
5
- VERSION = "0.4.8"
3
+ VERSION = "0.5.1"
6
4
  end
7
5
  end
@@ -77,8 +77,8 @@ module ActiveRabbit
77
77
  result = exception_tracker.track_exception(**args)
78
78
 
79
79
  # Log the result
80
- configuration.logger&.info("[ActiveRabbit] Exception tracked: #{exception.class.name}")
81
- configuration.logger&.debug("[ActiveRabbit] Exception tracking result: #{result.inspect}")
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 notify_deploy(project_slug:, status:, user:, version:, started_at: nil, finished_at: nil)
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
- status: status,
137
- user: user,
138
- started_at: started_at,
139
- finished_at: finished_at
140
+ environment: environment,
141
+ metadata: metadata || {}
140
142
  }
141
143
 
142
- http_client.post("/api/v1/deploys", payload)
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
- Rails.logger&.error("[ActiveRabbit] ErrorCaptureMiddleware failed: #{inner.class}: #{inner.message}") if defined?(Rails)
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
- Rails.logger&.error("[ActiveRabbit] report_exception failed: #{e.class}: #{e.message}")
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
- Rails.logger&.error("[ActiveRabbit] NotFoundApp failed to report: #{e.class}: #{e.message}") if defined?(Rails)
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['active_rabbit_API_KEY'] || 'test-key-for-validation'
18
- TEST_PROJECT_ID = ENV['active_rabbit_PROJECT_ID'] || 'test-project'
19
- TEST_API_URL = ENV['active_rabbit_API_URL'] || 'https://api.activerabbit.com'
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 active_rabbit_API_KEY and active_rabbit_PROJECT_ID"
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.8
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-27 00:00:00.000000000 Z
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