activerabbit-ai 0.4.1 → 0.4.4

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: cc6f70049c2b884248d9d15062ab2bb69887cdcf0430e92c96213d6a81559310
4
- data.tar.gz: 2dd36a9c979f33b42bbe22e5422bc659a7d4c0752b312d9b8345cd2d11ad65bb
3
+ metadata.gz: 86853d97a830e8d411ba9052ff231b29a2cddb526799e6b5c89e848ce026c7b2
4
+ data.tar.gz: 1faeb9a59503089d03b0ecda80fab72cd8028b3bfd49f4da5c5d25ff9cfb3139
5
5
  SHA512:
6
- metadata.gz: eebd3ced14adfd6831ec0405e2a33df376eb2e510156e37a78195038710133f75d2794b5aae06322ba3a7f1d29cb6633531a9eacc25e842df49995d85334739b
7
- data.tar.gz: cc369187974da2d7c9b8fffccfc0079f875e1d78ae3644197eedeadfc64d0ab3e881c99ee1755597f85371bd7d923dbac2f8e403c0f62666c39fe75b070a0ee8
6
+ metadata.gz: 760d010f75113d98d6e2f83fd3768d37d7cf67e307c24418717caa8c07ea489337ed7923ef876ed7bb95e4556e6780466efaec374d2ffc62d00b7b16a37f6fda
7
+ data.tar.gz: 441b28d1cc42c02a13c3efe4d387674932fceafca7c8571c6b24792ac69134338050ad030ebe65253ee48387e108b8b7d85014866bd42fed1e56f1c9fa356647
data/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.4.4] - 2025-10-22
6
+
7
+ ### Improved
8
+ - **Time-based error deduplication**: Changed from "once per server lifecycle" to time-window based
9
+ - Added `dedupe_window` configuration option (default: 300 seconds / 5 minutes)
10
+ - Set `dedupe_window` to `0` to disable deduplication (useful for development/testing)
11
+ - Automatic memory cleanup of old dedupe entries (keeps last hour only)
12
+ - Better logging when errors are deduplicated
13
+
14
+ ### Fixed
15
+ - Error deduplication no longer prevents same error from being reported after time window expires
16
+ - Memory leak prevention by cleaning old deduplication entries
17
+
18
+ ## [0.4.3] - 2025-10-22
19
+
20
+ ### Fixed
21
+ - **Critical bug fix**: Removed reference to non-existent `ActiveRabbit::Client::Dedupe` class in error reporter
22
+ - Error tracking now works properly via Rails error reporter integration
23
+ - Errors are successfully captured and sent to ActiveRabbit API
24
+
25
+ ## [0.4.2] - 2025-01-04
26
+
27
+ ### Fixed
28
+ - **Major Rails 6.1 compatibility fix**: Added Rails Engine as primary integration method
29
+ - Engine loads after Rails initialization (safer than Railtie)
30
+ - Added comprehensive error handling for Rails loading edge cases
31
+ - Fallback mechanism: Engine -> Railtie -> Graceful degradation
32
+ - Fixed Logger initialization issues in Docker environments
33
+ - Added better error messages for debugging Rails integration issues
34
+
35
+ ### Added
36
+ - Rails Engine integration (`ActiveRabbit::Client::Engine`)
37
+ - Safer middleware insertion with error handling
38
+ - Enhanced shutdown hooks and signal handling
39
+ - Better request context management
40
+
5
41
  ## [0.4.1] - 2025-01-04
6
42
 
7
43
  ### Fixed
data/README.md CHANGED
@@ -236,6 +236,65 @@ ActiveRabbit::Client.configure do |config|
236
236
  end
237
237
  ```
238
238
 
239
+ ### Recommended Exceptions to Capture
240
+
241
+ Below is a practical list of exceptions APMs should capture by default in a Rails app. Some are optional/noisy and typically excluded unless needed.
242
+
243
+ - Core (Ruby/Stdlib)
244
+ - `StandardError`, `RuntimeError`, `NoMethodError`, `NameError`, `ArgumentError`, `TypeError`, `IndexError`, `KeyError`
245
+ - `Timeout::Error`, `JSON::ParserError`, `OpenSSL::SSL::SSLError`, `SocketError`, `Errno::ECONNREFUSED`/`ETIMEDOUT`/`EHOSTUNREACH`
246
+
247
+ - ActionPack / Controllers
248
+ - `ActionController::ParameterMissing`
249
+ - `ActionController::BadRequest`
250
+ - `ActionController::InvalidAuthenticityToken` (optional; can be noisy)
251
+ - `ActionController::UnknownFormat`
252
+ - `ActionController::NotImplemented`
253
+
254
+ - Routing (optional/noisy)
255
+ - `ActionController::RoutingError` (commonly excluded; enable only if needed)
256
+
257
+ - Views / Templates
258
+ - `ActionView::Template::Error`
259
+ - `ActionView::MissingTemplate`
260
+ - `Encoding::UndefinedConversionError` (template rendering)
261
+
262
+ - ActiveRecord / Database
263
+ - `ActiveRecord::RecordInvalid`
264
+ - `ActiveRecord::RecordNotFound` (optional; may be expected business logic)
265
+ - `ActiveRecord::StatementInvalid` (includes `PG::Error` subclasses)
266
+ - `ActiveRecord::Deadlocked`, `ActiveRecord::LockWaitTimeout`
267
+ - `ActiveRecord::RecordNotUnique`
268
+ - `ActiveRecord::ConnectionTimeoutError`
269
+ - `ActiveRecord::SerializationFailure`
270
+
271
+ - Background Jobs (ActiveJob/Sidekiq)
272
+ - `ActiveJob::DeserializationError`
273
+ - Any unhandled exception raised in job `perform`
274
+
275
+ - Networking/HTTP Clients
276
+ - `Net::OpenTimeout`, `Net::ReadTimeout`
277
+ - `Faraday::TimeoutError`, `Faraday::ConnectionFailed`
278
+ - `HTTP::Error` (http.rb), `RestClient::Exception`
279
+
280
+ - Caching/Redis
281
+ - `Redis::BaseError`, `Redis::TimeoutError`, `Redis::CannotConnectError`
282
+
283
+ - ActiveStorage
284
+ - `ActiveStorage::IntegrityError`
285
+ - `ActiveStorage::FileNotFoundError`
286
+
287
+ - ActionCable
288
+ - `ActionCable::Connection::Authorization::UnauthorizedError` (if applicable)
289
+
290
+ - Security/Crypto
291
+ - `ActiveSupport::MessageEncryptor::InvalidMessage`
292
+ - `ActiveSupport::MessageVerifier::InvalidSignature`
293
+
294
+ Notes:
295
+ - Optional/noisy: `RoutingError`, `RecordNotFound`, `InvalidAuthenticityToken`. Consider monitoring via metrics or targeted capture.
296
+ - If exceptions are rescued by your app, enable reporting of rescued exceptions (`before_send_exception`/custom middleware) so they are still tracked when appropriate.
297
+
239
298
  ### Callbacks
240
299
 
241
300
  ```ruby
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ return unless defined?(ActionMailer)
4
+
5
+ module ActiveRabbit
6
+ module Client
7
+ module ActionMailerPatch
8
+ def deliver_now
9
+ start_time = Time.now
10
+ super
11
+ ensure
12
+ if ActiveRabbit::Client.configured?
13
+ duration_ms = ((Time.now - start_time) * 1000).round(2)
14
+ ActiveRabbit::Client.track_event(
15
+ "email_sent",
16
+ {
17
+ mailer: self.class.name,
18
+ message_id: (message.message_id rescue nil),
19
+ subject: (message.subject rescue nil),
20
+ to: (Array(message.to).first rescue nil),
21
+ duration_ms: duration_ms
22
+ }
23
+ )
24
+ end
25
+ end
26
+
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?
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ ActionMailer::MessageDelivery.prepend(ActiveRabbit::Client::ActionMailerPatch)
39
+
40
+
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRabbit
4
+ module Client
5
+ module ActiveJobExtensions
6
+ def self.included(base)
7
+ base.around_perform do |job, block|
8
+ start_time = Time.now
9
+
10
+ Thread.current[:active_rabbit_job_context] = {
11
+ job_class: job.class.name,
12
+ job_id: job.job_id,
13
+ queue_name: job.queue_name,
14
+ arguments: ActiveRabbit::Client::ActiveJobExtensions.scrub_arguments(job.arguments),
15
+ provider_job_id: (job.respond_to?(:provider_job_id) ? job.provider_job_id : nil)
16
+ }
17
+
18
+ begin
19
+ block.call
20
+
21
+ duration_ms = ((Time.now - start_time) * 1000).round(2)
22
+ if ActiveRabbit::Client.configured?
23
+ ActiveRabbit::Client.track_performance(
24
+ "active_job.perform",
25
+ duration_ms,
26
+ metadata: { job_class: job.class.name, queue_name: job.queue_name, status: "completed" }
27
+ )
28
+ end
29
+ rescue Exception => exception
30
+ duration_ms = ((Time.now - start_time) * 1000).round(2)
31
+ if ActiveRabbit::Client.configured?
32
+ ActiveRabbit::Client.track_performance(
33
+ "active_job.perform",
34
+ duration_ms,
35
+ metadata: { job_class: job.class.name, queue_name: job.queue_name, status: "failed" }
36
+ )
37
+
38
+ ActiveRabbit::Client.track_exception(
39
+ exception,
40
+ context: {
41
+ job: {
42
+ job_class: job.class.name,
43
+ job_id: job.job_id,
44
+ queue_name: job.queue_name,
45
+ arguments: ActiveRabbit::Client::ActiveJobExtensions.scrub_arguments(job.arguments)
46
+ }
47
+ },
48
+ tags: { component: "active_job", queue: job.queue_name }
49
+ )
50
+ end
51
+ raise
52
+ ensure
53
+ Thread.current[:active_rabbit_job_context] = nil
54
+ end
55
+ end
56
+ end
57
+
58
+ def self.scrub_arguments(args)
59
+ return args unless ActiveRabbit::Client.configuration&.enable_pii_scrubbing
60
+ PiiScrubber.new(ActiveRabbit::Client.configuration).scrub(args)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+
@@ -11,12 +11,13 @@ module ActiveRabbit
11
11
  attr_accessor :batch_size, :flush_interval, :queue_size
12
12
  attr_accessor :enable_performance_monitoring, :enable_n_plus_one_detection
13
13
  attr_accessor :enable_pii_scrubbing, :pii_fields
14
- attr_accessor :ignored_exceptions, :ignored_user_agents
14
+ attr_accessor :ignored_exceptions, :ignored_user_agents, :ignore_404
15
15
  attr_accessor :release, :server_name, :logger
16
16
  attr_accessor :before_send_event, :before_send_exception
17
+ attr_accessor :dedupe_window # Time window in seconds for error deduplication (0 = disabled)
17
18
 
18
19
  def initialize
19
- @api_url = ENV.fetch("active_rabbit_API_URL", "https://api.activerabbit.com")
20
+ @api_url = ENV.fetch("active_rabbit_API_URL", "https://api.activerabbit.ai")
20
21
  @api_key = ENV["active_rabbit_API_KEY"]
21
22
  @project_id = ENV["active_rabbit_PROJECT_ID"]
22
23
  @environment = ENV.fetch("active_rabbit_ENVIRONMENT", detect_environment)
@@ -45,9 +46,10 @@ module ActiveRabbit
45
46
  ]
46
47
 
47
48
  # Filtering
49
+ # default ignores (404 controlled by ignore_404)
50
+ @ignore_404 = true
48
51
  @ignored_exceptions = %w[
49
52
  ActiveRecord::RecordNotFound
50
- ActionController::RoutingError
51
53
  ActionController::InvalidAuthenticityToken
52
54
  CGI::Session::CookieStore::TamperedWithCookie
53
55
  ]
@@ -59,6 +61,9 @@ module ActiveRabbit
59
61
  /Twitterbot/i
60
62
  ]
61
63
 
64
+ # Deduplication (0 = disabled, time in seconds for same error to be considered duplicate)
65
+ @dedupe_window = 300 # 5 minutes by default
66
+
62
67
  # Metadata
63
68
  @release = detect_release
64
69
  @server_name = detect_server_name
@@ -83,6 +88,14 @@ module ActiveRabbit
83
88
 
84
89
  def should_ignore_exception?(exception)
85
90
  return false unless exception
91
+ # Special-case 404 via flag
92
+ if @ignore_404
93
+ begin
94
+ return true if exception.is_a?(ActionController::RoutingError)
95
+ rescue NameError
96
+ # Ignore if AC not loaded
97
+ end
98
+ end
86
99
 
87
100
  ignored_exceptions.any? do |ignored|
88
101
  case ignored
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module ActiveRabbit
6
+ module Client
7
+ module Dedupe
8
+ extend self
9
+
10
+ WINDOW_SECONDS = 5
11
+
12
+ @seen = {}
13
+ @lock = Monitor.new
14
+
15
+ def seen_recently?(exception, context = {}, window: WINDOW_SECONDS)
16
+ key = build_key(exception, context)
17
+ now = Time.now.to_f
18
+ @lock.synchronize do
19
+ prune!(now, window)
20
+ last = @seen[key]
21
+ @seen[key] = now
22
+ return last && (now - last) < window
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def prune!(now, window)
29
+ cutoff = now - window
30
+ @seen.delete_if { |_k, ts| ts < cutoff }
31
+ end
32
+
33
+ def build_key(exception, context)
34
+ top = Array(exception.backtrace).first.to_s
35
+ req_id = context[:request]&.[](:request_id) || context[:request_id] || context[:requestId]
36
+ [exception.class.name, top, req_id].join("|")
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+ require_relative '../reporting'
4
+
5
+ module ActiveRabbit
6
+ module Client
7
+ module ErrorReporter
8
+ class Subscriber
9
+ def report(exception, handled:, severity:, context:, source: nil)
10
+ begin
11
+ Rails.logger.info "[ActiveRabbit] Error reporter caught: #{exception.class}: #{exception.message}" if defined?(Rails.logger)
12
+
13
+ # Time-based deduplication: track errors with timestamps
14
+ $reported_errors ||= {}
15
+
16
+ # Generate a unique key for this error
17
+ error_key = "#{exception.class.name}:#{exception.message}:#{exception.backtrace&.first}"
18
+
19
+ # Get dedupe window from config (default 5 minutes, 0 = disabled)
20
+ dedupe_window = defined?(ActiveRabbit::Client.configuration.dedupe_window) ?
21
+ ActiveRabbit::Client.configuration.dedupe_window : 300
22
+
23
+ current_time = Time.now.to_i
24
+ last_seen = $reported_errors[error_key]
25
+
26
+ # Report if: never seen before, OR dedupe disabled (0), OR outside dedupe window
27
+ should_report = last_seen.nil? || dedupe_window == 0 || (current_time - last_seen) > dedupe_window
28
+
29
+ if should_report
30
+ $reported_errors[error_key] = current_time
31
+
32
+ # Clean old entries to prevent memory leak (keep last hour)
33
+ $reported_errors.delete_if { |_, timestamp| current_time - timestamp > 3600 }
34
+
35
+ enriched = build_enriched_context(exception, handled: handled, severity: severity, context: context)
36
+ ActiveRabbit::Client.track_exception(exception, handled: handled, context: enriched)
37
+ else
38
+ Rails.logger.debug "[ActiveRabbit] Error deduplicated (last seen #{current_time - last_seen}s ago)" if defined?(Rails.logger)
39
+ end
40
+ rescue => e
41
+ Rails.logger.error "[ActiveRabbit] Error in ErrorReporter::Subscriber#report: #{e.class} - #{e.message}" if defined?(Rails.logger)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def build_enriched_context(exception, handled:, severity:, context: {})
48
+ ctx = { handled: handled, severity: severity, source: 'rails_error_reporter' }
49
+ ctx[:framework_context] = context || {}
50
+
51
+ env = context && (context[:env] || context['env'])
52
+ if env
53
+ req_info = ActiveRabbit::Reporting.rack_request_info(env)
54
+ ctx[:request] = req_info[:request]
55
+ ctx[:routing] = req_info[:routing]
56
+ # Top-level convenience for UI
57
+ ctx[:request_path] = ctx[:request][:path]
58
+ ctx[:request_method] = ctx[:request][:method]
59
+ end
60
+
61
+ if defined?(ActionController::RoutingError) && exception.is_a?(ActionController::RoutingError)
62
+ ctx[:controller_action] = 'Routing#not_found'
63
+ ctx[:error_type] = 'route_not_found'
64
+ ctx[:error_status] = 404
65
+ ctx[:error_component] = 'ActionDispatch'
66
+ ctx[:error_source] = 'Router'
67
+ ctx[:tags] = (ctx[:tags] || {}).merge(error_type: 'routing_error', severity: 'warning')
68
+ end
69
+
70
+ ctx
71
+ end
72
+ end
73
+
74
+ def self.attach!
75
+ # Rails 7.0+: Rails.error; earlier versions no-op
76
+ if defined?(Rails) && Rails.respond_to?(:error)
77
+ Rails.logger.info "[ActiveRabbit] Attaching to Rails error reporter" if defined?(Rails.logger)
78
+
79
+ subscriber = Subscriber.new
80
+ Rails.error.subscribe(subscriber)
81
+
82
+ Rails.logger.info "[ActiveRabbit] Rails error reporter attached successfully" if defined?(Rails.logger)
83
+ else
84
+ Rails.logger.info "[ActiveRabbit] Rails error reporter not available (Rails < 7.0)" if defined?(Rails.logger)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -102,6 +102,11 @@ module ActiveRabbit
102
102
  context[:request] = Thread.current[:active_rabbit_request_context]
103
103
  end
104
104
 
105
+ # Background job information (if available)
106
+ if defined?(Thread) && Thread.current[:active_rabbit_job_context]
107
+ context[:job] = Thread.current[:active_rabbit_job_context]
108
+ end
109
+
105
110
  context
106
111
  end
107
112
 
@@ -13,15 +13,16 @@ module ActiveRabbit
13
13
  @http_client = http_client
14
14
  end
15
15
 
16
- def track_exception(exception:, context: {}, user_id: nil, tags: {})
16
+ def track_exception(exception:, context: {}, user_id: nil, tags: {}, handled: nil, force: false)
17
17
  return unless exception
18
- return if should_ignore_exception?(exception)
18
+ return if !force && should_ignore_exception?(exception)
19
19
 
20
20
  exception_data = build_exception_data(
21
21
  exception: exception,
22
22
  context: context,
23
23
  user_id: user_id,
24
- tags: tags
24
+ tags: tags,
25
+ handled: handled
25
26
  )
26
27
 
27
28
  # Apply before_send callback if configured
@@ -30,7 +31,27 @@ module ActiveRabbit
30
31
  return unless exception_data # Callback can filter out exceptions by returning nil
31
32
  end
32
33
 
33
- http_client.post_exception(exception_data)
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}")
37
+
38
+ # Ensure we have required fields
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}")
42
+ return nil
43
+ end
44
+
45
+ response = http_client.post_exception(exception_data)
46
+
47
+ if response.nil?
48
+ configuration.logger&.error("[ActiveRabbit] Failed to send exception - both primary and fallback endpoints failed")
49
+ return nil
50
+ end
51
+
52
+ configuration.logger&.info("[ActiveRabbit] Exception successfully sent to API")
53
+ configuration.logger&.debug("[ActiveRabbit] API Response: #{response.inspect}")
54
+ response
34
55
  end
35
56
 
36
57
  def flush
@@ -39,33 +60,83 @@ module ActiveRabbit
39
60
 
40
61
  private
41
62
 
42
- def build_exception_data(exception:, context:, user_id:, tags:)
43
- backtrace = parse_backtrace(exception.backtrace || [])
63
+ def build_exception_data(exception:, context:, user_id:, tags:, handled: nil)
64
+ parsed_bt = parse_backtrace(exception.backtrace || [])
65
+ backtrace_lines = parsed_bt.map { |frame| frame[:line] }
66
+
67
+ # Fallback: synthesize a helpful frame for routing errors with no backtrace
68
+ if backtrace_lines.empty?
69
+ synthetic = nil
70
+ if context && (context[:routing]&.[](:path) || context[:request_path])
71
+ path = context[:routing]&.[](:path) || context[:request_path]
72
+ synthetic = "#{defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : 'app'}/config/routes.rb:1:in `route_not_found' for #{path}"
73
+ elsif exception && exception.message && exception.message =~ /No route matches \[(\w+)\] \"(.+?)\"/
74
+ path = $2
75
+ synthetic = "#{defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : 'app'}/config/routes.rb:1:in `route_not_found' for #{path}"
76
+ end
77
+ backtrace_lines = [synthetic] if synthetic
78
+ end
44
79
 
80
+ # Build data in the format the API expects
45
81
  data = {
46
- type: exception.class.name,
82
+ # Required fields
83
+ exception_class: exception.class.name,
47
84
  message: exception.message,
48
- backtrace: backtrace,
49
- fingerprint: generate_fingerprint(exception),
50
- timestamp: Time.now.iso8601(3),
51
- environment: configuration.environment,
52
- release: configuration.release,
85
+ backtrace: backtrace_lines,
86
+
87
+ # Timing and environment
88
+ occurred_at: Time.now.iso8601(3),
89
+ environment: configuration.environment || 'development',
90
+ release_version: configuration.release,
53
91
  server_name: configuration.server_name,
54
- context: scrub_pii(context || {}),
55
- tags: tags || {}
56
- }
57
92
 
58
- data[:user_id] = user_id if user_id
59
- data[:project_id] = configuration.project_id if configuration.project_id
93
+ # Context from the error
94
+ controller_action: context[:controller_action],
95
+ request_path: context[:request_path],
96
+ request_method: context[:request_method],
60
97
 
61
- # Add runtime context
62
- data[:runtime_context] = build_runtime_context
98
+ # Additional context
99
+ context: scrub_pii(context || {}),
100
+ tags: tags || {},
101
+ user_id: user_id,
102
+ project_id: configuration.project_id,
103
+
104
+ # Runtime info
105
+ runtime_context: build_runtime_context,
106
+
107
+ # Error details (for better UI display)
108
+ error_type: context[:error_type] || exception.class.name,
109
+ error_message: context[:error_message] || exception.message,
110
+ error_location: context[:error_location] || backtrace_lines.first,
111
+ error_severity: context[:error_severity] || :error,
112
+ error_status: context[:error_status] || 500,
113
+ error_source: context[:error_source] || 'Application',
114
+ error_component: context[:error_component] || 'Unknown',
115
+ error_action: context[:error_action],
116
+ handled: context.key?(:handled) ? context[:handled] : handled,
117
+
118
+ # Request details
119
+ request_details: context[:request_details],
120
+ response_time: context[:response_time],
121
+ routing_info: context[:routing_info]
122
+ }
63
123
 
64
124
  # Add request context if available
65
125
  if defined?(Thread) && Thread.current[:active_rabbit_request_context]
66
126
  data[:request_context] = Thread.current[:active_rabbit_request_context]
67
127
  end
68
128
 
129
+ # Add background job context if available
130
+ if defined?(Thread) && Thread.current[:active_rabbit_job_context]
131
+ data[:job_context] = Thread.current[:active_rabbit_job_context]
132
+ end
133
+
134
+ # 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]}")
139
+
69
140
  data
70
141
  end
71
142