activerabbit-ai 0.6.1 → 0.6.3

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: 8e1cd1d7c16eb9b4cea15e63674f3c89156c9d8e3b88d51ff2cb17b149e717cf
4
- data.tar.gz: b5e54da451cfa9d98e7ba55a65940af01707bb2f0b17254000ac0aa07e86a8e8
3
+ metadata.gz: d02e83fd5992873b3b7aabb322ab9e3c914b91e7e64b4a80be335007561bc404
4
+ data.tar.gz: d60e55a0fcab7c981d7b7a214cff142825c613e7d32ddf15533bccd451cb6657
5
5
  SHA512:
6
- metadata.gz: 2d3aa17b86a6c204454d70374c653e5f50c2648cb236941f13c063e4d9d932d950b4bc8425ea115e7f16d84dee69355fba29466fef28e75daedb3c6a95f3cbf3
7
- data.tar.gz: f7243d857f07247ce600ed7f98156b96e4591e68af2aa954ad6357ba20282d475cd521c55beb4bb74b02133f7ae697d3c973cfaf1b07bcb98132602045c4b220
6
+ metadata.gz: 32a57acfde857e55a97932f56ae1a449f04a7e51e7ad8903d2cf63380ddd338cf0930d18785559f2bf358250470ce1890f503dde670aaebddf1756f6b5675c2f
7
+ data.tar.gz: dad613e6edf130e26c185b2b17670366400c4a40a36ebf0d8017a77189f01c1647c0cbd3ef7f810727a47beccf46b5f482394b681424982ad64a020eb1bcd768
data/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.6.3] - 2026-03-30
6
+
7
+ ### Added
8
+ - **HTTP client request id**: `make_request` sends a stable `X-Request-Id` header (same value across retries) so APIs can correlate or deduplicate requests after timeouts and transient failures.
9
+ - **Explicit `securerandom` load**: `HttpClient` requires `securerandom` so `SecureRandom` is always available regardless of load order.
10
+
11
+ ## [0.6.2] - 2026-01-26
12
+
13
+ ### Fixed
14
+ - **ActionMailer deliver_later RuntimeError**: Fixed critical bug where `deliver_later` would raise `RuntimeError: You've accessed the message before asking to deliver it later`
15
+ - The patch was incorrectly accessing `message.subject` and `message.to` before calling `super`
16
+ - Rails requires that `message` is NOT accessed before `deliver_later` because only mailer method arguments are passed to the job
17
+ - Now only tracks safe metadata: `@mailer_class`, `@action`, and argument types
18
+ - Added error handling to ensure tracking failures don't break email delivery
19
+
20
+ ### Added
21
+ - **ActionMailer patch tests**: 18 comprehensive tests for the ActionMailer integration
22
+ - Verifies `deliver_later` does NOT access the message object
23
+ - Verifies `deliver_now` correctly tracks message details
24
+ - Tests error handling and edge cases
25
+
5
26
  ## [0.6.1] - 2026-01-09
6
27
 
7
28
  ### Fixed
@@ -14,7 +14,8 @@ module ActiveRabbit
14
14
  ActiveRabbit::Client.track_event(
15
15
  "email_sent",
16
16
  {
17
- mailer: self.class.name,
17
+ mailer_class: @mailer_class.name,
18
+ action: @action,
18
19
  message_id: (message.message_id rescue nil),
19
20
  subject: (message.subject rescue nil),
20
21
  to: (Array(message.to).first rescue nil),
@@ -24,11 +25,26 @@ module ActiveRabbit
24
25
  end
25
26
  end
26
27
 
27
- def deliver_later
28
- ActiveRabbit::Client.track_event(
29
- "email_enqueued",
30
- { mailer: self.class.name, subject: (message.subject rescue nil), to: (Array(message.to).first rescue nil) }
31
- ) if ActiveRabbit::Client.configured?
28
+ def deliver_later(...)
29
+ # IMPORTANT: Do NOT access `message` here!
30
+ # Rails raises RuntimeError if you access the message before deliver_later
31
+ # because only mailer method arguments are passed to the job.
32
+ # We can only safely access metadata that doesn't touch the message object.
33
+ if ActiveRabbit::Client.configured?
34
+ begin
35
+ ActiveRabbit::Client.track_event(
36
+ "email_enqueued",
37
+ {
38
+ mailer_class: @mailer_class.name,
39
+ action: @action,
40
+ args: @args&.map { |a| a.class.name }
41
+ }
42
+ )
43
+ rescue => e
44
+ # Don't let tracking failures break email delivery
45
+ Rails.logger.error "[ActiveRabbit] Failed to track email_enqueued: #{e.message}" if defined?(Rails) && Rails.logger
46
+ end
47
+ end
32
48
  super
33
49
  end
34
50
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRabbit
4
+ module Client
5
+ # Sentry-style cron check-ins for Active Job: in_progress at start, ok on success, error on failure.
6
+ #
7
+ # class BackupJob < ApplicationJob
8
+ # include ActiveRabbit::Client::CronMonitor
9
+ # active_rabbit_cron slug: "nightly_backup"
10
+ #
11
+ # def perform
12
+ # # ...
13
+ # end
14
+ # end
15
+ module CronMonitor
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ base.class_eval do
19
+ around_perform :_active_rabbit_cron_monitor_around
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ # @param value [String, Symbol, Hash] string/symbol slug, or { slug: "..." }
25
+ def active_rabbit_cron(value = nil)
26
+ case value
27
+ when String, Symbol
28
+ @active_rabbit_cron_slug = value.to_s.strip
29
+ when Hash
30
+ s = value[:slug].to_s.strip
31
+ @active_rabbit_cron_slug = s.empty? ? nil : s
32
+ when nil
33
+ @active_rabbit_cron_slug = nil
34
+ end
35
+ end
36
+
37
+ def active_rabbit_cron_monitor_slug
38
+ slug = instance_variable_defined?(:@active_rabbit_cron_slug) ? @active_rabbit_cron_slug : nil
39
+ return slug if slug.is_a?(String) && !slug.strip.empty?
40
+
41
+ name.to_s.underscore.tr("/", "_")
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def _active_rabbit_cron_monitor_around(&block)
48
+ slug = self.class.active_rabbit_cron_monitor_slug
49
+ if slug.to_s.strip.empty? || !ActiveRabbit::Client.configured?
50
+ block.call
51
+ return
52
+ end
53
+
54
+ ActiveRabbit::Client.capture_cron_check_in(slug, :in_progress)
55
+ begin
56
+ block.call
57
+ rescue StandardError => e
58
+ ActiveRabbit::Client.capture_cron_check_in(slug, :error)
59
+ raise e
60
+ end
61
+ ActiveRabbit::Client.capture_cron_check_in(slug, :ok)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -131,6 +131,9 @@ module ActiveRabbit
131
131
  structured_stack_trace: structured_frames,
132
132
  culprit_frame: culprit_frame,
133
133
 
134
+ # Error source: backend (Ruby gem) vs frontend (JS SDK)
135
+ source: "backend",
136
+
134
137
  # Timing and environment
135
138
  occurred_at: Time.now.iso8601(3),
136
139
  environment: configuration.environment || 'development',
@@ -6,6 +6,7 @@ require "json"
6
6
  require "concurrent"
7
7
  require "time"
8
8
  require "uri"
9
+ require "securerandom"
9
10
 
10
11
  module ActiveRabbit
11
12
  module Client
@@ -140,6 +141,12 @@ module ActiveRabbit
140
141
  { success: false, error: e.message }
141
142
  end
142
143
 
144
+ # Cron / heartbeat check-in (project token + monitor slug). Synchronous, not batched.
145
+ def post_cron_check_in(slug:, status: :ok)
146
+ payload = stringify_and_sanitize({ slug: slug.to_s, status: status.to_s })
147
+ make_request(:post, "/api/v1/cron/check_ins", payload)
148
+ end
149
+
143
150
  def flush
144
151
  return if @request_queue.empty?
145
152
 
@@ -225,12 +232,16 @@ module ActiveRabbit
225
232
  log(:debug, "[ActiveRabbit] Request headers: X-Project-Token=#{configuration.api_key}, X-Project-ID=#{configuration.project_id}")
226
233
  log(:debug, "[ActiveRabbit] Request body: #{safe_preview(data)}")
227
234
 
235
+ # Generate a stable request ID for dedup — same ID is reused across retries
236
+ # so the server can reject duplicates when we retry after a timeout.
237
+ request_id = SecureRandom.uuid
238
+
228
239
  # Retry logic with exponential backoff
229
240
  retries = 0
230
241
  max_retries = configuration.retry_count
231
242
 
232
243
  begin
233
- response = perform_request(uri, method, data)
244
+ response = perform_request(uri, method, data, request_id: request_id)
234
245
  log(:info, "[ActiveRabbit] Response status: #{response.code}")
235
246
  log(:debug, "[ActiveRabbit] Response headers: #{response.to_hash.inspect}")
236
247
  log(:debug, "[ActiveRabbit] Response body: #{response.body}")
@@ -273,7 +284,7 @@ module ActiveRabbit
273
284
  end
274
285
  end
275
286
 
276
- def perform_request(uri, method, data)
287
+ def perform_request(uri, method, data, request_id: nil)
277
288
  log(:debug, "[ActiveRabbit] Making HTTP request: #{method.upcase} #{uri}")
278
289
  http = Net::HTTP.new(uri.host, uri.port)
279
290
 
@@ -308,6 +319,7 @@ module ActiveRabbit
308
319
  request['Accept'] = 'application/json'
309
320
  request['User-Agent'] = "ActiveRabbit-Client/#{ActiveRabbit::Client::VERSION}"
310
321
  request['X-Project-Token'] = configuration.api_key
322
+ request['X-Request-Id'] = request_id if request_id
311
323
 
312
324
  if configuration.project_id
313
325
  request['X-Project-ID'] = configuration.project_id
@@ -1,5 +1,5 @@
1
1
  module ActiveRabbit
2
2
  module Client
3
- VERSION = "0.6.1"
3
+ VERSION = "0.6.3"
4
4
  end
5
5
  end
@@ -24,6 +24,12 @@ rescue LoadError
24
24
  # Sidekiq not available, skip integration
25
25
  end
26
26
 
27
+ # Active Job cron monitors (optional)
28
+ begin
29
+ require_relative "client/cron_monitor" if defined?(ActiveJob)
30
+ rescue LoadError
31
+ end
32
+
27
33
  module ActiveRabbit
28
34
  module Client
29
35
  class Error < StandardError; end
@@ -144,6 +150,19 @@ module ActiveRabbit
144
150
  http_client.post_release(payload)
145
151
  end
146
152
 
153
+ # Cron / heartbeat monitor (uses project API token + slug). Status: :ok, :success, :in_progress, :error.
154
+ def capture_cron_check_in(slug, status = :ok)
155
+ return unless configured?
156
+
157
+ s = slug.to_s.strip
158
+ return nil if s.empty?
159
+
160
+ http_client.post_cron_check_in(slug: s, status: status)
161
+ rescue => e
162
+ log(:error, "[ActiveRabbit] capture_cron_check_in failed: #{e.class}: #{e.message}")
163
+ nil
164
+ end
165
+
147
166
  def log(level, message)
148
167
  cfg = configuration
149
168
  return if cfg.nil? || cfg.disable_console_logs
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerabbit-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Shapalov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-01-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: concurrent-ruby
@@ -81,7 +80,6 @@ files:
81
80
  - README.md
82
81
  - Rakefile
83
82
  - TESTING_GUIDE.md
84
- - activerabbit-ai-0.4.5.gem
85
83
  - check_api_data.rb
86
84
  - examples/rails_app_testing.rb
87
85
  - examples/rails_integration.rb
@@ -91,6 +89,7 @@ files:
91
89
  - lib/active_rabbit/client/action_mailer_patch.rb
92
90
  - lib/active_rabbit/client/active_job_extensions.rb
93
91
  - lib/active_rabbit/client/configuration.rb
92
+ - lib/active_rabbit/client/cron_monitor.rb
94
93
  - lib/active_rabbit/client/dedupe.rb
95
94
  - lib/active_rabbit/client/error_reporter.rb
96
95
  - lib/active_rabbit/client/event_processor.rb
@@ -121,7 +120,6 @@ metadata:
121
120
  allowed_push_host: https://rubygems.org
122
121
  source_code_uri: https://github.com/bugrabbit/active_rabbit-client
123
122
  changelog_uri: https://github.com/bugrabbit/active_rabbit-client/blob/main/CHANGELOG.md
124
- post_install_message:
125
123
  rdoc_options: []
126
124
  require_paths:
127
125
  - lib
@@ -136,8 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
134
  - !ruby/object:Gem::Version
137
135
  version: '0'
138
136
  requirements: []
139
- rubygems_version: 3.5.15
140
- signing_key:
137
+ rubygems_version: 4.0.7
141
138
  specification_version: 4
142
139
  summary: Ruby client for ActiveRabbit.ai application monitoring and error tracking
143
140
  test_files: []
Binary file