rails_error_dashboard 0.8.0 → 0.8.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 +4 -4
- data/README.md +34 -1
- data/app/controllers/rails_error_dashboard/application_controller.rb +5 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +12 -0
- data/app/controllers/rails_error_dashboard/webhooks_controller.rb +35 -1
- data/app/jobs/rails_error_dashboard/storm_flush_job.rb +19 -0
- data/app/jobs/rails_error_dashboard/storm_notification_job.rb +74 -0
- data/app/models/rails_error_dashboard/storm_event.rb +34 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +21 -0
- data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +1 -0
- data/app/views/rails_error_dashboard/errors/storms.html.erb +91 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20260306000002_add_instance_variables_to_error_logs.rb +7 -1
- data/db/migrate/20260306000003_create_rails_error_dashboard_swallowed_exceptions.rb +4 -0
- data/db/migrate/20260307000001_create_rails_error_dashboard_diagnostic_dumps.rb +4 -0
- data/db/migrate/20260613000001_create_storm_events.rb +28 -0
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +49 -2
- data/lib/rails_error_dashboard/commands/create_issue.rb +1 -1
- data/lib/rails_error_dashboard/commands/flush_storm_counts.rb +188 -0
- data/lib/rails_error_dashboard/commands/link_existing_issue.rb +3 -1
- data/lib/rails_error_dashboard/commands/log_error.rb +70 -12
- data/lib/rails_error_dashboard/configuration.rb +73 -7
- data/lib/rails_error_dashboard/queries/storm_history.rb +39 -0
- data/lib/rails_error_dashboard/services/issue_tracker_client.rb +8 -5
- data/lib/rails_error_dashboard/services/linear_issue_client.rb +248 -0
- data/lib/rails_error_dashboard/services/storm_protection/circuit_breaker.rb +195 -0
- data/lib/rails_error_dashboard/services/storm_protection/count_buffer.rb +100 -0
- data/lib/rails_error_dashboard/services/storm_protection/fingerprint_buckets.rb +123 -0
- data/lib/rails_error_dashboard/services/storm_protection/gate.rb +258 -0
- data/lib/rails_error_dashboard/subscribers/issue_tracker_subscriber.rb +12 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +7 -0
- metadata +15 -3
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsErrorDashboard
|
|
4
|
+
module Services
|
|
5
|
+
module StormProtection
|
|
6
|
+
# Facade for the storm-protection hot path. One call per capture attempt:
|
|
7
|
+
#
|
|
8
|
+
# Gate.admit!(exception, context) # => :full | :lite | :count_only
|
|
9
|
+
#
|
|
10
|
+
# :full — capture everything (the normal path)
|
|
11
|
+
# :lite — capture the error + occurrence row, shed context
|
|
12
|
+
# (breadcrumbs / system health / locals / ivars)
|
|
13
|
+
# :count_only — nothing stored now; counted in memory, reconciled
|
|
14
|
+
# onto ErrorLog.occurrence_count by the flush job
|
|
15
|
+
#
|
|
16
|
+
# Safety contract (mirrors SwallowedExceptionTracker):
|
|
17
|
+
# - FAILS OPEN: any internal error → :full. Protection must never be
|
|
18
|
+
# the thing that loses an error.
|
|
19
|
+
# - Zero I/O on the hot path. The only DB-adjacent work is enqueueing
|
|
20
|
+
# the flush job at most once per flush interval.
|
|
21
|
+
# - Budget: digest + atomic increment + comparisons (~µs). Benchmarked.
|
|
22
|
+
# - Per-process state; Puma workers each run their own breaker. No
|
|
23
|
+
# thread-locals — shared atomics, so no Thread.current cleanup needed.
|
|
24
|
+
#
|
|
25
|
+
# IMPORTANT ordering: callers must run ExceptionFilter (ignore list +
|
|
26
|
+
# static sampling) BEFORE this gate — ignored exceptions must never
|
|
27
|
+
# count toward storm state or be reconciled into ErrorLogs.
|
|
28
|
+
class Gate
|
|
29
|
+
class << self
|
|
30
|
+
def admit!(exception, context = {})
|
|
31
|
+
return :full unless enabled?
|
|
32
|
+
|
|
33
|
+
state = breaker.record!
|
|
34
|
+
maybe_storm_notification(state)
|
|
35
|
+
|
|
36
|
+
decision = decide(state, exception, context)
|
|
37
|
+
maybe_flush!
|
|
38
|
+
decision
|
|
39
|
+
rescue => e
|
|
40
|
+
RailsErrorDashboard::Logger.error(
|
|
41
|
+
"[RailsErrorDashboard] StormProtection failed open: #{e.class} - #{e.message}"
|
|
42
|
+
)
|
|
43
|
+
:full
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# While the breaker is not closed, per-error notifications are
|
|
47
|
+
# suppressed (a single storm notification replaces them).
|
|
48
|
+
def notifications_suppressed?
|
|
49
|
+
enabled? && breaker.state != :closed
|
|
50
|
+
rescue
|
|
51
|
+
false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Always-on cap for auto-created issues (a storm of NEW critical
|
|
55
|
+
# fingerprints must not open 500 GitHub/Linear issues). Token
|
|
56
|
+
# bucket: N per rolling window, per process. Each call consumes a
|
|
57
|
+
# token — call only when actually about to create an issue.
|
|
58
|
+
def issue_creation_allowed?
|
|
59
|
+
return true unless enabled?
|
|
60
|
+
|
|
61
|
+
now = monotonic_now
|
|
62
|
+
window = RailsErrorDashboard.configuration.auto_issue_rate_limit_window_minutes.to_i * 60
|
|
63
|
+
limit = RailsErrorDashboard.configuration.auto_issue_rate_limit_count.to_i
|
|
64
|
+
|
|
65
|
+
@issue_window_start ||= now
|
|
66
|
+
@issue_window_count ||= Concurrent::AtomicFixnum.new(0)
|
|
67
|
+
|
|
68
|
+
if now - @issue_window_start >= window
|
|
69
|
+
@issue_window_start = now
|
|
70
|
+
@issue_window_count = Concurrent::AtomicFixnum.new(0)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@issue_window_count.increment <= limit
|
|
74
|
+
rescue
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def state
|
|
79
|
+
enabled? ? breaker.state : :closed
|
|
80
|
+
rescue
|
|
81
|
+
:closed
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Exposed for the flush job (episode metadata for storm_events).
|
|
85
|
+
def breaker
|
|
86
|
+
@breaker ||= CircuitBreaker.new
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def count_buffer
|
|
90
|
+
@count_buffer ||= CountBuffer.new
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def fingerprint_buckets
|
|
94
|
+
@fingerprint_buckets ||= FingerprintBuckets.new
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Test hook + fork hygiene: fresh state, no leftover episodes.
|
|
98
|
+
def reset!
|
|
99
|
+
@breaker = nil
|
|
100
|
+
@count_buffer = nil
|
|
101
|
+
@fingerprint_buckets = nil
|
|
102
|
+
@probe_counter = nil
|
|
103
|
+
@issue_window_start = nil
|
|
104
|
+
@issue_window_count = nil
|
|
105
|
+
@last_flush = nil
|
|
106
|
+
@storm_notified_episode = nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def enabled?
|
|
112
|
+
RailsErrorDashboard.configuration.enable_storm_protection
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# All identity computation happens here, once, in locals — no
|
|
116
|
+
# shared mutable caches (thread safety by construction).
|
|
117
|
+
def decide(state, exception, context)
|
|
118
|
+
case state
|
|
119
|
+
when :open
|
|
120
|
+
count!(exception, context)
|
|
121
|
+
:count_only
|
|
122
|
+
when :half_open
|
|
123
|
+
# Probe: a trickle of :lite captures tells us whether the storm
|
|
124
|
+
# has actually subsided; everything else stays counted.
|
|
125
|
+
if (probe_counter.increment % 10).zero?
|
|
126
|
+
:lite
|
|
127
|
+
else
|
|
128
|
+
count!(exception, context)
|
|
129
|
+
:count_only
|
|
130
|
+
end
|
|
131
|
+
when :shedding
|
|
132
|
+
# Never :full while shedding — context capture is request-thread
|
|
133
|
+
# CPU we can't afford. Buckets still apply their per-fingerprint
|
|
134
|
+
# row sampling underneath.
|
|
135
|
+
parts = gate_parts(exception, context)
|
|
136
|
+
if fingerprint_buckets.decide(gate_key(parts)) == :count_only
|
|
137
|
+
count_buffer.record(gate_key(parts), parts)
|
|
138
|
+
:count_only
|
|
139
|
+
else
|
|
140
|
+
:lite
|
|
141
|
+
end
|
|
142
|
+
else # :closed
|
|
143
|
+
parts = gate_parts(exception, context)
|
|
144
|
+
decision = fingerprint_buckets.decide(gate_key(parts))
|
|
145
|
+
count_buffer.record(gate_key(parts), parts) if decision == :count_only
|
|
146
|
+
decision
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def count!(exception, context)
|
|
151
|
+
parts = gate_parts(exception, context)
|
|
152
|
+
count_buffer.record(gate_key(parts), parts)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Cheap in-process bucketing key. Deliberately NOT the canonical
|
|
156
|
+
# error_hash (that needs application_id = DB); the flush job
|
|
157
|
+
# recomputes the canonical hash from the stored parts.
|
|
158
|
+
def gate_key(parts)
|
|
159
|
+
parts[:gate_key] ||= parts[:custom_hash] || Digest::SHA256.hexdigest(
|
|
160
|
+
"#{parts[:error_class]}|#{ErrorHashGenerator.normalize_message(parts[:message])}|" \
|
|
161
|
+
"#{parts[:first_app_frame]}|#{parts[:controller_name]}|#{parts[:action_name]}"
|
|
162
|
+
)[0..15]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def gate_parts(exception, context)
|
|
166
|
+
{
|
|
167
|
+
error_class: exception.class.name,
|
|
168
|
+
message: exception.message.to_s[0, 500],
|
|
169
|
+
first_app_frame: ErrorHashGenerator.extract_app_frame_from_locations(exception) ||
|
|
170
|
+
ErrorHashGenerator.extract_app_frame(exception.backtrace),
|
|
171
|
+
controller_name: context[:controller_name]&.to_s,
|
|
172
|
+
action_name: context[:action_name]&.to_s,
|
|
173
|
+
custom_hash: custom_hash_for(exception, context)
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# When a custom fingerprint lambda is configured the canonical hash
|
|
178
|
+
# doesn't include application_id, so the gate can compute it exactly
|
|
179
|
+
# — the flush job then reconciles by hash directly.
|
|
180
|
+
def custom_hash_for(exception, context)
|
|
181
|
+
return nil unless RailsErrorDashboard.configuration.custom_fingerprint
|
|
182
|
+
|
|
183
|
+
result = RailsErrorDashboard.configuration.custom_fingerprint.call(exception, context)
|
|
184
|
+
return nil unless result.is_a?(String) && !result.empty?
|
|
185
|
+
|
|
186
|
+
Digest::SHA256.hexdigest(result)[0..15]
|
|
187
|
+
rescue
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def probe_counter
|
|
192
|
+
@probe_counter ||= Concurrent::AtomicFixnum.new(0)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Piggyback flush (SwallowedExceptionTracker pattern): cheap
|
|
196
|
+
# timestamp check per admit; enqueue at most once per interval.
|
|
197
|
+
def maybe_flush!
|
|
198
|
+
now = monotonic_now
|
|
199
|
+
interval = RailsErrorDashboard.configuration.storm_flush_interval_seconds.to_i
|
|
200
|
+
return if now - (@last_flush ||= now) < interval
|
|
201
|
+
return unless count_buffer.any? || breaker.episode_snapshot
|
|
202
|
+
|
|
203
|
+
@last_flush = now
|
|
204
|
+
snapshot = count_buffer.snapshot!
|
|
205
|
+
episode = breaker.episode_snapshot
|
|
206
|
+
breaker.clear_closed_episode!
|
|
207
|
+
|
|
208
|
+
StormFlushJob.perform_later(
|
|
209
|
+
entries: snapshot[:entries],
|
|
210
|
+
overflow: snapshot[:overflow],
|
|
211
|
+
episode: serialize_episode(episode)
|
|
212
|
+
)
|
|
213
|
+
rescue => e
|
|
214
|
+
RailsErrorDashboard::Logger.error(
|
|
215
|
+
"[RailsErrorDashboard] Storm flush enqueue failed: #{e.class} - #{e.message}"
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def serialize_episode(episode)
|
|
220
|
+
return nil unless episode
|
|
221
|
+
|
|
222
|
+
{
|
|
223
|
+
"started_at" => episode[:started_at]&.iso8601,
|
|
224
|
+
"ended_at" => episode[:ended_at]&.iso8601,
|
|
225
|
+
"peak_rate_per_minute" => episode[:peak_rate_per_minute],
|
|
226
|
+
"reached_open" => episode[:reached_open]
|
|
227
|
+
}
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# One notification per storm episode, on the first transition out
|
|
231
|
+
# of :closed.
|
|
232
|
+
def maybe_storm_notification(state)
|
|
233
|
+
return if state == :closed
|
|
234
|
+
return unless RailsErrorDashboard.configuration.storm_notification
|
|
235
|
+
|
|
236
|
+
episode = breaker.episode_snapshot
|
|
237
|
+
return unless episode
|
|
238
|
+
return if @storm_notified_episode == episode[:started_at]
|
|
239
|
+
|
|
240
|
+
@storm_notified_episode = episode[:started_at]
|
|
241
|
+
StormNotificationJob.perform_later(
|
|
242
|
+
started_at: episode[:started_at].iso8601,
|
|
243
|
+
state: state.to_s
|
|
244
|
+
)
|
|
245
|
+
rescue => e
|
|
246
|
+
RailsErrorDashboard::Logger.error(
|
|
247
|
+
"[RailsErrorDashboard] Storm notification enqueue failed: #{e.class} - #{e.message}"
|
|
248
|
+
)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def monotonic_now
|
|
252
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
@@ -13,6 +13,18 @@ module RailsErrorDashboard
|
|
|
13
13
|
# Called when a new error is first logged
|
|
14
14
|
def on_error_logged(error_log)
|
|
15
15
|
return unless should_auto_create?(error_log)
|
|
16
|
+
|
|
17
|
+
# Always-on rate cap (default 5 per 10 min): a storm of NEW distinct
|
|
18
|
+
# fingerprints must not open hundreds of issues. Checked LAST so a
|
|
19
|
+
# token is only consumed when we were actually about to create.
|
|
20
|
+
unless Services::StormProtection::Gate.issue_creation_allowed?
|
|
21
|
+
RailsErrorDashboard::Logger.warn(
|
|
22
|
+
"[RailsErrorDashboard] Auto-issue creation rate limit reached — " \
|
|
23
|
+
"skipping issue for ErrorLog ##{error_log.id} (#{error_log.error_type})"
|
|
24
|
+
)
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
|
|
16
28
|
dashboard_url = Services::NotificationHelpers.dashboard_url(error_log)
|
|
17
29
|
CreateIssueJob.perform_later(error_log.id, dashboard_url: dashboard_url)
|
|
18
30
|
rescue => e
|
|
@@ -68,6 +68,11 @@ require "rails_error_dashboard/services/issue_tracker_client"
|
|
|
68
68
|
require "rails_error_dashboard/services/github_issue_client"
|
|
69
69
|
require "rails_error_dashboard/services/gitlab_issue_client"
|
|
70
70
|
require "rails_error_dashboard/services/codeberg_issue_client"
|
|
71
|
+
require "rails_error_dashboard/services/linear_issue_client"
|
|
72
|
+
require "rails_error_dashboard/services/storm_protection/circuit_breaker"
|
|
73
|
+
require "rails_error_dashboard/services/storm_protection/fingerprint_buckets"
|
|
74
|
+
require "rails_error_dashboard/services/storm_protection/count_buffer"
|
|
75
|
+
require "rails_error_dashboard/services/storm_protection/gate"
|
|
71
76
|
require "rails_error_dashboard/services/database_health_inspector"
|
|
72
77
|
require "rails_error_dashboard/services/cache_analyzer"
|
|
73
78
|
require "rails_error_dashboard/services/llm_summary"
|
|
@@ -79,6 +84,7 @@ require "rails_error_dashboard/services/diagnostic_dump_generator"
|
|
|
79
84
|
require "rails_error_dashboard/services/coverage_tracker"
|
|
80
85
|
require "rails_error_dashboard/services/digest_builder"
|
|
81
86
|
require "rails_error_dashboard/queries/user_impact_summary"
|
|
87
|
+
require "rails_error_dashboard/queries/storm_history"
|
|
82
88
|
require "rails_error_dashboard/subscribers/breadcrumb_subscriber"
|
|
83
89
|
require "rails_error_dashboard/subscribers/rack_attack_subscriber"
|
|
84
90
|
require "rails_error_dashboard/subscribers/action_cable_subscriber"
|
|
@@ -96,6 +102,7 @@ require "rails_error_dashboard/commands/log_error"
|
|
|
96
102
|
require "rails_error_dashboard/commands/resolve_error"
|
|
97
103
|
require "rails_error_dashboard/commands/create_issue"
|
|
98
104
|
require "rails_error_dashboard/commands/link_existing_issue"
|
|
105
|
+
require "rails_error_dashboard/commands/flush_storm_counts"
|
|
99
106
|
require "rails_error_dashboard/services/issue_body_formatter"
|
|
100
107
|
require "rails_error_dashboard/commands/batch_resolve_errors"
|
|
101
108
|
require "rails_error_dashboard/commands/batch_delete_errors"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_error_dashboard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -262,6 +262,8 @@ files:
|
|
|
262
262
|
- app/jobs/rails_error_dashboard/retention_cleanup_job.rb
|
|
263
263
|
- app/jobs/rails_error_dashboard/scheduled_digest_job.rb
|
|
264
264
|
- app/jobs/rails_error_dashboard/slack_error_notification_job.rb
|
|
265
|
+
- app/jobs/rails_error_dashboard/storm_flush_job.rb
|
|
266
|
+
- app/jobs/rails_error_dashboard/storm_notification_job.rb
|
|
265
267
|
- app/jobs/rails_error_dashboard/swallowed_exception_flush_job.rb
|
|
266
268
|
- app/jobs/rails_error_dashboard/webhook_error_notification_job.rb
|
|
267
269
|
- app/mailers/rails_error_dashboard/application_mailer.rb
|
|
@@ -275,6 +277,7 @@ files:
|
|
|
275
277
|
- app/models/rails_error_dashboard/error_log.rb
|
|
276
278
|
- app/models/rails_error_dashboard/error_logs_record.rb
|
|
277
279
|
- app/models/rails_error_dashboard/error_occurrence.rb
|
|
280
|
+
- app/models/rails_error_dashboard/storm_event.rb
|
|
278
281
|
- app/models/rails_error_dashboard/swallowed_exception.rb
|
|
279
282
|
- app/views/layouts/rails_error_dashboard.html.erb
|
|
280
283
|
- app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb
|
|
@@ -321,6 +324,7 @@ files:
|
|
|
321
324
|
- app/views/rails_error_dashboard/errors/settings.html.erb
|
|
322
325
|
- app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb
|
|
323
326
|
- app/views/rails_error_dashboard/errors/show.html.erb
|
|
327
|
+
- app/views/rails_error_dashboard/errors/storms.html.erb
|
|
324
328
|
- app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb
|
|
325
329
|
- app/views/rails_error_dashboard/errors/user_impact.html.erb
|
|
326
330
|
- config/routes.rb
|
|
@@ -359,6 +363,7 @@ files:
|
|
|
359
363
|
- db/migrate/20260325000001_fix_swallowed_exceptions_index_for_mysql.rb
|
|
360
364
|
- db/migrate/20260326000001_add_issue_tracking_to_error_logs.rb
|
|
361
365
|
- db/migrate/20260503000001_backfill_resolved_status.rb
|
|
366
|
+
- db/migrate/20260613000001_create_storm_events.rb
|
|
362
367
|
- lib/generators/rails_error_dashboard/install/install_generator.rb
|
|
363
368
|
- lib/generators/rails_error_dashboard/install/templates/README
|
|
364
369
|
- lib/generators/rails_error_dashboard/install/templates/initializer.rb
|
|
@@ -376,6 +381,7 @@ files:
|
|
|
376
381
|
- lib/rails_error_dashboard/commands/create_issue.rb
|
|
377
382
|
- lib/rails_error_dashboard/commands/find_or_create_application.rb
|
|
378
383
|
- lib/rails_error_dashboard/commands/find_or_increment_error.rb
|
|
384
|
+
- lib/rails_error_dashboard/commands/flush_storm_counts.rb
|
|
379
385
|
- lib/rails_error_dashboard/commands/flush_swallowed_exceptions.rb
|
|
380
386
|
- lib/rails_error_dashboard/commands/increment_cascade_detection.rb
|
|
381
387
|
- lib/rails_error_dashboard/commands/link_existing_issue.rb
|
|
@@ -431,6 +437,7 @@ files:
|
|
|
431
437
|
- lib/rails_error_dashboard/queries/recurring_issues.rb
|
|
432
438
|
- lib/rails_error_dashboard/queries/release_timeline.rb
|
|
433
439
|
- lib/rails_error_dashboard/queries/similar_errors.rb
|
|
440
|
+
- lib/rails_error_dashboard/queries/storm_history.rb
|
|
434
441
|
- lib/rails_error_dashboard/queries/swallowed_exception_summary.rb
|
|
435
442
|
- lib/rails_error_dashboard/queries/user_impact_summary.rb
|
|
436
443
|
- lib/rails_error_dashboard/services/analytics_cache_manager.rb
|
|
@@ -463,6 +470,7 @@ files:
|
|
|
463
470
|
- lib/rails_error_dashboard/services/gitlab_issue_client.rb
|
|
464
471
|
- lib/rails_error_dashboard/services/issue_body_formatter.rb
|
|
465
472
|
- lib/rails_error_dashboard/services/issue_tracker_client.rb
|
|
473
|
+
- lib/rails_error_dashboard/services/linear_issue_client.rb
|
|
466
474
|
- lib/rails_error_dashboard/services/llm_client.rb
|
|
467
475
|
- lib/rails_error_dashboard/services/llm_cost_estimator.rb
|
|
468
476
|
- lib/rails_error_dashboard/services/llm_summary.rb
|
|
@@ -483,6 +491,10 @@ files:
|
|
|
483
491
|
- lib/rails_error_dashboard/services/slack_payload_builder.rb
|
|
484
492
|
- lib/rails_error_dashboard/services/source_code_reader.rb
|
|
485
493
|
- lib/rails_error_dashboard/services/statistical_classifier.rb
|
|
494
|
+
- lib/rails_error_dashboard/services/storm_protection/circuit_breaker.rb
|
|
495
|
+
- lib/rails_error_dashboard/services/storm_protection/count_buffer.rb
|
|
496
|
+
- lib/rails_error_dashboard/services/storm_protection/fingerprint_buckets.rb
|
|
497
|
+
- lib/rails_error_dashboard/services/storm_protection/gate.rb
|
|
486
498
|
- lib/rails_error_dashboard/services/swallowed_exception_tracker.rb
|
|
487
499
|
- lib/rails_error_dashboard/services/system_health_snapshot.rb
|
|
488
500
|
- lib/rails_error_dashboard/services/variable_serializer.rb
|
|
@@ -511,7 +523,7 @@ metadata:
|
|
|
511
523
|
funding_uri: https://github.com/sponsors/AnjanJ
|
|
512
524
|
post_install_message: |
|
|
513
525
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
514
|
-
RED (Rails Error Dashboard) v0.8.
|
|
526
|
+
RED (Rails Error Dashboard) v0.8.2
|
|
515
527
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
516
528
|
|
|
517
529
|
First install:
|
|
@@ -547,7 +559,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
547
559
|
- !ruby/object:Gem::Version
|
|
548
560
|
version: '0'
|
|
549
561
|
requirements: []
|
|
550
|
-
rubygems_version:
|
|
562
|
+
rubygems_version: 3.6.9
|
|
551
563
|
specification_version: 4
|
|
552
564
|
summary: Self-hosted error tracking and exception monitoring for Rails. Free, forever.
|
|
553
565
|
test_files: []
|