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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 13adbf148b2adf2f831fdf34c73814362fc2e08dab9564183fb69f193c935a34
|
|
4
|
+
data.tar.gz: 77f6661494c46d98e72697fe6816de893e1f66fcf9a06f5bfffa7d9f61095da9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
@@ -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
|
|
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
|
|
42
|
-
return if
|
|
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
|
data/lib/rails_informant.rb
CHANGED
|
@@ -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.
|
|
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
|
-
...
|