rails_error_dashboard 0.4.0 → 0.4.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 +106 -8
- data/app/controllers/rails_error_dashboard/errors_controller.rb +15 -0
- data/app/models/rails_error_dashboard/error_log.rb +16 -0
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +3 -0
- data/app/views/rails_error_dashboard/errors/_modals.html.erb +35 -0
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +77 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +7 -0
- data/app/views/rails_error_dashboard/errors/show.html.erb +6 -1
- data/config/routes.rb +2 -0
- data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +9 -0
- data/db/migrate/20260323000001_add_muted_to_error_logs.rb +14 -0
- data/lib/rails_error_dashboard/commands/batch_mute_errors.rb +55 -0
- data/lib/rails_error_dashboard/commands/batch_unmute_errors.rb +55 -0
- data/lib/rails_error_dashboard/commands/log_error.rb +17 -17
- data/lib/rails_error_dashboard/commands/mute_error.rb +40 -0
- data/lib/rails_error_dashboard/commands/unmute_error.rb +30 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +11 -0
- data/lib/rails_error_dashboard/services/system_health_snapshot.rb +33 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +4 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e514a83af22b8b3938befb45429a46617ee0f547a6831d98cdb44e435f3e3a1c
|
|
4
|
+
data.tar.gz: 8329f6c148cfdd9c2d5fb9cd87e89e77f93c7a45beb55ec278218bb5f12e2dde
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8bfe23263da657c250f8c982ec9271e1ad6158f28041db817a3fc9ee529a0dbdadc8045f30f7633a771265ae0592cc2e0903af215b63f447f449ed0dc478039c
|
|
7
|
+
data.tar.gz: 889dac2f878d6d31afa91c4efbd20be4432452046bcb5a1071f507e23b41d7b5bcdb07ae67468bf3f678917084dc5149492fa01733276918fc52c4daf6f0a766
|
data/README.md
CHANGED
|
@@ -22,7 +22,7 @@ gem 'rails_error_dashboard'
|
|
|
22
22
|
|
|
23
23
|
**[rails-error-dashboard.anjan.dev](https://rails-error-dashboard.anjan.dev)** — Username: `gandalf` · Password: `youshallnotpass`
|
|
24
24
|
|
|
25
|
-
> **Beta Software** — Functional and tested (2,
|
|
25
|
+
> **Beta Software** — Functional and tested (2,600+ tests passing), but the API may change before v1.0. Supports Rails 7.0-8.1 and Ruby 3.2-4.0.
|
|
26
26
|
|
|
27
27
|
### Screenshots
|
|
28
28
|
|
|
@@ -53,6 +53,8 @@ gem 'rails_error_dashboard'
|
|
|
53
53
|
| SaaS pricing tiers and usage limits | Unlimited errors, unlimited projects |
|
|
54
54
|
| Vendor lock-in with proprietary APIs | 100% open source, fully portable |
|
|
55
55
|
| Complex SDK setup and external services | 5-minute Rails Engine installation |
|
|
56
|
+
| Pay extra for local variable capture (Sentry) | Local + instance variables included free |
|
|
57
|
+
| No tool detects silently rescued exceptions | Swallowed exception detection built in |
|
|
56
58
|
|
|
57
59
|
---
|
|
58
60
|
|
|
@@ -60,7 +62,7 @@ gem 'rails_error_dashboard'
|
|
|
60
62
|
|
|
61
63
|
### Core (Always Enabled)
|
|
62
64
|
|
|
63
|
-
Error capture from controllers, jobs, and middleware. Beautiful Bootstrap 5 dashboard with dark/light mode, search, filtering, and real-time updates. Analytics with trend charts, severity breakdown, and spike detection. Workflow management with assignment, priority, snooze, comments, and batch operations. Security via HTTP Basic Auth or custom lambda (Devise, Warden, session-based). Exception cause chains, enriched HTTP context, custom fingerprinting, CurrentAttributes integration, auto-reopen on recurrence, and sensitive data filtering — all built in.
|
|
65
|
+
Error capture from controllers, jobs, and middleware. Beautiful Bootstrap 5 dashboard with dark/light mode, search, filtering, and real-time updates. Analytics with trend charts, severity breakdown, and spike detection. Workflow management with assignment, priority, snooze, mute/unmute (notification suppression), comments, and batch operations. Security via HTTP Basic Auth or custom lambda (Devise, Warden, session-based). Exception cause chains, enriched HTTP context, custom fingerprinting, CurrentAttributes integration, auto-reopen on recurrence, and sensitive data filtering — all built in.
|
|
64
66
|
|
|
65
67
|
### Optional Features
|
|
66
68
|
|
|
@@ -85,10 +87,12 @@ config.enable_breadcrumbs = true
|
|
|
85
87
|
<details>
|
|
86
88
|
<summary><strong>System Health Snapshot</strong></summary>
|
|
87
89
|
|
|
88
|
-
Know your app's runtime state at the moment of failure — GC stats, process memory, thread count, connection pool utilization,
|
|
90
|
+
Know your app's runtime state at the moment of failure — GC stats, process memory, thread count, connection pool utilization, Puma thread stats, RubyVM cache health, and YJIT compilation stats captured automatically.
|
|
89
91
|
|
|
90
92
|
- Sub-millisecond total snapshot, every metric individually rescue-wrapped
|
|
91
93
|
- No ObjectSpace scanning, no Thread backtraces, no subprocess calls
|
|
94
|
+
- RubyVM.stat: constant cache invalidations, shape cache stats
|
|
95
|
+
- YJIT runtime stats: compiled iseqs, invalidation count, code region sizes
|
|
92
96
|
|
|
93
97
|
```ruby
|
|
94
98
|
config.enable_system_health = true
|
|
@@ -181,6 +185,98 @@ Seven analysis engines built in:
|
|
|
181
185
|
[Complete documentation →](docs/FEATURES.md#advanced-analytics-features)
|
|
182
186
|
</details>
|
|
183
187
|
|
|
188
|
+
<details>
|
|
189
|
+
<summary><strong>Local Variable + Instance Variable Capture</strong></summary>
|
|
190
|
+
|
|
191
|
+
See the exact values of local variables and instance variables at the moment an exception was raised — the most valuable debugging context possible.
|
|
192
|
+
|
|
193
|
+
- TracePoint(`:raise`) captures locals and ivars before the stack unwinds
|
|
194
|
+
- Configurable limits: max variable count, nesting depth, string truncation length
|
|
195
|
+
- Sensitive data auto-filtered via Rails `filter_parameters` — passwords, tokens, and PII never stored
|
|
196
|
+
- Never stores Binding objects — values extracted immediately, Binding is GC'd
|
|
197
|
+
- Independent config flags: enable one or both
|
|
198
|
+
|
|
199
|
+

|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
config.enable_local_variables = true
|
|
203
|
+
config.enable_instance_variables = true
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
[Complete documentation →](docs/FEATURES.md)
|
|
207
|
+
</details>
|
|
208
|
+
|
|
209
|
+
<details>
|
|
210
|
+
<summary><strong>Swallowed Exception Detection</strong></summary>
|
|
211
|
+
|
|
212
|
+
Detect exceptions that are raised but silently rescued — the hardest bugs to find. No other error tracker does this.
|
|
213
|
+
|
|
214
|
+
- Uses TracePoint(`:raise`) + TracePoint(`:rescue`) to track exception lifecycle
|
|
215
|
+
- Identifies code paths where exceptions are caught but never logged or re-raised
|
|
216
|
+
- Dashboard page at `/errors/swallowed_exceptions` shows detection counts, locations, and patterns
|
|
217
|
+
- Memory-bounded aggregation with background flush
|
|
218
|
+
- Requires Ruby 3.3+
|
|
219
|
+
|
|
220
|
+

|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
config.detect_swallowed_exceptions = true
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
[Complete documentation →](docs/FEATURES.md)
|
|
227
|
+
</details>
|
|
228
|
+
|
|
229
|
+
<details>
|
|
230
|
+
<summary><strong>On-Demand Diagnostic Dump</strong></summary>
|
|
231
|
+
|
|
232
|
+
Snapshot your app's entire system state on demand — environment, GC stats, threads, connection pool, memory, job queue health, and more.
|
|
233
|
+
|
|
234
|
+
- Trigger via dashboard button or `rake rails_error_dashboard:diagnostic_dump`
|
|
235
|
+
- Dashboard page at `/errors/diagnostic_dumps` with full history
|
|
236
|
+
- Useful for debugging intermittent production issues without reproducing them
|
|
237
|
+
|
|
238
|
+

|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
config.enable_diagnostic_dump = true
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
[Complete documentation →](docs/FEATURES.md)
|
|
245
|
+
</details>
|
|
246
|
+
|
|
247
|
+
<details>
|
|
248
|
+
<summary><strong>Rack Attack Event Tracking</strong></summary>
|
|
249
|
+
|
|
250
|
+
Track Rack Attack security events (throttles, blocklists, tracks) as breadcrumbs attached to errors, with a dedicated summary page.
|
|
251
|
+
|
|
252
|
+
- Captures throttle, blocklist, and track events automatically
|
|
253
|
+
- Dashboard page at `/errors/rack_attack_summary` with event breakdown
|
|
254
|
+
- Requires breadcrumbs to be enabled
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
config.enable_rack_attack_tracking = true
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
[Complete documentation →](docs/FEATURES.md)
|
|
261
|
+
</details>
|
|
262
|
+
|
|
263
|
+
<details>
|
|
264
|
+
<summary><strong>Process Crash Capture</strong></summary>
|
|
265
|
+
|
|
266
|
+
Capture unhandled exceptions that crash the Ruby process via an `at_exit` hook — the last line of defense.
|
|
267
|
+
|
|
268
|
+
- Disk-based fallback: writes crash data to disk because the database may be unavailable during shutdown
|
|
269
|
+
- Imported automatically on next boot
|
|
270
|
+
- Captures exception details, backtrace, uptime, GC stats, thread count, and cause chain
|
|
271
|
+
- A self-hosted only feature — impossible for SaaS tools
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
config.enable_crash_capture = true
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
[Complete documentation →](docs/FEATURES.md)
|
|
278
|
+
</details>
|
|
279
|
+
|
|
184
280
|
<details>
|
|
185
281
|
<summary><strong>Plugin System</strong></summary>
|
|
186
282
|
|
|
@@ -304,7 +400,7 @@ Built with **CQRS (Command/Query Responsibility Segregation)**:
|
|
|
304
400
|
|
|
305
401
|
## Testing
|
|
306
402
|
|
|
307
|
-
2,
|
|
403
|
+
2,600+ tests covering unit, integration, and browser-based system tests.
|
|
308
404
|
|
|
309
405
|
```bash
|
|
310
406
|
bundle exec rspec # Full suite
|
|
@@ -338,22 +434,24 @@ Available as open source under the [MIT License](https://opensource.org/licenses
|
|
|
338
434
|
|
|
339
435
|
## Acknowledgments
|
|
340
436
|
|
|
341
|
-
Built with [Rails](https://rubyonrails.org/) · UI by [Bootstrap 5](https://getbootstrap.com/) · Charts by [Chart.js](https://www.chartjs.org/) · Pagination by [Pagy](https://github.com/ddnexus/pagy)
|
|
437
|
+
Built with [Rails](https://rubyonrails.org/) · UI by [Bootstrap 5](https://getbootstrap.com/) · Charts by [Chart.js](https://www.chartjs.org/) · Pagination by [Pagy](https://github.com/ddnexus/pagy) · Docs theme by [Jekyll VitePress Theme](https://jekyll-vitepress.dev/) by [@crmne](https://github.com/crmne)
|
|
342
438
|
|
|
343
439
|
## Contributors
|
|
344
440
|
|
|
345
441
|
[](https://github.com/AnjanJ/rails_error_dashboard/graphs/contributors)
|
|
346
442
|
|
|
347
|
-
Special thanks to [@bonniesimon](https://github.com/bonniesimon), [@gundestrup](https://github.com/gundestrup),
|
|
443
|
+
Special thanks to [@bonniesimon](https://github.com/bonniesimon), [@gundestrup](https://github.com/gundestrup), [@midwire](https://github.com/midwire), [@RafaelTurtle](https://github.com/RafaelTurtle), and [@j4rs](https://github.com/j4rs). See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the full list.
|
|
348
444
|
|
|
349
445
|
---
|
|
350
446
|
|
|
351
447
|
## Support
|
|
352
448
|
|
|
353
|
-
If this gem saves you some headaches (or some money on error tracking SaaS), consider
|
|
449
|
+
If this gem saves you some headaches (or some money on error tracking SaaS), consider buying me a coffee. It keeps the project going and lets me know people are finding it useful.
|
|
450
|
+
|
|
451
|
+
<a href="https://www.buymeacoffee.com/anjanj" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="200"></a>
|
|
354
452
|
|
|
355
453
|
---
|
|
356
454
|
|
|
357
|
-
**Made with
|
|
455
|
+
**Made with ❤️ by [Anjan](https://anjan.dev)**
|
|
358
456
|
|
|
359
457
|
*One Gem to rule them all, One Gem to find them, One Gem to bring them all, and in the dashboard bind them.*
|
|
@@ -19,6 +19,7 @@ module RailsErrorDashboard
|
|
|
19
19
|
assignee_name
|
|
20
20
|
priority_level
|
|
21
21
|
hide_snoozed
|
|
22
|
+
hide_muted
|
|
22
23
|
reopened
|
|
23
24
|
sort_by
|
|
24
25
|
sort_direction
|
|
@@ -121,6 +122,16 @@ module RailsErrorDashboard
|
|
|
121
122
|
redirect_to error_path(@error)
|
|
122
123
|
end
|
|
123
124
|
|
|
125
|
+
def mute
|
|
126
|
+
@error = Commands::MuteError.call(params[:id], muted_by: params[:muted_by], reason: params[:reason])
|
|
127
|
+
redirect_to error_path(@error)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def unmute
|
|
131
|
+
@error = Commands::UnmuteError.call(params[:id])
|
|
132
|
+
redirect_to error_path(@error)
|
|
133
|
+
end
|
|
134
|
+
|
|
124
135
|
def update_status
|
|
125
136
|
result = Commands::UpdateErrorStatus.call(params[:id], status: params[:status], comment: params[:comment])
|
|
126
137
|
redirect_to error_path(result[:error])
|
|
@@ -200,6 +211,10 @@ module RailsErrorDashboard
|
|
|
200
211
|
resolved_by_name: params[:resolved_by_name],
|
|
201
212
|
resolution_comment: params[:resolution_comment]
|
|
202
213
|
)
|
|
214
|
+
when "mute"
|
|
215
|
+
Commands::BatchMuteErrors.call(error_ids, muted_by: params[:muted_by])
|
|
216
|
+
when "unmute"
|
|
217
|
+
Commands::BatchUnmuteErrors.call(error_ids)
|
|
203
218
|
when "delete"
|
|
204
219
|
Commands::BatchDeleteErrors.call(error_ids)
|
|
205
220
|
else
|
|
@@ -71,6 +71,8 @@ module RailsErrorDashboard
|
|
|
71
71
|
scope :unassigned, -> { where(assigned_to: nil) }
|
|
72
72
|
scope :by_assignee, ->(name) { where(assigned_to: name) }
|
|
73
73
|
scope :by_priority, ->(level) { where(priority_level: level) }
|
|
74
|
+
scope :muted, -> { where(muted: true) }
|
|
75
|
+
scope :unmuted, -> { where(muted: false) }
|
|
74
76
|
|
|
75
77
|
# Set defaults and tracking
|
|
76
78
|
before_validation :set_defaults, on: :create
|
|
@@ -160,6 +162,20 @@ module RailsErrorDashboard
|
|
|
160
162
|
snoozed_until.present? && snoozed_until >= Time.current
|
|
161
163
|
end
|
|
162
164
|
|
|
165
|
+
# Mute query — checks column existence for backward compatibility
|
|
166
|
+
def muted?
|
|
167
|
+
self.class.column_names.include?("muted") && muted == true
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Mute/unmute convenience methods — delegate to Commands
|
|
171
|
+
def mute!(mute_data = {})
|
|
172
|
+
Commands::MuteError.call(id, **mute_data)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def unmute!
|
|
176
|
+
Commands::UnmuteError.call(id)
|
|
177
|
+
end
|
|
178
|
+
|
|
163
179
|
# Priority methods
|
|
164
180
|
def priority_label
|
|
165
181
|
priority_data = PRIORITY_LEVELS[priority_level]
|
|
@@ -90,6 +90,9 @@
|
|
|
90
90
|
<% if error.reopened? %>
|
|
91
91
|
<i class="bi bi-arrow-counterclockwise text-warning ms-1" data-bs-toggle="tooltip" title="Reopened"></i>
|
|
92
92
|
<% end %>
|
|
93
|
+
<% if error.respond_to?(:muted?) && error.muted? %>
|
|
94
|
+
<i class="bi bi-bell-slash text-secondary ms-1" data-bs-toggle="tooltip" title="Muted - notifications silenced"></i>
|
|
95
|
+
<% end %>
|
|
93
96
|
</td>
|
|
94
97
|
<td onclick="event.stopPropagation();">
|
|
95
98
|
<%= link_to error_path(error), class: "btn btn-sm btn-outline-primary" do %>
|
|
@@ -137,3 +137,38 @@
|
|
|
137
137
|
</div>
|
|
138
138
|
</div>
|
|
139
139
|
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Mute Notifications Modal -->
|
|
142
|
+
<div class="modal fade" id="muteModal" tabindex="-1" aria-labelledby="muteModalLabel" aria-hidden="true">
|
|
143
|
+
<div class="modal-dialog">
|
|
144
|
+
<div class="modal-content">
|
|
145
|
+
<%= form_with url: mute_error_path(error), method: :post do |f| %>
|
|
146
|
+
<div class="modal-header">
|
|
147
|
+
<h5 class="modal-title" id="muteModalLabel">
|
|
148
|
+
<i class="bi bi-bell-slash"></i> Mute Notifications
|
|
149
|
+
</h5>
|
|
150
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="modal-body">
|
|
153
|
+
<p class="text-muted">
|
|
154
|
+
Muted errors still appear in the dashboard but will not trigger any notifications
|
|
155
|
+
(Slack, email, Discord, PagerDuty, webhooks).
|
|
156
|
+
</p>
|
|
157
|
+
<div class="mb-3">
|
|
158
|
+
<label for="muted_by" class="form-label">Your Name (Optional)</label>
|
|
159
|
+
<%= text_field_tag :muted_by, nil, class: "form-control", placeholder: "e.g., John Doe" %>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="mb-3">
|
|
162
|
+
<label for="reason" class="form-label">Reason (Optional)</label>
|
|
163
|
+
<%= text_area_tag :reason, nil, class: "form-control", rows: 3, placeholder: "Why are you muting this error?" %>
|
|
164
|
+
<small class="text-muted">Reason will be added as a comment</small>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="modal-footer">
|
|
168
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
169
|
+
<%= submit_tag "Mute Notifications", class: "btn btn-secondary", data: { action: "click->loading#click" } %>
|
|
170
|
+
</div>
|
|
171
|
+
<% end %>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
@@ -172,6 +172,35 @@
|
|
|
172
172
|
</div>
|
|
173
173
|
<% end %>
|
|
174
174
|
|
|
175
|
+
<!-- Mute Notifications -->
|
|
176
|
+
<% if error.respond_to?(:muted?) %>
|
|
177
|
+
<div class="mb-3">
|
|
178
|
+
<small class="metadata-label d-block mb-1">Notifications</small>
|
|
179
|
+
<% if error.muted? %>
|
|
180
|
+
<div class="alert alert-secondary py-2 mb-2">
|
|
181
|
+
<i class="bi bi-bell-slash"></i>
|
|
182
|
+
<strong>Muted</strong><br>
|
|
183
|
+
<% if error.muted_by.present? %>
|
|
184
|
+
<small>By <%= error.muted_by %></small><br>
|
|
185
|
+
<% end %>
|
|
186
|
+
<% if error.muted_reason.present? %>
|
|
187
|
+
<small><em><%= error.muted_reason %></em></small><br>
|
|
188
|
+
<% end %>
|
|
189
|
+
<% if error.muted_at.present? %>
|
|
190
|
+
<small>Since <%= local_time(error.muted_at, format: :short) %></small>
|
|
191
|
+
<% end %>
|
|
192
|
+
</div>
|
|
193
|
+
<%= button_to unmute_error_path(error), method: :post, class: "btn btn-sm btn-outline-primary" do %>
|
|
194
|
+
<i class="bi bi-bell"></i> Unmute
|
|
195
|
+
<% end %>
|
|
196
|
+
<% else %>
|
|
197
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#muteModal">
|
|
198
|
+
<i class="bi bi-bell-slash"></i> Mute
|
|
199
|
+
</button>
|
|
200
|
+
<% end %>
|
|
201
|
+
</div>
|
|
202
|
+
<% end %>
|
|
203
|
+
|
|
175
204
|
<% if error.resolved? && error.resolved_by_name.present? %>
|
|
176
205
|
<div class="mb-3">
|
|
177
206
|
<small class="metadata-label d-block mb-1">Resolved By</small>
|
|
@@ -334,6 +363,54 @@
|
|
|
334
363
|
</code>
|
|
335
364
|
</div>
|
|
336
365
|
<% end %>
|
|
366
|
+
|
|
367
|
+
<% if health[:ruby_vm] %>
|
|
368
|
+
<% vm = health[:ruby_vm] %>
|
|
369
|
+
<div class="mb-1">
|
|
370
|
+
<small class="text-muted">VM Cache Invalidations:</small>
|
|
371
|
+
<% invals = vm[:constant_cache_invalidations].to_i %>
|
|
372
|
+
<code class="ms-1 <%= 'text-danger' if invals > 10_000 %>"><%= begin; number_with_delimiter(invals); rescue; invals; end %></code>
|
|
373
|
+
</div>
|
|
374
|
+
<div class="mb-1">
|
|
375
|
+
<small class="text-muted">VM Cache Misses:</small>
|
|
376
|
+
<code class="ms-1"><%= begin; number_with_delimiter(vm[:constant_cache_misses]); rescue; vm[:constant_cache_misses]; end %></code>
|
|
377
|
+
</div>
|
|
378
|
+
<% if vm[:shape_cache_size] %>
|
|
379
|
+
<div class="mb-1">
|
|
380
|
+
<small class="text-muted">Shape Cache Size:</small>
|
|
381
|
+
<code class="ms-1"><%= begin; number_with_delimiter(vm[:shape_cache_size]); rescue; vm[:shape_cache_size]; end %></code>
|
|
382
|
+
</div>
|
|
383
|
+
<% end %>
|
|
384
|
+
<% end %>
|
|
385
|
+
|
|
386
|
+
<% if health[:yjit] %>
|
|
387
|
+
<% yj = health[:yjit] %>
|
|
388
|
+
<% if yj[:compiled_iseq_count] || yj[:compiled_block_count] %>
|
|
389
|
+
<div class="mb-1">
|
|
390
|
+
<small class="text-muted">YJIT Compiled:</small>
|
|
391
|
+
<code class="ms-1"><%= yj[:compiled_iseq_count] %> iseqs / <%= yj[:compiled_block_count] %> blocks</code>
|
|
392
|
+
</div>
|
|
393
|
+
<% end %>
|
|
394
|
+
<% if yj[:invalidation_count] %>
|
|
395
|
+
<div class="mb-1">
|
|
396
|
+
<small class="text-muted">YJIT Invalidations:</small>
|
|
397
|
+
<% yj_invals = yj[:invalidation_count].to_i %>
|
|
398
|
+
<code class="ms-1 <%= 'text-danger' if yj_invals > 100 %>"><%= yj_invals %></code>
|
|
399
|
+
</div>
|
|
400
|
+
<% end %>
|
|
401
|
+
<% if yj[:code_region_size] %>
|
|
402
|
+
<div class="mb-1">
|
|
403
|
+
<small class="text-muted">YJIT Code Size:</small>
|
|
404
|
+
<code class="ms-1"><%= (yj[:code_region_size].to_f / 1024).round(1) %> KB</code>
|
|
405
|
+
</div>
|
|
406
|
+
<% end %>
|
|
407
|
+
<% if yj[:compile_time_ns] %>
|
|
408
|
+
<div class="mb-1">
|
|
409
|
+
<small class="text-muted">YJIT Compile Time:</small>
|
|
410
|
+
<code class="ms-1"><%= (yj[:compile_time_ns].to_f / 1_000_000).round(2) %> ms</code>
|
|
411
|
+
</div>
|
|
412
|
+
<% end %>
|
|
413
|
+
<% end %>
|
|
337
414
|
</div>
|
|
338
415
|
</div>
|
|
339
416
|
<% end %>
|
|
@@ -360,6 +360,13 @@
|
|
|
360
360
|
<%= label_tag :hide_snoozed, "Hide snoozed", class: "form-check-label" %>
|
|
361
361
|
</div>
|
|
362
362
|
</div>
|
|
363
|
+
|
|
364
|
+
<div class="col-auto">
|
|
365
|
+
<div class="form-check">
|
|
366
|
+
<%= check_box_tag :hide_muted, "1", params[:hide_muted] == "1", class: "form-check-input" %>
|
|
367
|
+
<%= label_tag :hide_muted, "Hide muted", class: "form-check-label" %>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
363
370
|
</div>
|
|
364
371
|
</div>
|
|
365
372
|
|
|
@@ -28,10 +28,15 @@
|
|
|
28
28
|
<% end %>
|
|
29
29
|
</h2>
|
|
30
30
|
</div>
|
|
31
|
-
<div class="d-flex gap-2">
|
|
31
|
+
<div class="d-flex gap-2 align-items-center">
|
|
32
32
|
<button type="button" class="btn btn-outline-secondary" onclick="downloadErrorJSON(event)" title="Download error details as JSON">
|
|
33
33
|
<i class="bi bi-download"></i> Export JSON
|
|
34
34
|
</button>
|
|
35
|
+
<% if @error.respond_to?(:muted?) && @error.muted? %>
|
|
36
|
+
<button type="button" class="btn btn-secondary" disabled>
|
|
37
|
+
<i class="bi bi-bell-slash"></i> Muted
|
|
38
|
+
</button>
|
|
39
|
+
<% end %>
|
|
35
40
|
<% if @error.resolved? %>
|
|
36
41
|
<span class="badge bg-success fs-6">
|
|
37
42
|
<i class="bi bi-check-circle"></i> Resolved
|
data/config/routes.rb
CHANGED
|
@@ -95,6 +95,12 @@ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
|
|
|
95
95
|
# Instance variable capture (from 20260306000002)
|
|
96
96
|
t.text :instance_variables
|
|
97
97
|
|
|
98
|
+
# Mute notifications (from 20260323000001)
|
|
99
|
+
t.boolean :muted, default: false, null: false
|
|
100
|
+
t.datetime :muted_at
|
|
101
|
+
t.string :muted_by
|
|
102
|
+
t.string :muted_reason
|
|
103
|
+
|
|
98
104
|
t.timestamps
|
|
99
105
|
end
|
|
100
106
|
|
|
@@ -133,6 +139,9 @@ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
|
|
|
133
139
|
add_index :rails_error_dashboard_error_logs, [ :application_id, :occurred_at ], name: "index_error_logs_on_app_occurred"
|
|
134
140
|
add_index :rails_error_dashboard_error_logs, [ :application_id, :resolved ], name: "index_error_logs_on_app_resolved"
|
|
135
141
|
|
|
142
|
+
# Mute index (from 20260323000001)
|
|
143
|
+
add_index :rails_error_dashboard_error_logs, :muted
|
|
144
|
+
|
|
136
145
|
# Workflow indexes (from 20251229111223)
|
|
137
146
|
add_index :rails_error_dashboard_error_logs, [ :assigned_to, :status, :occurred_at ], name: "index_error_logs_on_assignment_workflow"
|
|
138
147
|
add_index :rails_error_dashboard_error_logs, [ :priority_level, :resolved, :occurred_at ], name: "index_error_logs_on_priority_resolution"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddMutedToErrorLogs < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
return if column_exists?(:rails_error_dashboard_error_logs, :muted)
|
|
6
|
+
|
|
7
|
+
add_column :rails_error_dashboard_error_logs, :muted, :boolean, default: false, null: false
|
|
8
|
+
add_column :rails_error_dashboard_error_logs, :muted_at, :datetime
|
|
9
|
+
add_column :rails_error_dashboard_error_logs, :muted_by, :string
|
|
10
|
+
add_column :rails_error_dashboard_error_logs, :muted_reason, :string
|
|
11
|
+
|
|
12
|
+
add_index :rails_error_dashboard_error_logs, :muted
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsErrorDashboard
|
|
4
|
+
module Commands
|
|
5
|
+
# Command: Mute multiple errors at once
|
|
6
|
+
class BatchMuteErrors
|
|
7
|
+
def self.call(error_ids, muted_by: nil)
|
|
8
|
+
new(error_ids, muted_by).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(error_ids, muted_by = nil)
|
|
12
|
+
@error_ids = Array(error_ids).compact
|
|
13
|
+
@muted_by = muted_by
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
return { success: false, count: 0, errors: [ "No error IDs provided" ] } if @error_ids.empty?
|
|
18
|
+
|
|
19
|
+
errors = ErrorLog.where(id: @error_ids)
|
|
20
|
+
|
|
21
|
+
muted_count = 0
|
|
22
|
+
failed_ids = []
|
|
23
|
+
muted_errors = []
|
|
24
|
+
|
|
25
|
+
errors.each do |error|
|
|
26
|
+
begin
|
|
27
|
+
error.update!(
|
|
28
|
+
muted: true,
|
|
29
|
+
muted_at: Time.current,
|
|
30
|
+
muted_by: @muted_by
|
|
31
|
+
)
|
|
32
|
+
muted_count += 1
|
|
33
|
+
muted_errors << error
|
|
34
|
+
rescue => e
|
|
35
|
+
failed_ids << error.id
|
|
36
|
+
RailsErrorDashboard::Logger.error("Failed to mute error #{error.id}: #{e.message}")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
PluginRegistry.dispatch(:on_errors_batch_muted, muted_errors) if muted_errors.any?
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
success: failed_ids.empty?,
|
|
44
|
+
count: muted_count,
|
|
45
|
+
total: @error_ids.size,
|
|
46
|
+
failed_ids: failed_ids,
|
|
47
|
+
errors: failed_ids.empty? ? [] : [ "Failed to mute #{failed_ids.size} error(s)" ]
|
|
48
|
+
}
|
|
49
|
+
rescue => e
|
|
50
|
+
RailsErrorDashboard::Logger.error("Batch mute failed: #{e.message}")
|
|
51
|
+
{ success: false, count: 0, total: @error_ids.size, errors: [ e.message ] }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsErrorDashboard
|
|
4
|
+
module Commands
|
|
5
|
+
# Command: Unmute multiple errors at once
|
|
6
|
+
class BatchUnmuteErrors
|
|
7
|
+
def self.call(error_ids)
|
|
8
|
+
new(error_ids).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(error_ids)
|
|
12
|
+
@error_ids = Array(error_ids).compact
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
return { success: false, count: 0, errors: [ "No error IDs provided" ] } if @error_ids.empty?
|
|
17
|
+
|
|
18
|
+
errors = ErrorLog.where(id: @error_ids)
|
|
19
|
+
|
|
20
|
+
unmuted_count = 0
|
|
21
|
+
failed_ids = []
|
|
22
|
+
unmuted_errors = []
|
|
23
|
+
|
|
24
|
+
errors.each do |error|
|
|
25
|
+
begin
|
|
26
|
+
error.update!(
|
|
27
|
+
muted: false,
|
|
28
|
+
muted_at: nil,
|
|
29
|
+
muted_by: nil,
|
|
30
|
+
muted_reason: nil
|
|
31
|
+
)
|
|
32
|
+
unmuted_count += 1
|
|
33
|
+
unmuted_errors << error
|
|
34
|
+
rescue => e
|
|
35
|
+
failed_ids << error.id
|
|
36
|
+
RailsErrorDashboard::Logger.error("Failed to unmute error #{error.id}: #{e.message}")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
PluginRegistry.dispatch(:on_errors_batch_unmuted, unmuted_errors) if unmuted_errors.any?
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
success: failed_ids.empty?,
|
|
44
|
+
count: unmuted_count,
|
|
45
|
+
total: @error_ids.size,
|
|
46
|
+
failed_ids: failed_ids,
|
|
47
|
+
errors: failed_ids.empty? ? [] : [ "Failed to unmute #{failed_ids.size} error(s)" ]
|
|
48
|
+
}
|
|
49
|
+
rescue => e
|
|
50
|
+
RailsErrorDashboard::Logger.error("Batch unmute failed: #{e.message}")
|
|
51
|
+
{ success: false, count: 0, total: @error_ids.size, errors: [ e.message ] }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -266,31 +266,20 @@ module RailsErrorDashboard
|
|
|
266
266
|
end
|
|
267
267
|
end
|
|
268
268
|
|
|
269
|
-
# Send notifications for new errors and reopened errors (with throttling)
|
|
269
|
+
# Send notifications for new errors and reopened errors (with throttling).
|
|
270
|
+
# Muted errors skip notification dispatch but still fire plugin events.
|
|
270
271
|
if error_log.occurrence_count == 1
|
|
271
|
-
|
|
272
|
-
if Services::NotificationThrottler.severity_meets_minimum?(error_log)
|
|
273
|
-
Services::ErrorNotificationDispatcher.call(error_log)
|
|
274
|
-
Services::NotificationThrottler.record_notification(error_log)
|
|
275
|
-
end
|
|
272
|
+
maybe_notify(error_log) { Services::NotificationThrottler.severity_meets_minimum?(error_log) }
|
|
276
273
|
PluginRegistry.dispatch(:on_error_logged, error_log)
|
|
277
274
|
trigger_callbacks(error_log)
|
|
278
275
|
emit_instrumentation_events(error_log)
|
|
279
276
|
elsif error_log.just_reopened
|
|
280
|
-
|
|
281
|
-
if Services::NotificationThrottler.should_notify?(error_log)
|
|
282
|
-
Services::ErrorNotificationDispatcher.call(error_log)
|
|
283
|
-
Services::NotificationThrottler.record_notification(error_log)
|
|
284
|
-
end
|
|
277
|
+
maybe_notify(error_log) { Services::NotificationThrottler.should_notify?(error_log) }
|
|
285
278
|
PluginRegistry.dispatch(:on_error_reopened, error_log)
|
|
286
279
|
trigger_callbacks(error_log)
|
|
287
280
|
emit_instrumentation_events(error_log)
|
|
288
281
|
else
|
|
289
|
-
|
|
290
|
-
if Services::NotificationThrottler.threshold_reached?(error_log)
|
|
291
|
-
Services::ErrorNotificationDispatcher.call(error_log)
|
|
292
|
-
Services::NotificationThrottler.record_notification(error_log)
|
|
293
|
-
end
|
|
282
|
+
maybe_notify(error_log) { Services::NotificationThrottler.threshold_reached?(error_log) }
|
|
294
283
|
PluginRegistry.dispatch(:on_error_recurred, error_log)
|
|
295
284
|
end
|
|
296
285
|
|
|
@@ -310,6 +299,16 @@ module RailsErrorDashboard
|
|
|
310
299
|
|
|
311
300
|
private
|
|
312
301
|
|
|
302
|
+
# Dispatch notification if error is not muted and the throttle check passes.
|
|
303
|
+
# Muted errors skip notifications but still fire plugin events/callbacks.
|
|
304
|
+
def maybe_notify(error_log)
|
|
305
|
+
return if error_log.muted?
|
|
306
|
+
return unless yield
|
|
307
|
+
|
|
308
|
+
Services::ErrorNotificationDispatcher.call(error_log)
|
|
309
|
+
Services::NotificationThrottler.record_notification(error_log)
|
|
310
|
+
end
|
|
311
|
+
|
|
313
312
|
# Find or create application for multi-app support
|
|
314
313
|
def find_or_create_application
|
|
315
314
|
app_name = RailsErrorDashboard.configuration.application_name ||
|
|
@@ -369,8 +368,9 @@ module RailsErrorDashboard
|
|
|
369
368
|
def check_baseline_anomaly(error_log)
|
|
370
369
|
config = RailsErrorDashboard.configuration
|
|
371
370
|
|
|
372
|
-
# Return early if baseline alerts are disabled
|
|
371
|
+
# Return early if baseline alerts are disabled or error is muted
|
|
373
372
|
return unless config.enable_baseline_alerts
|
|
373
|
+
return if error_log.muted?
|
|
374
374
|
return unless defined?(Queries::BaselineStats)
|
|
375
375
|
return unless defined?(BaselineAlertJob)
|
|
376
376
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsErrorDashboard
|
|
4
|
+
module Commands
|
|
5
|
+
# Command: Mute notifications for an error
|
|
6
|
+
# Muted errors still appear in the dashboard but do not trigger any notifications.
|
|
7
|
+
class MuteError
|
|
8
|
+
def self.call(error_id, muted_by: nil, reason: nil)
|
|
9
|
+
new(error_id, muted_by, reason).call
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(error_id, muted_by, reason)
|
|
13
|
+
@error_id = error_id
|
|
14
|
+
@muted_by = muted_by
|
|
15
|
+
@reason = reason
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
error = ErrorLog.find(@error_id)
|
|
20
|
+
|
|
21
|
+
if @reason.present?
|
|
22
|
+
error.comments.create!(
|
|
23
|
+
author_name: @muted_by || "System",
|
|
24
|
+
body: "Muted notifications: #{@reason}"
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
error.update!(
|
|
29
|
+
muted: true,
|
|
30
|
+
muted_at: Time.current,
|
|
31
|
+
muted_by: @muted_by,
|
|
32
|
+
muted_reason: @reason
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
PluginRegistry.dispatch(:on_error_muted, error)
|
|
36
|
+
error
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsErrorDashboard
|
|
4
|
+
module Commands
|
|
5
|
+
# Command: Unmute notifications for an error
|
|
6
|
+
# Restores normal notification behavior for the error.
|
|
7
|
+
class UnmuteError
|
|
8
|
+
def self.call(error_id)
|
|
9
|
+
new(error_id).call
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(error_id)
|
|
13
|
+
@error_id = error_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
error = ErrorLog.find(@error_id)
|
|
18
|
+
error.update!(
|
|
19
|
+
muted: false,
|
|
20
|
+
muted_at: nil,
|
|
21
|
+
muted_by: nil,
|
|
22
|
+
muted_reason: nil
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
PluginRegistry.dispatch(:on_error_unmuted, error)
|
|
26
|
+
error
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -39,6 +39,7 @@ module RailsErrorDashboard
|
|
|
39
39
|
query = filter_by_assignment(query)
|
|
40
40
|
query = filter_by_priority(query)
|
|
41
41
|
query = filter_by_snoozed(query)
|
|
42
|
+
query = filter_by_muted(query)
|
|
42
43
|
query = filter_by_reopened(query)
|
|
43
44
|
query
|
|
44
45
|
end
|
|
@@ -196,6 +197,16 @@ module RailsErrorDashboard
|
|
|
196
197
|
end
|
|
197
198
|
end
|
|
198
199
|
|
|
200
|
+
def filter_by_muted(query)
|
|
201
|
+
return query unless ErrorLog.column_names.include?("muted")
|
|
202
|
+
|
|
203
|
+
if @filters[:hide_muted] == "1" || @filters[:hide_muted] == true
|
|
204
|
+
query.unmuted
|
|
205
|
+
else
|
|
206
|
+
query
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
199
210
|
def filter_by_reopened(query)
|
|
200
211
|
return query unless @filters[:reopened] == "true"
|
|
201
212
|
return query unless ErrorLog.column_names.include?("reopened_at")
|
|
@@ -34,6 +34,8 @@ module RailsErrorDashboard
|
|
|
34
34
|
connection_pool: connection_pool_stats,
|
|
35
35
|
puma: puma_stats,
|
|
36
36
|
job_queue: job_queue_stats,
|
|
37
|
+
ruby_vm: ruby_vm_stats,
|
|
38
|
+
yjit: yjit_stats,
|
|
37
39
|
captured_at: Time.current.iso8601
|
|
38
40
|
}
|
|
39
41
|
end
|
|
@@ -140,6 +142,37 @@ module RailsErrorDashboard
|
|
|
140
142
|
rescue => e
|
|
141
143
|
nil
|
|
142
144
|
end
|
|
145
|
+
|
|
146
|
+
# RubyVM.stat — constant/method cache invalidation rates
|
|
147
|
+
# Keys vary by Ruby version; pass through full hash for forward-compat
|
|
148
|
+
# Ruby 3.2+: constant_cache_invalidations, constant_cache_misses,
|
|
149
|
+
# global_cvar_state, next_shape_id, shape_cache_size
|
|
150
|
+
def ruby_vm_stats
|
|
151
|
+
return nil unless defined?(RubyVM) && RubyVM.respond_to?(:stat)
|
|
152
|
+
RubyVM.stat
|
|
153
|
+
rescue => e
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# RubyVM::YJIT.runtime_stats — JIT compilation health
|
|
158
|
+
# Cherry-picks diagnostic keys (full hash has 30+ entries)
|
|
159
|
+
def yjit_stats
|
|
160
|
+
return nil unless defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enabled?) && RubyVM::YJIT.enabled?
|
|
161
|
+
raw = RubyVM::YJIT.runtime_stats
|
|
162
|
+
{
|
|
163
|
+
inline_code_size: raw[:inline_code_size],
|
|
164
|
+
code_region_size: raw[:code_region_size],
|
|
165
|
+
compiled_iseq_count: raw[:compiled_iseq_count],
|
|
166
|
+
compiled_block_count: raw[:compiled_block_count],
|
|
167
|
+
compile_time_ns: raw[:compile_time_ns],
|
|
168
|
+
invalidation_count: raw[:invalidation_count],
|
|
169
|
+
invalidate_method_lookup: raw[:invalidate_method_lookup],
|
|
170
|
+
invalidate_constant_state_bump: raw[:invalidate_constant_state_bump],
|
|
171
|
+
object_shape_count: raw[:object_shape_count]
|
|
172
|
+
}
|
|
173
|
+
rescue => e
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
143
176
|
end
|
|
144
177
|
end
|
|
145
178
|
end
|
|
@@ -78,6 +78,10 @@ require "rails_error_dashboard/commands/unassign_error"
|
|
|
78
78
|
require "rails_error_dashboard/commands/update_error_priority"
|
|
79
79
|
require "rails_error_dashboard/commands/snooze_error"
|
|
80
80
|
require "rails_error_dashboard/commands/unsnooze_error"
|
|
81
|
+
require "rails_error_dashboard/commands/mute_error"
|
|
82
|
+
require "rails_error_dashboard/commands/unmute_error"
|
|
83
|
+
require "rails_error_dashboard/commands/batch_mute_errors"
|
|
84
|
+
require "rails_error_dashboard/commands/batch_unmute_errors"
|
|
81
85
|
require "rails_error_dashboard/commands/update_error_status"
|
|
82
86
|
require "rails_error_dashboard/commands/add_error_comment"
|
|
83
87
|
require "rails_error_dashboard/commands/increment_cascade_detection"
|
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.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -338,6 +338,7 @@ files:
|
|
|
338
338
|
- db/migrate/20260306000002_add_instance_variables_to_error_logs.rb
|
|
339
339
|
- db/migrate/20260306000003_create_rails_error_dashboard_swallowed_exceptions.rb
|
|
340
340
|
- db/migrate/20260307000001_create_rails_error_dashboard_diagnostic_dumps.rb
|
|
341
|
+
- db/migrate/20260323000001_add_muted_to_error_logs.rb
|
|
341
342
|
- lib/generators/rails_error_dashboard/install/install_generator.rb
|
|
342
343
|
- lib/generators/rails_error_dashboard/install/templates/README
|
|
343
344
|
- lib/generators/rails_error_dashboard/install/templates/initializer.rb
|
|
@@ -348,16 +349,20 @@ files:
|
|
|
348
349
|
- lib/rails_error_dashboard/commands/add_error_comment.rb
|
|
349
350
|
- lib/rails_error_dashboard/commands/assign_error.rb
|
|
350
351
|
- lib/rails_error_dashboard/commands/batch_delete_errors.rb
|
|
352
|
+
- lib/rails_error_dashboard/commands/batch_mute_errors.rb
|
|
351
353
|
- lib/rails_error_dashboard/commands/batch_resolve_errors.rb
|
|
354
|
+
- lib/rails_error_dashboard/commands/batch_unmute_errors.rb
|
|
352
355
|
- lib/rails_error_dashboard/commands/calculate_cascade_probability.rb
|
|
353
356
|
- lib/rails_error_dashboard/commands/find_or_create_application.rb
|
|
354
357
|
- lib/rails_error_dashboard/commands/find_or_increment_error.rb
|
|
355
358
|
- lib/rails_error_dashboard/commands/flush_swallowed_exceptions.rb
|
|
356
359
|
- lib/rails_error_dashboard/commands/increment_cascade_detection.rb
|
|
357
360
|
- lib/rails_error_dashboard/commands/log_error.rb
|
|
361
|
+
- lib/rails_error_dashboard/commands/mute_error.rb
|
|
358
362
|
- lib/rails_error_dashboard/commands/resolve_error.rb
|
|
359
363
|
- lib/rails_error_dashboard/commands/snooze_error.rb
|
|
360
364
|
- lib/rails_error_dashboard/commands/unassign_error.rb
|
|
365
|
+
- lib/rails_error_dashboard/commands/unmute_error.rb
|
|
361
366
|
- lib/rails_error_dashboard/commands/unsnooze_error.rb
|
|
362
367
|
- lib/rails_error_dashboard/commands/update_error_priority.rb
|
|
363
368
|
- lib/rails_error_dashboard/commands/update_error_status.rb
|
|
@@ -457,7 +462,7 @@ metadata:
|
|
|
457
462
|
bug_tracker_uri: https://github.com/AnjanJ/rails_error_dashboard/issues
|
|
458
463
|
funding_uri: https://buymeacoffee.com/anjanj
|
|
459
464
|
post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
|
|
460
|
-
\ Rails Error Dashboard v0.4.
|
|
465
|
+
\ Rails Error Dashboard v0.4.2\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
|
|
461
466
|
First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
|
|
462
467
|
db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
|
|
463
468
|
=> '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n
|