rails-informant 0.2.1 → 0.2.2

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: a3921be9c6e9f434524b03c140e94ca5de03128bbd9b27c9a9df67477ed79aa7
4
- data.tar.gz: 90feef10dc4d4ada45acaefa06b65ea3cdc5543ba86beb5952f78b3167ec7948
3
+ metadata.gz: 13adbf148b2adf2f831fdf34c73814362fc2e08dab9564183fb69f193c935a34
4
+ data.tar.gz: 77f6661494c46d98e72697fe6816de893e1f66fcf9a06f5bfffa7d9f61095da9
5
5
  SHA512:
6
- metadata.gz: caef8697007fb7a2c678f925db8366ebb6dd7d493f489cc87e7a099f57b8852238a5ca7949d133a11120be6dd6a9eda3535d4d62f60bf31a7ea81187d9b004f7
7
- data.tar.gz: bc56bb6ca81812f771196081a70fade906ca1c4a8c4f047f6d63c326c87f67e941bbd05c0cc5c355898726ea6d6591b86fdbed72293bccfbb20dfffca6102c5b
6
+ metadata.gz: c2dbb4db63a9749d40d0b1c8cd46020e400912f409810d266b483c7916384083cdf1ac51a43732fdf5675fe19b09e02b13ec7c31cd49a832a4eeaf4671ff8464
7
+ data.tar.gz: a359ac2d32fa026e29d9c53e17551af84cd13b7e4702b8141612ac0b92a0cab41f555ea54419849244f4b62c62d2bae49c6fb198a04bb6471afef7021d50fed9
@@ -3,9 +3,16 @@ module RailsInformant
3
3
  queue_as :default
4
4
 
5
5
  retry_on ::Net::OpenTimeout, ::Net::ReadTimeout, ::SocketError, RailsInformant::NotifierError, attempts: 5, wait: 15.seconds
6
- discard_on ActiveRecord::RecordNotFound
6
+ discard_on ActiveRecord::RecordNotFound,
7
+ ArgumentError,
8
+ Errno::ECONNREFUSED,
9
+ Errno::ECONNRESET,
10
+ Errno::EHOSTUNREACH,
11
+ OpenSSL::SSL::SSLError
7
12
 
8
13
  def perform(group)
14
+ Current.delivering_notification = true
15
+
9
16
  occurrence = group.occurrences.order(created_at: :desc).first
10
17
  failures = []
11
18
 
@@ -17,12 +24,16 @@ module RailsInformant
17
24
  failures << e
18
25
  end
19
26
 
20
- group.update_column(:last_notified_at, Time.current) if failures.empty?
21
-
22
- if failures.any?
27
+ if failures.empty?
28
+ group.update_column(:last_notified_at, Time.current)
29
+ Notifiers::CircuitBreaker.record_success
30
+ else
31
+ Notifiers::CircuitBreaker.record_failure
23
32
  failures.drop(1).each { |e| Rails.logger.error "[RailsInformant] Notifier failed: #{e.class}: #{e.message}" }
24
33
  raise failures.first
25
34
  end
35
+ ensure
36
+ Current.delivering_notification = false
26
37
  end
27
38
 
28
39
  private
@@ -1,5 +1,5 @@
1
1
  module RailsInformant
2
2
  class Current < ActiveSupport::CurrentAttributes
3
- attribute :breadcrumbs, :user_context, :custom_context
3
+ attribute :breadcrumbs, :custom_context, :delivering_notification, :user_context
4
4
  end
5
5
  end
@@ -6,18 +6,27 @@ module RailsInformant
6
6
  class << self
7
7
  def record(error, severity: "error", context: {}, source: nil, env: nil)
8
8
  return unless RailsInformant.initialized?
9
+ return if self_caused_error?(error)
10
+
9
11
  now = Time.current
10
12
  attrs = ContextBuilder.group_attributes(error, severity:, context:, env:, now:)
11
13
  group = ErrorGroup.find_or_create_for(Fingerprint.generate(error), attrs)
12
14
  group.detect_regression!
13
15
  store_occurrence(group, error, env:, context:) if should_store_occurrence?(group)
14
- notify(group, error)
16
+ notify(group)
15
17
  rescue StandardError => e
16
18
  Rails.logger.error "[RailsInformant] Capture failed: #{e.class}: #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}"
17
19
  end
18
20
 
19
21
  private
20
22
 
23
+ # Detect errors caused by RailsInformant itself to prevent feedback loops.
24
+ # Primary: CurrentAttributes flag set during notification delivery.
25
+ # Fallback: backtrace heuristic for cross-execution scenarios (e.g. queue retries).
26
+ def self_caused_error?(error)
27
+ Current.delivering_notification || error.backtrace&.any? { it.include?("rails_informant/notifiers") }
28
+ end
29
+
21
30
  def should_store_occurrence?(group)
22
31
  return true if group.total_occurrences <= 1
23
32
  return true unless group.last_occurrence_stored_at
@@ -38,15 +47,11 @@ module RailsInformant
38
47
  keep_ids = group.occurrences.order(created_at: :desc).limit(MAX_OCCURRENCES_PER_GROUP).select(:id)
39
48
  Occurrence.where(error_group_id: group.id).where.not(id: keep_ids).delete_all
40
49
  end
41
- def notify(group, error)
42
- return if notifier_error?(error)
50
+ def notify(group)
51
+ return if Notifiers::CircuitBreaker.open?
43
52
  return unless RailsInformant.config.notifiers.any? { it.should_notify?(group) }
44
53
  RailsInformant::NotifyJob.perform_later group
45
54
  end
46
-
47
- def notifier_error?(error)
48
- error.backtrace&.any? { it.include?("rails_informant/notifiers") }
49
- end
50
55
  end
51
56
  end
52
57
  end
@@ -0,0 +1,40 @@
1
+ module RailsInformant
2
+ module Notifiers
3
+ class CircuitBreaker
4
+ FAILURE_THRESHOLD = 5
5
+ RESET_TIMEOUT = 10.minutes
6
+
7
+ class << self
8
+ def open?
9
+ return false if failure_count < FAILURE_THRESHOLD
10
+
11
+ last_failure_at > RESET_TIMEOUT.ago
12
+ end
13
+
14
+ def record_failure
15
+ @failure_count = failure_count + 1
16
+ @last_failure_at = Time.current
17
+ end
18
+
19
+ def record_success
20
+ reset!
21
+ end
22
+
23
+ def reset!
24
+ @failure_count = 0
25
+ @last_failure_at = nil
26
+ end
27
+
28
+ private
29
+
30
+ def failure_count
31
+ @failure_count || 0
32
+ end
33
+
34
+ def last_failure_at
35
+ @last_failure_at || Time.at(0)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -47,6 +47,7 @@ module RailsInformant
47
47
  end
48
48
 
49
49
  module Notifiers
50
+ autoload :CircuitBreaker, "rails_informant/notifiers/circuit_breaker"
50
51
  autoload :NotificationPolicy, "rails_informant/notifiers/notification_policy"
51
52
  autoload :Slack, "rails_informant/notifiers/slack"
52
53
  autoload :Webhook, "rails_informant/notifiers/webhook"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-informant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel López Prat
@@ -162,6 +162,7 @@ files:
162
162
  - lib/rails_informant/mcp/tools/verify_pending_fixes.rb
163
163
  - lib/rails_informant/middleware/error_capture.rb
164
164
  - lib/rails_informant/middleware/rescued_exception_interceptor.rb
165
+ - lib/rails_informant/notifiers/circuit_breaker.rb
165
166
  - lib/rails_informant/notifiers/notification_policy.rb
166
167
  - lib/rails_informant/notifiers/slack.rb
167
168
  - lib/rails_informant/notifiers/webhook.rb
@@ -176,16 +177,6 @@ metadata:
176
177
  changelog_uri: https://github.com/6temes/rails-informant/releases
177
178
  rubygems_mfa_required: 'true'
178
179
  source_code_uri: https://github.com/6temes/rails-informant
179
- post_install_message: |2+
180
-
181
- Rails Informant: SKILL.md has been simplified — workflow guidance now
182
- auto-updates via MCP server instructions. Regenerate your skill file:
183
-
184
- bin/rails generate rails_informant:skill
185
-
186
- This is a one-time migration. Future gem updates will not require
187
- regenerating SKILL.md.
188
-
189
180
  rdoc_options: []
190
181
  require_paths:
191
182
  - lib
@@ -204,4 +195,3 @@ rubygems_version: 4.0.3
204
195
  specification_version: 4
205
196
  summary: Self-hosted error monitoring for Rails with MCP server for agentic workflows
206
197
  test_files: []
207
- ...