rails_error_dashboard 0.14.3 → 0.14.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 +4 -4
- data/README.md +33 -12
- data/app/helpers/rails_error_dashboard/application_helper.rb +8 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +13 -0
- data/config/locales/de.yml +4 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/es.yml +4 -0
- data/config/locales/fr.yml +4 -0
- data/config/locales/it.yml +4 -0
- data/config/locales/ja.yml +4 -0
- data/config/locales/pl.yml +4 -0
- data/config/locales/pt-BR.yml +4 -0
- data/config/locales/ru.yml +4 -0
- data/config/locales/uk.yml +4 -0
- data/config/locales/zh-CN.yml +4 -0
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +9 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +31 -34
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +4 -3
- data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +11 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +5 -2
- data/lib/rails_error_dashboard/configuration.rb +34 -2
- data/lib/rails_error_dashboard/engine.rb +10 -4
- data/lib/rails_error_dashboard/middleware/rate_limiter.rb +15 -43
- data/lib/rails_error_dashboard/plugin.rb +30 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +3 -1
- data/lib/rails_error_dashboard/services/exception_filter.rb +4 -1
- data/lib/rails_error_dashboard/services/solid_queue_config_check.rb +47 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/tasks/error_dashboard.rake +43 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd115b4f0fa37f0b90cc22ceea5d5f8db5bdb59ceb40972c3d9ff70c6055a643
|
|
4
|
+
data.tar.gz: b1339930832c6e108d0e55aae93ad2cf14ed1935fa5af23bee24b9f9f4d8943d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9901f9f6fcc64cf53f71cdf43545e2314fe410638eafb414619159d9955763e8251007b13aa5d02e3967ebfbe4b87fb6dff3700694d27031d67ed39c8281e8ac
|
|
7
|
+
data.tar.gz: d039b6f3796fad5884ae83bbe374671d5d2f90137747c0f404fd917cbcf8088f3b4101caf5c9b77524562b4e4c2c2ca3581d99abadfc16797d9bd35122c83afc
|
data/README.md
CHANGED
|
@@ -19,7 +19,7 @@ rails generate rails_error_dashboard:install
|
|
|
19
19
|
rails db:migrate
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Open `/red
|
|
22
|
+
Open `/red`, then send a test error from its Settings page. No monitoring account or ingestion service is required.
|
|
23
23
|
|
|
24
24
|
[Try the live demo](https://rails-error-dashboard.anjan.dev) (`gandalf` / `youshallnotpass`) · [Read the documentation](https://anjanj.github.io/rails_error_dashboard/) · [View on RubyGems](https://rubygems.org/gems/rails_error_dashboard)
|
|
25
25
|
|
|
@@ -91,7 +91,7 @@ That makes RED a self-hosted Sentry alternative for teams that want Rails-specif
|
|
|
91
91
|
|
|
92
92
|
- Store data in the application's existing PostgreSQL, MySQL/Trilogy or SQLite database.
|
|
93
93
|
- Isolate monitoring writes in a separate error database.
|
|
94
|
-
- Use synchronous writes, or async logging through
|
|
94
|
+
- Use synchronous writes, or async logging through your app's own Active Job backend (Sidekiq, Solid Queue, GoodJob or Rails' in-process `:async`).
|
|
95
95
|
- Track several Rails applications through a shared database.
|
|
96
96
|
|
|
97
97
|
No RED licence or event-ingestion fee, and no plan limits — your database is the only cap, and storm protection deliberately sheds context during floods.
|
|
@@ -619,6 +619,11 @@ Span names follow the `rails_error_dashboard.<operation>` convention, e.g. `rail
|
|
|
619
619
|
gem 'rails_error_dashboard'
|
|
620
620
|
```
|
|
621
621
|
|
|
622
|
+
RED requires Rails 7.0 or newer, Ruby 3.2 or newer, `pagy ~> 43` and `groupdate ~> 6`. An app that pins an older Pagy or groupdate must upgrade it first, or `bundle install` fails.
|
|
623
|
+
|
|
624
|
+
- **Rails before 8.1.4 or 7.2.4** (every 8.0, 7.1 and 7.0 release): also add `gem "json", "< 3"`. json 3 breaks those Rails versions, and the dashboard returns 500. See [the json pin](docs/UPGRADING.md#rails-before-814-or-724-pin-json-below-3).
|
|
625
|
+
- **MySQL**: load the time-zone tables (`mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql`), or the charts fail.
|
|
626
|
+
|
|
622
627
|
### 2. Install with Interactive Setup
|
|
623
628
|
|
|
624
629
|
```bash
|
|
@@ -627,7 +632,9 @@ rails generate rails_error_dashboard:install
|
|
|
627
632
|
rails db:migrate
|
|
628
633
|
```
|
|
629
634
|
|
|
630
|
-
The installer
|
|
635
|
+
The installer asks about notifications and advanced analytics, then where to store errors: in your app's database (the default), a separate one, or one shared by several apps. Without a terminal (CI, Docker) it asks nothing: async logging is on, and every other optional feature is off.
|
|
636
|
+
|
|
637
|
+
**Chose a separate or shared database?** Add the `config/database.yml` entry the installer prints, then run `bin/rails db:create` before `db:migrate`. See [Database Options](docs/guides/DATABASE_OPTIONS.md).
|
|
631
638
|
|
|
632
639
|
### 3. Visit your dashboard
|
|
633
640
|
|
|
@@ -637,17 +644,30 @@ http://localhost:3000/red
|
|
|
637
644
|
|
|
638
645
|
Default credentials: `gandalf` / `youshallnotpass`, for development and test only.
|
|
639
646
|
|
|
640
|
-
**Before you deploy anywhere else**, set `ERROR_DASHBOARD_USER` and `ERROR_DASHBOARD_PASSWORD` in that environment. Outside development and test, the app refuses to boot on the defaults. See [Dashboard Credentials](docs/guides/CONFIGURATION.md#dashboard-credentials).
|
|
647
|
+
**Before you deploy anywhere else**, set `ERROR_DASHBOARD_USER` and `ERROR_DASHBOARD_PASSWORD` in that environment. Outside development and test, the app refuses to boot on the defaults. See [Dashboard Credentials](docs/guides/CONFIGURATION.md#dashboard-credentials). For the rest of what production needs (a worker, scheduled jobs, migrations on deploy), see [Running in Production](docs/PRODUCTION.md).
|
|
641
648
|
|
|
642
649
|
### 4. Test it out
|
|
643
650
|
|
|
651
|
+
Open `/red/settings` and click **Send Test Error**, or raise an exception in any controller action. Errors raised at the Rails console prompt are not captured. To report one from the console, use:
|
|
652
|
+
|
|
644
653
|
```ruby
|
|
645
|
-
|
|
646
|
-
raise "Test error from Rails Error Dashboard!"
|
|
654
|
+
Rails.error.report(RuntimeError.new("Test error from Rails Error Dashboard"), handled: false)
|
|
647
655
|
```
|
|
648
656
|
|
|
657
|
+
With async logging on (the default), leave the console open for a second or two so the background job can write it.
|
|
658
|
+
|
|
649
659
|
[Full installation guide →](docs/QUICKSTART.md)
|
|
650
660
|
|
|
661
|
+
### Upgrading
|
|
662
|
+
|
|
663
|
+
```bash
|
|
664
|
+
bundle update rails_error_dashboard
|
|
665
|
+
bin/rails generate rails_error_dashboard:install --no-interactive
|
|
666
|
+
bin/rails db:migrate
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
The installer keeps your initializer and copies only the new migrations. Some releases need a step of their own, and two of them can stop an app booting: [read the upgrade guide](docs/UPGRADING.md) before you deploy.
|
|
670
|
+
|
|
651
671
|
---
|
|
652
672
|
|
|
653
673
|
## Configuration
|
|
@@ -661,8 +681,7 @@ RailsErrorDashboard.configure do |config|
|
|
|
661
681
|
# Optional features — enable as needed
|
|
662
682
|
config.enable_slack_notifications = true
|
|
663
683
|
config.slack_webhook_url = ENV['SLACK_WEBHOOK_URL']
|
|
664
|
-
config.async_logging = true
|
|
665
|
-
config.async_adapter = :sidekiq # or :solid_queue, :async
|
|
684
|
+
config.async_logging = true # jobs run on your app's Active Job adapter; production needs a worker
|
|
666
685
|
end
|
|
667
686
|
```
|
|
668
687
|
|
|
@@ -670,7 +689,7 @@ end
|
|
|
670
689
|
|
|
671
690
|
**Multi-App Support** — Track errors from multiple Rails apps in a single shared database. Auto-detects app name, supports per-app filtering. [Multi-App guide →](docs/MULTI_APP_PERFORMANCE.md)
|
|
672
691
|
|
|
673
|
-
**OpenTelemetry Export** — Emit error-capture operations as OTel spans to Datadog, Honeycomb, or Jaeger. Add `gem "opentelemetry-api"` and set `config.enable_otel_export = true`. See
|
|
692
|
+
**OpenTelemetry Export** — Emit error-capture operations as OTel spans to Datadog, Honeycomb, or Jaeger. Add `gem "opentelemetry-api"` and set `config.enable_otel_export = true`. See **OpenTelemetry Export** under [Features](#features) above for the full options.
|
|
674
693
|
|
|
675
694
|
---
|
|
676
695
|
|
|
@@ -711,7 +730,7 @@ RED translates through its own private I18n backend, so it never reads, writes o
|
|
|
711
730
|
## FAQ
|
|
712
731
|
|
|
713
732
|
**Does Rails Error Dashboard support a separate database for errors?**
|
|
714
|
-
Yes. You can store errors in your app's existing database
|
|
733
|
+
Yes. You can store errors in your app's existing database **or** in a dedicated one. Choose it at the installer's database prompt (or pass `--separate-database`), then add the `error_dashboard` entry it prints to `config/database.yml` for every environment. The engine routes all of its tables through `connects_to`, keeping error data isolated from your app data. Both modes are first-class and covered by the [Database Options guide](docs/guides/DATABASE_OPTIONS.md).
|
|
715
734
|
|
|
716
735
|
**Which databases does it work with?**
|
|
717
736
|
SQLite, PostgreSQL, and MySQL/Trilogy — in either shared or separate-database mode.
|
|
@@ -726,13 +745,13 @@ Yes — local **and** instance variables at the moment the exception is raised,
|
|
|
726
745
|
No. Storm protection (a circuit breaker with adaptive sampling, **ON by default**) makes the gem degrade itself first during error floods — occurrence counts stay exact while it sheds the expensive work, and a Storm History page shows exactly what was shed. There is no I/O on the hot path — the check is a digest and an atomic increment.
|
|
727
746
|
|
|
728
747
|
**Does it work with my background jobs?**
|
|
729
|
-
Yes — errors raised in jobs are captured, and it can log errors asynchronously through Sidekiq
|
|
748
|
+
Yes — errors raised in jobs are captured, and it can log errors asynchronously through your app's Active Job adapter (Sidekiq, Solid Queue, GoodJob or the in-process `:async`). In production, run a worker for the `default` and `error_notifications` queues. Sidekiq, Solid Queue and GoodJob are all auto-detected for the job-queue stats stored on each error.
|
|
730
749
|
|
|
731
750
|
**Does it work with my authentication?**
|
|
732
751
|
Yes — HTTP Basic Auth out of the box, or a custom `authenticate_with` lambda that integrates with Devise, Warden, or session-based auth.
|
|
733
752
|
|
|
734
753
|
**Can it track more than one app?**
|
|
735
|
-
Yes —
|
|
754
|
+
Yes — apps that point at the same error database share one dashboard, with per-app filtering.
|
|
736
755
|
|
|
737
756
|
**What Rails and Ruby versions are supported?**
|
|
738
757
|
Rails 7.0–8.1 and Ruby 3.2–4.0.
|
|
@@ -744,6 +763,8 @@ Rails 7.0–8.1 and Ruby 3.2–4.0.
|
|
|
744
763
|
### Getting Started
|
|
745
764
|
- **[Quickstart Guide](docs/QUICKSTART.md)** — 5-minute setup
|
|
746
765
|
- **[Configuration](docs/guides/CONFIGURATION.md)** — All configuration options
|
|
766
|
+
- **[Upgrading](docs/UPGRADING.md)** — The upgrade, and the releases that need a step
|
|
767
|
+
- **[Running in Production](docs/PRODUCTION.md)** — A worker, scheduled jobs and the other things RED needs before the first deploy
|
|
747
768
|
- **[Uninstalling](docs/UNINSTALL.md)** — Clean removal
|
|
748
769
|
|
|
749
770
|
### Features
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
module RailsErrorDashboard
|
|
2
2
|
module ApplicationHelper
|
|
3
|
+
# Problems in config/queue.yml that stop RED's jobs in this environment:
|
|
4
|
+
# empty when the config is fine, or when Solid Queue doesn't run RED's jobs.
|
|
5
|
+
def red_solid_queue_problems
|
|
6
|
+
Services::SolidQueueConfigCheck.current_problems
|
|
7
|
+
rescue StandardError
|
|
8
|
+
[]
|
|
9
|
+
end
|
|
10
|
+
|
|
3
11
|
# Returns the host app's CSP nonce (if any) so inline <script> tags pass strict CSP.
|
|
4
12
|
# Falls back to nil when the host has no CSP configured — in that case the script tag
|
|
5
13
|
# works without a nonce attribute. Strict CSPs (script-src 'self' 'nonce-...') require
|
|
@@ -1335,6 +1335,19 @@ tr[data-red-row-href]:hover .sev-bar { opacity: 1 !important; }
|
|
|
1335
1335
|
</div>
|
|
1336
1336
|
<script<%= " nonce=\"#{red_csp_nonce}\"".html_safe if red_csp_nonce %>>try { if (sessionStorage.getItem('red_dismiss_creds_warning') === '1') { document.getElementById('security-warning').style.display = 'none'; } } catch(e) {}</script>
|
|
1337
1337
|
<% end %>
|
|
1338
|
+
<% solid_queue_problems = red_solid_queue_problems %>
|
|
1339
|
+
<% if solid_queue_problems.any? %>
|
|
1340
|
+
<div id="solid-queue-warning" class="alert alert-warning" style="display: flex; align-items: flex-start; gap: 10px; margin-top: var(--space-2); margin-bottom: var(--space-4); border-left: 4px solid var(--status-warning);">
|
|
1341
|
+
<i class="bi bi-exclamation-triangle-fill" style="font-size: 18px; flex-shrink: 0;"></i>
|
|
1342
|
+
<div style="flex: 1;">
|
|
1343
|
+
<strong><%= red_t("red.banners.solid_queue.label") %></strong> <%= red_t("red.banners.solid_queue.body_html") %>
|
|
1344
|
+
<ul style="margin: var(--space-2) 0; padding-left: var(--space-5); font-family: var(--font-mono); font-size: 12px;">
|
|
1345
|
+
<% solid_queue_problems.each do |problem| %><li><%= problem %></li><% end %>
|
|
1346
|
+
</ul>
|
|
1347
|
+
<a href="<%= RailsErrorDashboard::Services::SolidQueueConfigCheck::GUIDE_URL %>" target="_blank" rel="noopener"><%= red_t("red.banners.solid_queue.guide") %></a>
|
|
1348
|
+
</div>
|
|
1349
|
+
</div>
|
|
1350
|
+
<% end %>
|
|
1338
1351
|
<% if defined?(@storm_banner_event) && @storm_banner_event %>
|
|
1339
1352
|
<% storm_active = @storm_banner_event.active? %>
|
|
1340
1353
|
<div id="storm-banner" class="alert <%= storm_active ? 'alert-danger' : 'alert-info' %>" style="display: flex; align-items: center; gap: 10px; margin-top: var(--space-2); margin-bottom: var(--space-4); border-left: 4px solid <%= storm_active ? 'var(--status-critical)' : 'var(--status-info)' %>;">
|
data/config/locales/de.yml
CHANGED
|
@@ -87,6 +87,10 @@ de:
|
|
|
87
87
|
<code>ERROR_DASHBOARD_PASSWORD</code> oder konfigurieren Sie <code>authenticate_with</code>
|
|
88
88
|
in Ihrem Initializer.
|
|
89
89
|
dismiss: Für diese Sitzung ausblenden
|
|
90
|
+
solid_queue:
|
|
91
|
+
label: "Solid Queue führt die Jobs von RED nicht aus."
|
|
92
|
+
body_html: "Im Hintergrund erfasste Fehler und Benachrichtigungen warten in der Warteschlange, bis <code>config/queue.yml</code> korrigiert ist. Führen Sie <code>bin/rails error_dashboard:verify</code> aus, um die Korrektur zu sehen, und starten Sie die App danach neu."
|
|
93
|
+
guide: "Was RED von Solid Queue braucht"
|
|
90
94
|
storm:
|
|
91
95
|
active_title: Fehlersturm im Gange
|
|
92
96
|
recent_title: Sturmschutz war kürzlich aktiv
|
data/config/locales/en.yml
CHANGED
|
@@ -155,6 +155,11 @@ en:
|
|
|
155
155
|
initializer.
|
|
156
156
|
dismiss: "Dismiss for this session"
|
|
157
157
|
|
|
158
|
+
solid_queue:
|
|
159
|
+
# Shown when Solid Queue runs RED's jobs with a config/queue.yml that never would.
|
|
160
|
+
label: "Solid Queue won't run RED's jobs."
|
|
161
|
+
body_html: "Errors captured in the background and notifications wait in the queue until <code>config/queue.yml</code> is fixed. Run <code>bin/rails error_dashboard:verify</code> for the fix, then restart."
|
|
162
|
+
guide: "What RED needs from Solid Queue"
|
|
158
163
|
storm:
|
|
159
164
|
# Whole sentences, not fragments. The English original assembled these
|
|
160
165
|
# from a stem plus a conditional clause, which cannot be reordered into
|
data/config/locales/es.yml
CHANGED
|
@@ -86,6 +86,10 @@ es:
|
|
|
86
86
|
Defina las variables de entorno <code>ERROR_DASHBOARD_USER</code> y <code>ERROR_DASHBOARD_PASSWORD</code>,
|
|
87
87
|
o configure <code>authenticate_with</code> en su initializer.
|
|
88
88
|
dismiss: Ocultar durante esta sesión
|
|
89
|
+
solid_queue:
|
|
90
|
+
label: "Solid Queue no ejecutará los trabajos de RED."
|
|
91
|
+
body_html: "Los errores capturados en segundo plano y las notificaciones esperan en la cola hasta que se corrija <code>config/queue.yml</code>. Ejecute <code>bin/rails error_dashboard:verify</code> para ver la corrección y luego reinicie."
|
|
92
|
+
guide: "Lo que RED necesita de Solid Queue"
|
|
89
93
|
storm:
|
|
90
94
|
active_title: Tormenta de errores en curso
|
|
91
95
|
recent_title: La protección ante tormentas se activó recientemente
|
data/config/locales/fr.yml
CHANGED
|
@@ -87,6 +87,10 @@ fr:
|
|
|
87
87
|
et <code>ERROR_DASHBOARD_PASSWORD</code>, ou configurez <code>authenticate_with</code>
|
|
88
88
|
dans votre initializer.
|
|
89
89
|
dismiss: Masquer pour cette session
|
|
90
|
+
solid_queue:
|
|
91
|
+
label: "Solid Queue n'exécutera pas les tâches de RED."
|
|
92
|
+
body_html: "Les erreurs capturées en arrière-plan et les notifications restent en file d'attente tant que <code>config/queue.yml</code> n'est pas corrigé. Lancez <code>bin/rails error_dashboard:verify</code> pour voir la correction, puis redémarrez."
|
|
93
|
+
guide: "Ce dont RED a besoin de Solid Queue"
|
|
90
94
|
storm:
|
|
91
95
|
active_title: Tempête d'erreurs en cours
|
|
92
96
|
recent_title: La protection anti-tempête s'est déclenchée récemment
|
data/config/locales/it.yml
CHANGED
|
@@ -86,6 +86,10 @@ it:
|
|
|
86
86
|
Imposta le variabili d'ambiente <code>ERROR_DASHBOARD_USER</code> e <code>ERROR_DASHBOARD_PASSWORD</code>
|
|
87
87
|
oppure configura <code>authenticate_with</code> nel tuo inizializzatore.
|
|
88
88
|
dismiss: Nascondi per questa sessione
|
|
89
|
+
solid_queue:
|
|
90
|
+
label: "Solid Queue non eseguirà i job di RED."
|
|
91
|
+
body_html: "Gli errori acquisiti in background e le notifiche restano in coda finché <code>config/queue.yml</code> non viene corretto. Esegui <code>bin/rails error_dashboard:verify</code> per vedere la correzione, poi riavvia."
|
|
92
|
+
guide: "Cosa serve a RED da Solid Queue"
|
|
89
93
|
storm:
|
|
90
94
|
active_title: Tempesta di errori in corso
|
|
91
95
|
recent_title: Protezione dalle tempeste attivata di recente
|
data/config/locales/ja.yml
CHANGED
|
@@ -86,6 +86,10 @@ ja:
|
|
|
86
86
|
と <code>ERROR_DASHBOARD_PASSWORD</code> を設定するか、イニシャライザで <code>authenticate_with</code>
|
|
87
87
|
を設定してください。
|
|
88
88
|
dismiss: このセッションでは表示しない
|
|
89
|
+
solid_queue:
|
|
90
|
+
label: "Solid Queue は RED のジョブを実行しません。"
|
|
91
|
+
body_html: "<code>config/queue.yml</code> を修正するまで、バックグラウンドで記録されたエラーと通知はキューで待機します。<code>bin/rails error_dashboard:verify</code> を実行して修正方法を確認し、再起動してください。"
|
|
92
|
+
guide: "RED が Solid Queue に必要とするもの"
|
|
89
93
|
storm:
|
|
90
94
|
active_title: エラーストームが発生中です
|
|
91
95
|
recent_title: ストーム保護が最近作動しました
|
data/config/locales/pl.yml
CHANGED
|
@@ -86,6 +86,10 @@ pl:
|
|
|
86
86
|
Ustaw zmienne środowiskowe <code>ERROR_DASHBOARD_USER</code> i <code>ERROR_DASHBOARD_PASSWORD</code>
|
|
87
87
|
albo skonfiguruj <code>authenticate_with</code> w swoim inicjalizatorze.
|
|
88
88
|
dismiss: Ukryj na czas tej sesji
|
|
89
|
+
solid_queue:
|
|
90
|
+
label: "Solid Queue nie uruchomi zadań RED."
|
|
91
|
+
body_html: "Błędy przechwycone w tle i powiadomienia czekają w kolejce, dopóki <code>config/queue.yml</code> nie zostanie poprawiony. Uruchom <code>bin/rails error_dashboard:verify</code>, aby zobaczyć poprawkę, a następnie zrestartuj aplikację."
|
|
92
|
+
guide: "Czego RED potrzebuje od Solid Queue"
|
|
89
93
|
storm:
|
|
90
94
|
active_title: Trwa sztorm błędów
|
|
91
95
|
recent_title: Ochrona przed sztormem zadziałała niedawno
|
data/config/locales/pt-BR.yml
CHANGED
|
@@ -86,6 +86,10 @@ pt-BR:
|
|
|
86
86
|
Defina as variáveis de ambiente <code>ERROR_DASHBOARD_USER</code> e <code>ERROR_DASHBOARD_PASSWORD</code>,
|
|
87
87
|
ou configure <code>authenticate_with</code> no seu initializer.
|
|
88
88
|
dismiss: Ocultar nesta sessão
|
|
89
|
+
solid_queue:
|
|
90
|
+
label: "O Solid Queue não executará os jobs do RED."
|
|
91
|
+
body_html: "Erros capturados em segundo plano e notificações aguardam na fila até que <code>config/queue.yml</code> seja corrigido. Execute <code>bin/rails error_dashboard:verify</code> para ver a correção e reinicie."
|
|
92
|
+
guide: "O que o RED precisa do Solid Queue"
|
|
89
93
|
storm:
|
|
90
94
|
active_title: Tempestade de erros em andamento
|
|
91
95
|
recent_title: A proteção contra tempestades foi acionada recentemente
|
data/config/locales/ru.yml
CHANGED
|
@@ -86,6 +86,10 @@ ru:
|
|
|
86
86
|
Задайте переменные окружения <code>ERROR_DASHBOARD_USER</code> и <code>ERROR_DASHBOARD_PASSWORD</code>
|
|
87
87
|
или настройте <code>authenticate_with</code> в инициализаторе.
|
|
88
88
|
dismiss: Скрыть до конца сессии
|
|
89
|
+
solid_queue:
|
|
90
|
+
label: "Solid Queue не будет выполнять задания RED."
|
|
91
|
+
body_html: "Ошибки, записанные в фоне, и уведомления ждут в очереди, пока не исправлен <code>config/queue.yml</code>. Запустите <code>bin/rails error_dashboard:verify</code>, чтобы увидеть исправление, затем перезапустите приложение."
|
|
92
|
+
guide: "Что RED нужно от Solid Queue"
|
|
89
93
|
storm:
|
|
90
94
|
active_title: Идёт шторм ошибок
|
|
91
95
|
recent_title: Недавно сработала защита от шторма
|
data/config/locales/uk.yml
CHANGED
|
@@ -86,6 +86,10 @@ uk:
|
|
|
86
86
|
Встановіть змінні середовища <code>ERROR_DASHBOARD_USER</code> і <code>ERROR_DASHBOARD_PASSWORD</code>
|
|
87
87
|
або налаштуйте <code>authenticate_with</code> у своєму ініціалізаторі.
|
|
88
88
|
dismiss: Приховати для цієї сесії
|
|
89
|
+
solid_queue:
|
|
90
|
+
label: "Solid Queue не виконуватиме завдання RED."
|
|
91
|
+
body_html: "Помилки, записані у фоні, і сповіщення чекають у черзі, доки не виправлено <code>config/queue.yml</code>. Запустіть <code>bin/rails error_dashboard:verify</code>, щоб побачити виправлення, потім перезапустіть застосунок."
|
|
92
|
+
guide: "Що RED потрібно від Solid Queue"
|
|
89
93
|
storm:
|
|
90
94
|
active_title: Триває шторм помилок
|
|
91
95
|
recent_title: Нещодавно спрацював захист від штормів
|
data/config/locales/zh-CN.yml
CHANGED
|
@@ -85,6 +85,10 @@ zh-CN:
|
|
|
85
85
|
body_html: 你正在使用默认凭据(gandalf/youshallnotpass)。请设置环境变量 <code>ERROR_DASHBOARD_USER</code>
|
|
86
86
|
和 <code>ERROR_DASHBOARD_PASSWORD</code>,或在初始化文件中配置 <code>authenticate_with</code>。
|
|
87
87
|
dismiss: 本次会话内不再提示
|
|
88
|
+
solid_queue:
|
|
89
|
+
label: "Solid Queue 不会运行 RED 的任务。"
|
|
90
|
+
body_html: "在修复 <code>config/queue.yml</code> 之前,后台捕获的错误和通知会一直在队列中等待。运行 <code>bin/rails error_dashboard:verify</code> 查看修复方法,然后重启应用。"
|
|
91
|
+
guide: "RED 对 Solid Queue 的要求"
|
|
88
92
|
storm:
|
|
89
93
|
active_title: 错误风暴进行中
|
|
90
94
|
recent_title: 近期触发过风暴保护
|
|
@@ -3,6 +3,15 @@ class RemoveEnvironmentFromErrorLogs < ActiveRecord::Migration[7.0]
|
|
|
3
3
|
# Skip if squashed migration ran (column never existed) or already removed
|
|
4
4
|
return unless column_exists?(:rails_error_dashboard_error_logs, :environment)
|
|
5
5
|
|
|
6
|
+
# Only the v0.1.x column is removed: it was NOT NULL. The column
|
|
7
|
+
# AddEnvironmentToErrorLogs adds back (0.11.0) is nullable and holds real
|
|
8
|
+
# data. This migration replays over it whenever RED's migrations run
|
|
9
|
+
# against tables that already exist (an app joining a shared error
|
|
10
|
+
# database runs its own renumbered copy of every migration), and dropping
|
|
11
|
+
# it there wiped every error's environment.
|
|
12
|
+
environment = columns(:rails_error_dashboard_error_logs).find { |column| column.name == "environment" }
|
|
13
|
+
return if environment.nil? || environment.null
|
|
14
|
+
|
|
6
15
|
# Remove composite index first
|
|
7
16
|
remove_index :rails_error_dashboard_error_logs,
|
|
8
17
|
name: 'index_error_logs_on_environment_and_occurred_at',
|
|
@@ -58,7 +58,7 @@ module RailsErrorDashboard
|
|
|
58
58
|
|
|
59
59
|
if quick_mode?
|
|
60
60
|
@selected_features = build_quick_defaults
|
|
61
|
-
say " Using sensible defaults (analytics ON,
|
|
61
|
+
say " Using sensible defaults (analytics, breadcrumbs ON, system health ON, 50% sampling; notifications OFF)", :green
|
|
62
62
|
return
|
|
63
63
|
end
|
|
64
64
|
|
|
@@ -131,7 +131,10 @@ module RailsErrorDashboard
|
|
|
131
131
|
advanced_keys = %i[async_logging error_sampling breadcrumbs system_health
|
|
132
132
|
source_code_integration git_blame swallowed_exceptions
|
|
133
133
|
crash_capture diagnostic_dump]
|
|
134
|
-
|
|
134
|
+
# A flag the user passed, not an option left at its default: async_logging
|
|
135
|
+
# defaults to true, so "is any of these set?" was always yes and this
|
|
136
|
+
# question was never asked.
|
|
137
|
+
any_advanced_cli_flag = advanced_keys.any? { |k| options[k] != self.class.class_options[k]&.default }
|
|
135
138
|
|
|
136
139
|
say "\n[3/3] Advanced Options (performance tuning & diagnostics)", :cyan
|
|
137
140
|
say " Async logging, error sampling, breadcrumbs, system health,", :white
|
|
@@ -169,8 +172,10 @@ module RailsErrorDashboard
|
|
|
169
172
|
advanced_features.each do |f|
|
|
170
173
|
say "\n #{f[:name]}", :cyan
|
|
171
174
|
say " #{f[:desc]}", :white
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
# Async logging is on unless declined, as it is without this question.
|
|
176
|
+
default_on = f[:key] == :async_logging
|
|
177
|
+
r = ask(" Enable? (#{default_on ? 'Y/n' : 'y/N'}):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
|
|
178
|
+
@selected_features[f[:key]] = default_on ? r.downcase != "n" : r.downcase == "y"
|
|
174
179
|
if @selected_features[f[:key]] && f[:key] == :swallowed_exceptions && RUBY_VERSION < "3.3"
|
|
175
180
|
say " ⚠ Requires Ruby 3.3+ (you have #{RUBY_VERSION}) — will activate after upgrade", :yellow
|
|
176
181
|
end
|
|
@@ -293,7 +298,9 @@ module RailsErrorDashboard
|
|
|
293
298
|
@enable_webhooks = @selected_features&.dig(:webhooks) || options[:webhooks]
|
|
294
299
|
|
|
295
300
|
# Performance
|
|
296
|
-
|
|
301
|
+
# An answer, when there was one, wins: `answer || default` turned a "no"
|
|
302
|
+
# into the default's "yes".
|
|
303
|
+
@enable_async_logging = @selected_features&.key?(:async_logging) ? @selected_features[:async_logging] : options[:async_logging]
|
|
297
304
|
@enable_error_sampling = @selected_features&.dig(:error_sampling) || options[:error_sampling]
|
|
298
305
|
|
|
299
306
|
# Database mode (set by select_database_mode or CLI flags)
|
|
@@ -493,12 +500,12 @@ module RailsErrorDashboard
|
|
|
493
500
|
say "Next Steps:", :cyan
|
|
494
501
|
if @enable_separate_database
|
|
495
502
|
say " 1. Add the database.yml entry shown above"
|
|
496
|
-
say " 2. Run: rails db:create
|
|
503
|
+
say " 2. Run: rails db:create:#{@database_name}"
|
|
497
504
|
if @enable_multi_app
|
|
498
505
|
say " 3. Run migrations (only needed on the FIRST app):"
|
|
499
|
-
say " rails db:migrate
|
|
506
|
+
say " rails db:migrate:#{@database_name}"
|
|
500
507
|
else
|
|
501
|
-
say " 3. Run: rails db:migrate
|
|
508
|
+
say " 3. Run: rails db:migrate:#{@database_name}"
|
|
502
509
|
end
|
|
503
510
|
say " 4. Before deploying: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD there"
|
|
504
511
|
say " 5. Restart your Rails server"
|
|
@@ -595,33 +602,23 @@ module RailsErrorDashboard
|
|
|
595
602
|
say " # Separate error database", :white
|
|
596
603
|
end
|
|
597
604
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
say "
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
say " <<: *default", :white
|
|
615
|
-
say " database: #{app_name_snake}_production", :white
|
|
616
|
-
say " error_dashboard:", :white
|
|
617
|
-
say " <<: *default", :white
|
|
618
|
-
if @enable_multi_app
|
|
619
|
-
shared_db_base = @shared_db_name || "shared_errors"
|
|
620
|
-
say " database: #{shared_db_base}_production", :white
|
|
621
|
-
else
|
|
622
|
-
say " database: #{app_name_snake}_errors_production", :white
|
|
605
|
+
# Every environment the app boots in needs the entry, test included:
|
|
606
|
+
# without it RED falls back to the main database and records nothing.
|
|
607
|
+
%w[development test production].each do |env|
|
|
608
|
+
say "\n #{env}:", :cyan
|
|
609
|
+
say " primary:", :white
|
|
610
|
+
say " <<: *default", :white
|
|
611
|
+
say " database: #{app_name_snake}_#{env}", :white
|
|
612
|
+
say " #{@database_name}:", :white
|
|
613
|
+
say " <<: *default", :white
|
|
614
|
+
if @enable_multi_app
|
|
615
|
+
shared_db_base = @shared_db_name || "shared_errors"
|
|
616
|
+
say " database: #{shared_db_base}_#{env}", :white
|
|
617
|
+
else
|
|
618
|
+
say " database: #{app_name_snake}_errors_#{env}", :white
|
|
619
|
+
end
|
|
620
|
+
say " migrations_paths: db/error_dashboard_migrate", :white
|
|
623
621
|
end
|
|
624
|
-
say " migrations_paths: db/error_dashboard_migrate", :white
|
|
625
622
|
say "\n"
|
|
626
623
|
|
|
627
624
|
if @enable_multi_app
|
|
@@ -186,7 +186,8 @@ RailsErrorDashboard.configure do |config|
|
|
|
186
186
|
<% if @enable_error_sampling -%>
|
|
187
187
|
# Error Sampling - ENABLED
|
|
188
188
|
# Samples non-critical errors to reduce storage volume.
|
|
189
|
-
# Critical
|
|
189
|
+
# Critical errors are always logged, regardless of this setting. So is the first
|
|
190
|
+
# occurrence of each error in each process.
|
|
190
191
|
# 0.5 = log 50% of non-critical occurrences — halves storage while keeping
|
|
191
192
|
# occurrence counts meaningful and error patterns visible.
|
|
192
193
|
# Tune lower (e.g. 0.1) if one error is flooding the DB; set to 1.0 to log everything.
|
|
@@ -244,7 +245,7 @@ RailsErrorDashboard.configure do |config|
|
|
|
244
245
|
# Automatically detect when error rates exceed normal patterns
|
|
245
246
|
config.enable_baseline_alerts = true
|
|
246
247
|
config.baseline_alert_threshold_std_devs = 2.0 # Alert when > 2 std devs above baseline
|
|
247
|
-
config.baseline_alert_severities = [ :critical, :high ]
|
|
248
|
+
config.baseline_alert_severities = [ :critical, :high ] # Only errors of these severities alert
|
|
248
249
|
config.baseline_alert_cooldown_minutes = 120 # 2 hours between alerts
|
|
249
250
|
# To disable: Set config.enable_baseline_alerts = false
|
|
250
251
|
|
|
@@ -253,7 +254,7 @@ RailsErrorDashboard.configure do |config|
|
|
|
253
254
|
# To enable: Set config.enable_baseline_alerts = true
|
|
254
255
|
config.enable_baseline_alerts = false
|
|
255
256
|
# config.baseline_alert_threshold_std_devs = 2.0
|
|
256
|
-
# config.baseline_alert_severities = [ :critical, :high ]
|
|
257
|
+
# config.baseline_alert_severities = [ :critical, :high ] # Only errors of these severities alert
|
|
257
258
|
# config.baseline_alert_cooldown_minutes = 120
|
|
258
259
|
|
|
259
260
|
<% end -%>
|
|
@@ -236,13 +236,22 @@ module RailsErrorDashboard
|
|
|
236
236
|
say "Remaining manual steps:", :cyan
|
|
237
237
|
say "\n"
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
unless options[:keep_data]
|
|
240
|
+
say "1. Regenerate the schema file:", :yellow
|
|
241
|
+
say " Run: bin/rails db:schema:dump (otherwise db:schema:load recreates the tables)"
|
|
242
|
+
say "\n"
|
|
243
|
+
|
|
244
|
+
say "2. Separate database only: remove its entry from config/database.yml", :yellow
|
|
245
|
+
say "\n"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
say "#{options[:keep_data] ? 1 : 3}. Remove from Gemfile:", :yellow
|
|
240
249
|
say " Open: Gemfile"
|
|
241
250
|
say " Remove: gem 'rails_error_dashboard'"
|
|
242
251
|
say " Run: bundle install"
|
|
243
252
|
say "\n"
|
|
244
253
|
|
|
245
|
-
say "2. Restart your application:", :yellow
|
|
254
|
+
say "#{options[:keep_data] ? 2 : 4}. Restart your application:", :yellow
|
|
246
255
|
say " Run: rails restart"
|
|
247
256
|
say " Or: kill and restart your server process"
|
|
248
257
|
say "\n"
|
|
@@ -878,8 +878,11 @@ module RailsErrorDashboard
|
|
|
878
878
|
# Return if no anomaly detected
|
|
879
879
|
return unless anomaly[:anomaly]
|
|
880
880
|
|
|
881
|
-
#
|
|
882
|
-
|
|
881
|
+
# baseline_alert_severities lists ERROR severities (critical/high/
|
|
882
|
+
# medium/low, as validated), not anomaly levels. Comparing it with
|
|
883
|
+
# anomaly[:level] (elevated/high/critical) skipped every anomaly below
|
|
884
|
+
# threshold + 1 sigma, and :medium/:low never matched at all.
|
|
885
|
+
return unless config.baseline_alert_severities.include?(error_log.severity)
|
|
883
886
|
|
|
884
887
|
# Enqueue alert job (which will handle throttling)
|
|
885
888
|
BaselineAlertJob.perform_later(error_log.id, anomaly, ApplicationJob.enqueue_locale)
|
|
@@ -8,6 +8,14 @@ module RailsErrorDashboard
|
|
|
8
8
|
DEFAULT_DASHBOARD_USERNAME = "gandalf"
|
|
9
9
|
DEFAULT_DASHBOARD_PASSWORD = "youshallnotpass"
|
|
10
10
|
|
|
11
|
+
# Settings that hold credentials. #inspect masks them, so printing the
|
|
12
|
+
# configuration in a console (IRB shows results with pp, which uses
|
|
13
|
+
# #inspect) or pasting it into an issue doesn't leak them.
|
|
14
|
+
SECRET_ATTRIBUTES = %i[
|
|
15
|
+
dashboard_password slack_webhook_url discord_webhook_url pagerduty_integration_key
|
|
16
|
+
webhook_urls issue_tracker_token issue_webhook_secret llm_api_key
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
11
19
|
# Dashboard authentication (always required)
|
|
12
20
|
attr_accessor :dashboard_username
|
|
13
21
|
attr_accessor :dashboard_password
|
|
@@ -351,7 +359,7 @@ module RailsErrorDashboard
|
|
|
351
359
|
|
|
352
360
|
# Rate limiting defaults
|
|
353
361
|
@enable_rate_limiting = false # OFF by default (opt-in)
|
|
354
|
-
@rate_limit_per_minute =
|
|
362
|
+
@rate_limit_per_minute = 300 # Requests per minute per IP, per dashboard path
|
|
355
363
|
|
|
356
364
|
# Enhanced metrics defaults
|
|
357
365
|
@app_version = ENV["APP_VERSION"]
|
|
@@ -380,7 +388,7 @@ module RailsErrorDashboard
|
|
|
380
388
|
# Baseline alert defaults
|
|
381
389
|
@enable_baseline_alerts = false # OFF by default (opt-in)
|
|
382
390
|
@baseline_alert_threshold_std_devs = ENV.fetch("BASELINE_ALERT_THRESHOLD", "2.0").to_f
|
|
383
|
-
@baseline_alert_severities = [ :critical, :high ] # Alert
|
|
391
|
+
@baseline_alert_severities = [ :critical, :high ] # Alert only for errors of these severities
|
|
384
392
|
@baseline_alert_cooldown_minutes = ENV.fetch("BASELINE_ALERT_COOLDOWN", "120").to_i
|
|
385
393
|
|
|
386
394
|
# Source code integration defaults - OFF by default (opt-in)
|
|
@@ -522,6 +530,30 @@ module RailsErrorDashboard
|
|
|
522
530
|
initialize
|
|
523
531
|
end
|
|
524
532
|
|
|
533
|
+
# Whether config/database.yml has the entry config.database names for this
|
|
534
|
+
# environment. Without it the engine skips connects_to and RED uses the
|
|
535
|
+
# main database, where its tables usually don't exist, so nothing is
|
|
536
|
+
# recorded. The engine and error_dashboard:verify both ask this.
|
|
537
|
+
#
|
|
538
|
+
# @param env_name [String] the Rails environment
|
|
539
|
+
# @return [Boolean]
|
|
540
|
+
def separate_database_entry?(env_name = Rails.env)
|
|
541
|
+
name = (database || :error_dashboard).to_s
|
|
542
|
+
ActiveRecord::Base.configurations.configs_for(env_name: env_name.to_s).any? { |config| config.name == name }
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Like Object#inspect, with every credential in SECRET_ATTRIBUTES replaced
|
|
546
|
+
# by [FILTERED] when set. Unset ones still show nil or [], which is useful
|
|
547
|
+
# when checking why a channel stays off.
|
|
548
|
+
def inspect
|
|
549
|
+
attributes = instance_variables.map do |ivar|
|
|
550
|
+
value = instance_variable_get(ivar)
|
|
551
|
+
secret = SECRET_ATTRIBUTES.include?(ivar.to_s.delete_prefix("@").to_sym) && value.present?
|
|
552
|
+
"#{ivar}=#{secret ? '[FILTERED]' : value.inspect}"
|
|
553
|
+
end
|
|
554
|
+
"#<#{self.class.name} #{attributes.join(', ')}>"
|
|
555
|
+
end
|
|
556
|
+
|
|
525
557
|
# Validate configuration values
|
|
526
558
|
# Raises ConfigurationError if any validation fails
|
|
527
559
|
# Logs warnings for non-fatal issues (e.g., Ruby version incompatibilities)
|
|
@@ -11,10 +11,11 @@ module RailsErrorDashboard
|
|
|
11
11
|
|
|
12
12
|
# Guard: skip connects_to if the database config doesn't exist yet in database.yml.
|
|
13
13
|
# This happens during `rails generate` when the initializer was just created but
|
|
14
|
-
# the user hasn't added the database.yml entry yet.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
# the user hasn't added the database.yml entry yet. Logged at error level:
|
|
15
|
+
# RED then uses the main database, and unless that has RED's tables no
|
|
16
|
+
# error is recorded. error_dashboard:verify reports it as FAILED.
|
|
17
|
+
unless RailsErrorDashboard.configuration.separate_database_entry?
|
|
18
|
+
Rails.logger.error "[Rails Error Dashboard] Separate database '#{database_name}' is not configured in database.yml for the '#{Rails.env}' environment. Skipping connects_to: errors are not recorded until it is added. See https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/DATABASE_OPTIONS.md"
|
|
18
19
|
next
|
|
19
20
|
end
|
|
20
21
|
|
|
@@ -76,6 +77,11 @@ module RailsErrorDashboard
|
|
|
76
77
|
# configured: then it is never consulted.
|
|
77
78
|
RailsErrorDashboard.detected_git_sha if RailsErrorDashboard.configuration.git_sha.blank?
|
|
78
79
|
|
|
80
|
+
# One error-level line if Solid Queue runs RED's jobs with a
|
|
81
|
+
# config/queue.yml that never would (no dispatcher, no worker for RED's
|
|
82
|
+
# queues). Reads the file once; never raises. The dashboard shows a banner.
|
|
83
|
+
RailsErrorDashboard::Services::SolidQueueConfigCheck.log_current_problems
|
|
84
|
+
|
|
79
85
|
if RailsErrorDashboard.configuration.enable_error_subscriber
|
|
80
86
|
Rails.error.subscribe(RailsErrorDashboard::ErrorReporter.new)
|
|
81
87
|
end
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module RailsErrorDashboard
|
|
4
4
|
module Middleware
|
|
5
|
-
# Rate limiting middleware for Rails Error Dashboard routes
|
|
6
|
-
#
|
|
5
|
+
# Rate limiting middleware for Rails Error Dashboard routes: at most
|
|
6
|
+
# config.rate_limit_per_minute requests per IP per path, per minute.
|
|
7
7
|
class RateLimiter
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
DASHBOARD_LIMIT = { limit: 300, period: 60 }.freeze # 300 req/min
|
|
8
|
+
PERIOD = 60 # seconds
|
|
9
|
+
DEFAULT_LIMIT = 300 # requests per PERIOD, when rate_limit_per_minute isn't a positive number
|
|
11
10
|
|
|
12
11
|
def initialize(app)
|
|
13
12
|
@app = app
|
|
@@ -22,16 +21,14 @@ module RailsErrorDashboard
|
|
|
22
21
|
# Only apply rate limiting to error dashboard routes
|
|
23
22
|
return @app.call(env) unless error_dashboard_route?(request.path)
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
limit_config = find_limit_config(request.path)
|
|
27
|
-
return @app.call(env) unless limit_config
|
|
24
|
+
limit_config = { limit: configured_limit, period: PERIOD }
|
|
28
25
|
|
|
29
26
|
# Check rate limit
|
|
30
|
-
key = rate_limit_key(request)
|
|
27
|
+
key = rate_limit_key(request, limit_config)
|
|
31
28
|
current_count = @cache.read(key).to_i
|
|
32
29
|
|
|
33
30
|
if current_count >= limit_config[:limit]
|
|
34
|
-
return
|
|
31
|
+
return html_rate_limit_response(limit_config)
|
|
35
32
|
end
|
|
36
33
|
|
|
37
34
|
# Increment counter with expiration
|
|
@@ -46,6 +43,12 @@ module RailsErrorDashboard
|
|
|
46
43
|
RailsErrorDashboard.configuration.enable_rate_limiting
|
|
47
44
|
end
|
|
48
45
|
|
|
46
|
+
# nil passes validation, and a limit of 0 would refuse every request.
|
|
47
|
+
def configured_limit
|
|
48
|
+
limit = RailsErrorDashboard.configuration.rate_limit_per_minute.to_i
|
|
49
|
+
limit.positive? ? limit : DEFAULT_LIMIT
|
|
50
|
+
end
|
|
51
|
+
|
|
49
52
|
def engine_mount_path
|
|
50
53
|
@engine_mount_path ||= RailsErrorDashboard.configuration.engine_mount_path
|
|
51
54
|
rescue
|
|
@@ -56,45 +59,14 @@ module RailsErrorDashboard
|
|
|
56
59
|
path.start_with?(engine_mount_path)
|
|
57
60
|
end
|
|
58
61
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
API_LIMIT
|
|
62
|
-
else
|
|
63
|
-
DASHBOARD_LIMIT
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def rate_limit_key(request)
|
|
68
|
-
# Key format: rate_limit:IP:path_prefix:time_window
|
|
62
|
+
def rate_limit_key(request, limit_config)
|
|
63
|
+
# Key format: rate_limit:IP:path:time_window
|
|
69
64
|
# Time window ensures keys expire and reset
|
|
70
|
-
limit_config = find_limit_config(request.path)
|
|
71
65
|
time_window = Time.now.to_i / limit_config[:period]
|
|
72
66
|
|
|
73
67
|
"rate_limit:#{request.ip}:#{request.path}:#{time_window}"
|
|
74
68
|
end
|
|
75
69
|
|
|
76
|
-
def rate_limit_response(request, limit_config)
|
|
77
|
-
# Return JSON for API requests, HTML for dashboard
|
|
78
|
-
if request.path.start_with?("#{engine_mount_path}/api")
|
|
79
|
-
json_rate_limit_response(limit_config)
|
|
80
|
-
else
|
|
81
|
-
html_rate_limit_response(limit_config)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def json_rate_limit_response(limit_config)
|
|
86
|
-
[
|
|
87
|
-
429,
|
|
88
|
-
{
|
|
89
|
-
"Content-Type" => "application/json",
|
|
90
|
-
"Retry-After" => limit_config[:period].to_s,
|
|
91
|
-
"X-RateLimit-Limit" => limit_config[:limit].to_s,
|
|
92
|
-
"X-RateLimit-Period" => "#{limit_config[:period]} seconds"
|
|
93
|
-
},
|
|
94
|
-
[ { error: "Rate limit exceeded. Please try again later." }.to_json ]
|
|
95
|
-
]
|
|
96
|
-
end
|
|
97
|
-
|
|
98
70
|
def html_rate_limit_response(limit_config)
|
|
99
71
|
body = <<~HTML
|
|
100
72
|
<!DOCTYPE html>
|
|
@@ -54,12 +54,42 @@ module RailsErrorDashboard
|
|
|
54
54
|
# Override in subclass to handle event
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# Called when a resolved error occurs again and is reopened
|
|
58
|
+
# @param error_log [ErrorLog] The reopened error log
|
|
59
|
+
def on_error_reopened(error_log)
|
|
60
|
+
# Override in subclass to handle event
|
|
61
|
+
end
|
|
62
|
+
|
|
57
63
|
# Called when an error is resolved
|
|
58
64
|
# @param error_log [ErrorLog] The resolved error log
|
|
59
65
|
def on_error_resolved(error_log)
|
|
60
66
|
# Override in subclass to handle event
|
|
61
67
|
end
|
|
62
68
|
|
|
69
|
+
# Called when an error is muted
|
|
70
|
+
# @param error_log [ErrorLog] The muted error log
|
|
71
|
+
def on_error_muted(error_log)
|
|
72
|
+
# Override in subclass to handle event
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Called when an error is unmuted
|
|
76
|
+
# @param error_log [ErrorLog] The unmuted error log
|
|
77
|
+
def on_error_unmuted(error_log)
|
|
78
|
+
# Override in subclass to handle event
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Called when errors are batch muted
|
|
82
|
+
# @param error_logs [Array<ErrorLog>] The muted error logs
|
|
83
|
+
def on_errors_batch_muted(error_logs)
|
|
84
|
+
# Override in subclass to handle event
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Called when errors are batch unmuted
|
|
88
|
+
# @param error_logs [Array<ErrorLog>] The unmuted error logs
|
|
89
|
+
def on_errors_batch_unmuted(error_logs)
|
|
90
|
+
# Override in subclass to handle event
|
|
91
|
+
end
|
|
92
|
+
|
|
63
93
|
# Called when errors are batch resolved
|
|
64
94
|
# @param error_logs [Array<ErrorLog>] The resolved error logs
|
|
65
95
|
def on_errors_batch_resolved(error_logs)
|
|
@@ -150,8 +150,10 @@ module RailsErrorDashboard
|
|
|
150
150
|
end
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
+
# RED's own connection: with a separate error database, the main
|
|
154
|
+
# database can use a different adapter.
|
|
153
155
|
def postgresql?
|
|
154
|
-
|
|
156
|
+
ErrorLogsRecord.connection.adapter_name.downcase == "postgresql"
|
|
155
157
|
end
|
|
156
158
|
|
|
157
159
|
def filter_by_severity(query)
|
|
@@ -28,7 +28,8 @@ module RailsErrorDashboard
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# Check if exception is in the ignored exceptions list
|
|
31
|
-
# Supports
|
|
31
|
+
# Supports class-name strings and Class/Module objects (both match
|
|
32
|
+
# subclasses), and regex patterns (matched against the class name)
|
|
32
33
|
# @param exception [Exception] The exception to check
|
|
33
34
|
# @return [Boolean] true if the exception should be ignored
|
|
34
35
|
def self.ignored?(exception)
|
|
@@ -41,6 +42,8 @@ module RailsErrorDashboard
|
|
|
41
42
|
case ignored
|
|
42
43
|
when String
|
|
43
44
|
exception.is_a?(ignored.constantize)
|
|
45
|
+
when Module
|
|
46
|
+
exception.is_a?(ignored)
|
|
44
47
|
when Regexp
|
|
45
48
|
exception_class_name.match?(ignored)
|
|
46
49
|
else
|
|
@@ -57,6 +57,53 @@ module RailsErrorDashboard
|
|
|
57
57
|
.sort
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# The running app's verdict: the problems that would stop RED's jobs in
|
|
61
|
+
# this environment, or [] when Solid Queue doesn't run them or there is
|
|
62
|
+
# no config file. Worked out once per process (it reads and parses a
|
|
63
|
+
# file); the dashboard banner and the boot log line both ask. Never raises.
|
|
64
|
+
#
|
|
65
|
+
# @return [Array<String>]
|
|
66
|
+
def self.current_problems
|
|
67
|
+
return @current_problems if defined?(@current_problems)
|
|
68
|
+
|
|
69
|
+
@current_problems = compute_current_problems.freeze
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# One error-level line at boot when there is a problem.
|
|
73
|
+
#
|
|
74
|
+
# @param logger [Logger]
|
|
75
|
+
# @return [void]
|
|
76
|
+
def self.log_current_problems(logger = Rails.logger)
|
|
77
|
+
problems = current_problems
|
|
78
|
+
return if problems.empty?
|
|
79
|
+
|
|
80
|
+
logger.error(
|
|
81
|
+
"[Rails Error Dashboard] Solid Queue won't run RED's jobs in #{problems.join('; ')}. " \
|
|
82
|
+
"Errors captured in the background and notifications wait in the queue until " \
|
|
83
|
+
"config/queue.yml is fixed: bin/rails error_dashboard:verify shows how. Guide: #{GUIDE_URL}"
|
|
84
|
+
)
|
|
85
|
+
rescue StandardError
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# For specs.
|
|
90
|
+
def self.reset_current_problems!
|
|
91
|
+
remove_instance_variable(:@current_problems) if defined?(@current_problems)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.compute_current_problems
|
|
95
|
+
return [] unless RailsErrorDashboard::ApplicationJob.queue_adapter_name.to_s == "solid_queue"
|
|
96
|
+
|
|
97
|
+
path = config_path(Rails.root)
|
|
98
|
+
return [] unless path.exist?
|
|
99
|
+
|
|
100
|
+
result = call(path, environments: [ Rails.env.to_s ])
|
|
101
|
+
result[:skipped] ? [] : result[:problems]
|
|
102
|
+
rescue StandardError, ScriptError
|
|
103
|
+
[]
|
|
104
|
+
end
|
|
105
|
+
private_class_method :compute_current_problems
|
|
106
|
+
|
|
60
107
|
# The queues RED's jobs are enqueued on, as this app names them
|
|
61
108
|
# (queue_name_prefix included).
|
|
62
109
|
#
|
|
@@ -34,6 +34,25 @@ namespace :error_dashboard do
|
|
|
34
34
|
end
|
|
35
35
|
checks_passed += 1
|
|
36
36
|
|
|
37
|
+
# 2b. Separate database: the database.yml entry for this environment. Without
|
|
38
|
+
# it the engine skips connects_to and RED uses the main database, which
|
|
39
|
+
# usually has no RED tables, so nothing is recorded.
|
|
40
|
+
if config.use_separate_database
|
|
41
|
+
print " Error database entry... "
|
|
42
|
+
db_name = config.database || :error_dashboard
|
|
43
|
+
if config.separate_database_entry?
|
|
44
|
+
puts "OK"
|
|
45
|
+
checks_passed += 1
|
|
46
|
+
else
|
|
47
|
+
puts "FAILED"
|
|
48
|
+
puts " config/database.yml has no '#{db_name}' entry for the '#{Rails.env}' environment."
|
|
49
|
+
puts " RED is using the main database instead, so errors are not recorded in the error database."
|
|
50
|
+
puts " Add '#{db_name}:' under '#{Rails.env}:' with migrations_paths: db/error_dashboard_migrate"
|
|
51
|
+
puts " (see docs/guides/DATABASE_OPTIONS.md), then restart."
|
|
52
|
+
checks_failed += 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
37
56
|
# 3. Database connection
|
|
38
57
|
print " Database connection... "
|
|
39
58
|
begin
|
|
@@ -218,6 +237,28 @@ namespace :error_dashboard do
|
|
|
218
237
|
end
|
|
219
238
|
end
|
|
220
239
|
|
|
240
|
+
# 10. json 3.0 breaks Rails before 8.1.4 and 7.2.4: dashboard pages return
|
|
241
|
+
# 500 and captures can fail, with no error message. The bug is in Rails.
|
|
242
|
+
json_version = Gem.loaded_specs["json"]&.version
|
|
243
|
+
if json_version && json_version >= Gem::Version.new("3")
|
|
244
|
+
print " json gem... "
|
|
245
|
+
rails_version = Gem::Version.new(Rails.version)
|
|
246
|
+
series = rails_version.segments.first(2)
|
|
247
|
+
json3_ok = (series == [ 8, 1 ] && rails_version >= Gem::Version.new("8.1.4")) ||
|
|
248
|
+
(series == [ 7, 2 ] && rails_version >= Gem::Version.new("7.2.4")) ||
|
|
249
|
+
(series <=> [ 8, 1 ]) == 1
|
|
250
|
+
if json3_ok
|
|
251
|
+
puts "OK (json #{json_version}, Rails #{Rails.version})"
|
|
252
|
+
checks_passed += 1
|
|
253
|
+
else
|
|
254
|
+
puts "WARNING"
|
|
255
|
+
puts " json #{json_version} breaks Rails #{Rails.version} (fixed in Rails 8.1.4 and 7.2.4):"
|
|
256
|
+
puts " dashboard pages return 500 and errors can fail to be captured."
|
|
257
|
+
puts " Add gem \"json\", \"< 3\" to your Gemfile and run bundle update json, or upgrade Rails."
|
|
258
|
+
warnings += 1
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
221
262
|
# Summary
|
|
222
263
|
puts "\n" + "-" * 70
|
|
223
264
|
puts " Results: #{checks_passed} passed, #{checks_failed} failed, #{warnings} warnings"
|
|
@@ -324,7 +365,7 @@ namespace :error_dashboard do
|
|
|
324
365
|
|
|
325
366
|
# Confirm before proceeding
|
|
326
367
|
print "\nProceed with backfill? (y/N): "
|
|
327
|
-
confirmation = $stdin.gets.chomp.downcase
|
|
368
|
+
confirmation = $stdin.gets.to_s.chomp.downcase
|
|
328
369
|
|
|
329
370
|
unless confirmation == "y" || confirmation == "yes"
|
|
330
371
|
puts "\n✗ Backfill cancelled"
|
|
@@ -569,7 +610,7 @@ namespace :error_dashboard do
|
|
|
569
610
|
end
|
|
570
611
|
|
|
571
612
|
print "\n Proceed with deletion? (y/N): "
|
|
572
|
-
confirmation = $stdin.gets.chomp.downcase
|
|
613
|
+
confirmation = $stdin.gets.to_s.chomp.downcase
|
|
573
614
|
|
|
574
615
|
unless confirmation == "y" || confirmation == "yes"
|
|
575
616
|
puts "\n Cleanup cancelled"
|
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.14.
|
|
4
|
+
version: 0.14.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -601,7 +601,7 @@ metadata:
|
|
|
601
601
|
funding_uri: https://github.com/sponsors/AnjanJ
|
|
602
602
|
post_install_message: |
|
|
603
603
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
604
|
-
RED (Rails Error Dashboard) v0.14.
|
|
604
|
+
RED (Rails Error Dashboard) v0.14.4
|
|
605
605
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
606
606
|
|
|
607
607
|
First install:
|