rails_error_dashboard 0.14.2 → 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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -18
  3. data/app/helpers/rails_error_dashboard/application_helper.rb +8 -0
  4. data/app/jobs/rails_error_dashboard/retention_cleanup_job.rb +27 -23
  5. data/app/views/layouts/rails_error_dashboard.html.erb +13 -0
  6. data/config/locales/de.yml +4 -0
  7. data/config/locales/en.yml +5 -0
  8. data/config/locales/es.yml +4 -0
  9. data/config/locales/fr.yml +4 -0
  10. data/config/locales/it.yml +4 -0
  11. data/config/locales/ja.yml +4 -0
  12. data/config/locales/pl.yml +4 -0
  13. data/config/locales/pt-BR.yml +4 -0
  14. data/config/locales/ru.yml +4 -0
  15. data/config/locales/uk.yml +4 -0
  16. data/config/locales/zh-CN.yml +4 -0
  17. data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +9 -0
  18. data/lib/generators/rails_error_dashboard/install/install_generator.rb +45 -50
  19. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +17 -10
  20. data/lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb +41 -21
  21. data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +79 -66
  22. data/lib/rails_error_dashboard/commands/drop_all_tables.rb +77 -0
  23. data/lib/rails_error_dashboard/commands/log_error.rb +5 -2
  24. data/lib/rails_error_dashboard/configuration.rb +67 -4
  25. data/lib/rails_error_dashboard/engine.rb +10 -4
  26. data/lib/rails_error_dashboard/logger.rb +11 -3
  27. data/lib/rails_error_dashboard/middleware/rate_limiter.rb +15 -43
  28. data/lib/rails_error_dashboard/plugin.rb +30 -0
  29. data/lib/rails_error_dashboard/queries/errors_list.rb +3 -1
  30. data/lib/rails_error_dashboard/queries/uninstall_plan.rb +63 -0
  31. data/lib/rails_error_dashboard/services/exception_filter.rb +4 -1
  32. data/lib/rails_error_dashboard/services/solid_queue_config_check.rb +261 -0
  33. data/lib/rails_error_dashboard/version.rb +1 -1
  34. data/lib/rails_error_dashboard.rb +3 -0
  35. data/lib/tasks/error_dashboard.rake +77 -5
  36. data/lib/tasks/rails_error_dashboard_tasks.rake +37 -43
  37. metadata +5 -3
  38. data/lib/generators/rails_error_dashboard/solid_queue/templates/queue.yml +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93c0e9e0f21298eb85db1d661793709d60158c5f2c6f85b164da36b4593525ee
4
- data.tar.gz: 37bae1ddaca2d502e434e0b4e6e95f6c755619598dec20f08495ce13fd0c286a
3
+ metadata.gz: bd115b4f0fa37f0b90cc22ceea5d5f8db5bdb59ceb40972c3d9ff70c6055a643
4
+ data.tar.gz: b1339930832c6e108d0e55aae93ad2cf14ed1935fa5af23bee24b9f9f4d8943d
5
5
  SHA512:
6
- metadata.gz: 9c6be20b1be293c069516ed0f5f6fc19e46203224bb9aaad6be1c645f45ebb3455e03e6b3fef8d8738e4cc658dc928cd56c9f2d403d270ec6caaaa74411074b7
7
- data.tar.gz: 7c1c344be4398b47faf98de6a9be675f41a86878b49404d76b73d1a5be91f046ded677c70ebb9c1f4118e8736873ea491edcfed07b6520465049b9ecb3e1066b
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` and raise a test exception. No monitoring account or ingestion service is required.
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 Sidekiq or Solid Queue (GoodJob is detected for job-health stats but is not an async adapter).
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 guides you through optional feature selection — notifications, performance optimizations, advanced analytics. All features are opt-in.
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
 
@@ -635,37 +642,46 @@ The installer guides you through optional feature selection — notifications, p
635
642
  http://localhost:3000/red
636
643
  ```
637
644
 
638
- Default credentials: `gandalf` / `youshallnotpass`
645
+ Default credentials: `gandalf` / `youshallnotpass`, for development and test only.
639
646
 
640
- **Change these before production!** Edit `config/initializers/rails_error_dashboard.rb`
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
- # In Rails console or any controller
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
654
674
 
655
675
  ```ruby
656
676
  RailsErrorDashboard.configure do |config|
657
- # Authentication
658
- config.dashboard_username = ENV.fetch('ERROR_DASHBOARD_USER', 'gandalf')
659
- config.dashboard_password = ENV.fetch('ERROR_DASHBOARD_PASSWORD', 'youshallnotpass')
660
-
661
- # Or use your existing auth (Devise, Warden, etc.):
677
+ # Dashboard login: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD outside
678
+ # development and test (the gem reads them itself). Or use your existing auth:
662
679
  # config.authenticate_with = -> { warden.authenticated? }
663
680
 
664
681
  # Optional features — enable as needed
665
682
  config.enable_slack_notifications = true
666
683
  config.slack_webhook_url = ENV['SLACK_WEBHOOK_URL']
667
- config.async_logging = true
668
- 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
669
685
  end
670
686
  ```
671
687
 
@@ -673,7 +689,7 @@ end
673
689
 
674
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)
675
691
 
676
- **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](#opentelemetry-export--emit-gem-operations-as-spans) above for full options.
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.
677
693
 
678
694
  ---
679
695
 
@@ -714,7 +730,7 @@ RED translates through its own private I18n backend, so it never reads, writes o
714
730
  ## FAQ
715
731
 
716
732
  **Does Rails Error Dashboard support a separate database for errors?**
717
- Yes. You can store errors in your app's existing database (shared) **or** in a dedicated, isolated database (separate). Set `config.use_separate_database = true` (or `USE_SEPARATE_ERROR_DB=true`) and point it at a separate connection — the engine routes all of its tables through `connects_to`, keeping error data fully isolated from your app data. Both modes are first-class and covered by the [Database Options guide](docs/guides/DATABASE_OPTIONS.md).
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).
718
734
 
719
735
  **Which databases does it work with?**
720
736
  SQLite, PostgreSQL, and MySQL/Trilogy — in either shared or separate-database mode.
@@ -729,13 +745,13 @@ Yes — local **and** instance variables at the moment the exception is raised,
729
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.
730
746
 
731
747
  **Does it work with my background jobs?**
732
- Yes — errors raised in jobs are captured, and it can log errors asynchronously through Sidekiq or SolidQueue (or the in-process `:async` adapter). Sidekiq, SolidQueue and GoodJob are all auto-detected for the job-queue stats stored on each error.
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.
733
749
 
734
750
  **Does it work with my authentication?**
735
751
  Yes — HTTP Basic Auth out of the box, or a custom `authenticate_with` lambda that integrates with Devise, Warden, or session-based auth.
736
752
 
737
753
  **Can it track more than one app?**
738
- Yes — multi-app support tracks errors from multiple Rails apps in one dashboard with per-app filtering.
754
+ Yes — apps that point at the same error database share one dashboard, with per-app filtering.
739
755
 
740
756
  **What Rails and Ruby versions are supported?**
741
757
  Rails 7.0–8.1 and Ruby 3.2–4.0.
@@ -747,6 +763,8 @@ Rails 7.0–8.1 and Ruby 3.2–4.0.
747
763
  ### Getting Started
748
764
  - **[Quickstart Guide](docs/QUICKSTART.md)** — 5-minute setup
749
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
750
768
  - **[Uninstalling](docs/UNINSTALL.md)** — Clean removal
751
769
 
752
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
@@ -33,6 +33,32 @@ module RailsErrorDashboard
33
33
  )
34
34
  end
35
35
 
36
+ # Deletes error logs and everything that references them: dependents first,
37
+ # all in batches. Occurrences, comments and cascade patterns hold foreign
38
+ # keys to the log, so deleting the logs first fails, and one unbounded
39
+ # DELETE would lock the table. Public because error_dashboard:cleanup_resolved
40
+ # deletes the same way.
41
+ #
42
+ # @param scope [ActiveRecord::Relation] the ErrorLog rows to delete
43
+ # @return [Integer] number of error logs deleted
44
+ def self.delete_with_dependents(scope)
45
+ ids = scope.select(:id)
46
+
47
+ ErrorOccurrence.where(error_log_id: ids).in_batches(of: 1000).delete_all
48
+ # Hour buckets for storm-shed events. Listed explicitly because delete_all
49
+ # does not fire the has_many :dependent callback on ErrorLog -- without
50
+ # this line the buckets outlive the group they describe, forever.
51
+ EventCount.where(error_log_id: ids).in_batches(of: 1000).delete_all if EventCount.table_exists?
52
+ ErrorComment.where(error_log_id: ids).in_batches(of: 1000).delete_all
53
+ CascadePattern.where(parent_error_id: ids)
54
+ .or(CascadePattern.where(child_error_id: ids))
55
+ .in_batches(of: 1000).delete_all
56
+
57
+ deleted = 0
58
+ scope.in_batches(of: 1000) { |batch| deleted += batch.delete_all }
59
+ deleted
60
+ end
61
+
36
62
  # @return [Integer] number of errors deleted
37
63
  def perform
38
64
  retention_days = RailsErrorDashboard.configuration.retention_days
@@ -52,29 +78,7 @@ module RailsErrorDashboard
52
78
  expired_scope = self.class.expired_scope(cutoff)
53
79
  return 0 if expired_scope.none?
54
80
 
55
- deleted_count = 0
56
-
57
- # Delete dependents first, then errors — all in batches to prevent table locks
58
- expired_ids_scope = expired_scope.select(:id)
59
-
60
- # Batch delete dependent records (occurrences, comments, cascade patterns)
61
- ErrorOccurrence.where(error_log_id: expired_ids_scope).in_batches(of: 1000).delete_all
62
- # Hour buckets for storm-shed events. Listed explicitly because this job
63
- # deletes with delete_all, which does not fire the has_many :dependent
64
- # callback on ErrorLog -- without this line the buckets outlive the group
65
- # they describe, forever. The migration's own comment promised this
66
- # pruning before the code existed.
67
- EventCount.where(error_log_id: expired_ids_scope).in_batches(of: 1000).delete_all if EventCount.table_exists?
68
- ErrorComment.where(error_log_id: expired_ids_scope).in_batches(of: 1000).delete_all
69
- CascadePattern.where(parent_error_id: expired_ids_scope)
70
- .or(CascadePattern.where(child_error_id: expired_ids_scope))
71
- .in_batches(of: 1000).delete_all
72
-
73
- # Now batch delete the error logs themselves
74
- expired_scope.in_batches(of: 1000) do |batch|
75
- batch_size = batch.delete_all
76
- deleted_count += batch_size
77
- end
81
+ deleted_count = self.class.delete_with_dependents(expired_scope)
78
82
 
79
83
  # delete_all skips callbacks, and the stat cards are cached.
80
84
  Services::AnalyticsCacheManager.clear if deleted_count > 0
@@ -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)' %>;">
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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: ストーム保護が最近作動しました
@@ -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
@@ -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
@@ -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: Недавно сработала защита от шторма
@@ -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: Нещодавно спрацював захист від штормів
@@ -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',
@@ -15,7 +15,7 @@ module RailsErrorDashboard
15
15
  class_option :pagerduty, type: :boolean, default: false, desc: "Enable PagerDuty notifications"
16
16
  class_option :webhooks, type: :boolean, default: false, desc: "Enable webhook notifications"
17
17
  # Performance options
18
- class_option :async_logging, type: :boolean, default: true, desc: "Enable async error logging (default: true, uses Rails :async adapter — no extra infrastructure needed)"
18
+ class_option :async_logging, type: :boolean, default: true, desc: "Enable async error logging (default: true; jobs run on your app's Active Job adapter, so production needs a worker)"
19
19
  class_option :error_sampling, type: :boolean, default: false, desc: "Enable error sampling (reduce volume)"
20
20
  class_option :separate_database, type: :boolean, default: false, desc: "Use separate database for errors"
21
21
  class_option :database, type: :string, default: nil, desc: "Database name to use for errors (e.g., 'error_dashboard')"
@@ -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, notifications OFF, breadcrumbs OFF)", :green
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
- any_advanced_cli_flag = advanced_keys.any? { |k| options[k] }
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
- r = ask(" Enable? (y/N):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
173
- @selected_features[f[:key]] = r.downcase == "y"
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
- @enable_async_logging = @selected_features&.dig(:async_logging) || options[:async_logging]
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)
@@ -383,7 +390,7 @@ module RailsErrorDashboard
383
390
  return
384
391
  end
385
392
 
386
- route "mount RailsErrorDashboard::Engine => '/red' # RED (Rails Error Dashboard) — also works at /error_dashboard"
393
+ route "mount RailsErrorDashboard::Engine => '/red' # RED (Rails Error Dashboard)"
387
394
  end
388
395
 
389
396
  def show_feature_summary
@@ -400,7 +407,7 @@ module RailsErrorDashboard
400
407
  say " ✓ Dashboard UI", :green
401
408
  say " ✓ Real-time Updates", :green
402
409
  say " ✓ Analytics", :green
403
- say " ✓ Async Logging (Rails :async adapter — no extra infrastructure needed)", :green
410
+ say " ✓ Async Logging (runs on your app's Active Job adapter)", :green
404
411
 
405
412
  # Count optional features enabled
406
413
  enabled_count = 0
@@ -477,14 +484,11 @@ module RailsErrorDashboard
477
484
  say " → Set PAGERDUTY_INTEGRATION_KEY in .env", :yellow if @enable_pagerduty
478
485
  say " → Set WEBHOOK_URLS in .env", :yellow if @enable_webhooks
479
486
  if @enable_async_logging
480
- # Check which async adapter is in use — only warn about external workers when needed
481
- async_section = File.exist?(File.join(destination_root, "config/initializers/rails_error_dashboard.rb")) &&
482
- File.read(File.join(destination_root, "config/initializers/rails_error_dashboard.rb"))
483
- if async_section && (async_section.include?("async_adapter = :sidekiq") || async_section.include?("async_adapter = :solid_queue"))
484
- say " → Ensure your background worker (Sidekiq/SolidQueue) is running for async logging", :yellow
485
- else
486
- say " ✓ Async logging uses Rails :async adapter — no extra process needed", :green
487
- end
487
+ # RED's jobs run on the app's own Active Job adapter; async_adapter only
488
+ # feeds validation and the Settings page. Rails 8 production uses Solid
489
+ # Queue, which needs a worker, so don't promise that none is needed.
490
+ say " → Async logging enqueues on your app's config.active_job.queue_adapter.", :yellow
491
+ say " Run a worker for the default and error_notifications queues, or set config.async_logging = false.", :yellow
488
492
  end
489
493
 
490
494
  # Database-specific instructions
@@ -496,28 +500,29 @@ module RailsErrorDashboard
496
500
  say "Next Steps:", :cyan
497
501
  if @enable_separate_database
498
502
  say " 1. Add the database.yml entry shown above"
499
- say " 2. Run: rails db:create:error_dashboard"
503
+ say " 2. Run: rails db:create:#{@database_name}"
500
504
  if @enable_multi_app
501
505
  say " 3. Run migrations (only needed on the FIRST app):"
502
- say " rails db:migrate:error_dashboard"
506
+ say " rails db:migrate:#{@database_name}"
503
507
  else
504
- say " 3. Run: rails db:migrate:error_dashboard"
508
+ say " 3. Run: rails db:migrate:#{@database_name}"
505
509
  end
506
- say " 4. Update credentials in config/initializers/rails_error_dashboard.rb"
510
+ say " 4. Before deploying: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD there"
507
511
  say " 5. Restart your Rails server"
508
512
  say " 6. Visit http://localhost:3000/red"
509
513
  say " 7. Verify: rails error_dashboard:verify"
510
514
  else
511
515
  say " 1. Run: rails db:migrate"
512
- say " 2. Update credentials in config/initializers/rails_error_dashboard.rb"
516
+ say " 2. Before deploying: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD there"
513
517
  say " 3. Restart your Rails server"
514
518
  say " 4. Visit http://localhost:3000/red"
515
519
  end
516
520
  say "Authentication:", :cyan
517
- say " Default: HTTP Basic Auth (gandalf/youshallnotpass)", :white
521
+ say " Default: HTTP Basic Auth, gandalf/youshallnotpass in development and test only", :white
522
+ say " Elsewhere: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD, or the app won't boot", :white
518
523
  say " Devise/Warden: config.authenticate_with = -> { warden.authenticated? }", :white
519
524
  say " Session-based: config.authenticate_with = -> { session[:admin] == true }", :white
520
- say " See: https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/CONFIGURATION.md#custom-authentication", :white
525
+ say " See: https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/CONFIGURATION.md#dashboard-credentials", :white
521
526
  say "\n"
522
527
  say "Issue Tracking (optional):", :cyan
523
528
  say " Create a dedicated RED (Rails Error Dashboard) bot account on your platform:", :white
@@ -549,7 +554,7 @@ module RailsErrorDashboard
549
554
 
550
555
  def build_quick_defaults
551
556
  {
552
- # Async ON (built-in Rails :async adapter, zero infrastructure)
557
+ # Async ON (runs on the app's Active Job adapter)
553
558
  async_logging: true,
554
559
  # All analytics ON — run at query/background time, zero request-path overhead
555
560
  baseline_alerts: true,
@@ -597,33 +602,23 @@ module RailsErrorDashboard
597
602
  say " # Separate error database", :white
598
603
  end
599
604
 
600
- say "\n development:", :cyan
601
- say " primary:", :white
602
- say " <<: *default", :white
603
- say " database: #{app_name_snake}_development", :white
604
- say " error_dashboard:", :white
605
- say " <<: *default", :white
606
- if @enable_multi_app
607
- shared_db_base = @shared_db_name || "shared_errors"
608
- say " database: #{shared_db_base}_development", :white
609
- else
610
- say " database: #{app_name_snake}_errors_development", :white
611
- end
612
- say " migrations_paths: db/error_dashboard_migrate", :white
613
-
614
- say "\n production:", :cyan
615
- say " primary:", :white
616
- say " <<: *default", :white
617
- say " database: #{app_name_snake}_production", :white
618
- say " error_dashboard:", :white
619
- say " <<: *default", :white
620
- if @enable_multi_app
621
- shared_db_base = @shared_db_name || "shared_errors"
622
- say " database: #{shared_db_base}_production", :white
623
- else
624
- 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
625
621
  end
626
- say " migrations_paths: db/error_dashboard_migrate", :white
627
622
  say "\n"
628
623
 
629
624
  if @enable_multi_app