rails_error_dashboard 0.14.1 → 0.14.3
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 +4 -7
- data/app/controllers/rails_error_dashboard/application_controller.rb +9 -0
- data/app/jobs/rails_error_dashboard/retention_cleanup_job.rb +27 -23
- data/config/locales/ja.yml +5 -5
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +14 -16
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +13 -7
- data/lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb +41 -21
- data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +68 -64
- data/lib/rails_error_dashboard/commands/drop_all_tables.rb +77 -0
- data/lib/rails_error_dashboard/configuration.rb +104 -21
- data/lib/rails_error_dashboard/logger.rb +11 -3
- data/lib/rails_error_dashboard/queries/uninstall_plan.rb +63 -0
- data/lib/rails_error_dashboard/services/solid_queue_config_check.rb +214 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +3 -0
- data/lib/tasks/error_dashboard.rake +42 -6
- data/lib/tasks/rails_error_dashboard_tasks.rake +37 -43
- metadata +5 -3
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f96e195659bca5562fcf7c4ed02c1f8f69c058daec06f6902523958f5746a8a4
|
|
4
|
+
data.tar.gz: 2ea940fe412652bef9e0f47eb818d4da93a6d7b7cb1e32cd5820e96cf032fafe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7b8a36c5ffde9bcd71a0649a0d2df111345625717ec67a997e4bd4929215700408efd95844b41506e3a48aa47fc7179cd1df140aa1274451a1a8760a5f32db7
|
|
7
|
+
data.tar.gz: d6b3722433ce12451ef44242874dfe98620441832626368d0e8ce9d852396d8e4b00b5cc5773f04a348d48f64254fc763cd4fae4a1580d5b399cb4aab21f3de3
|
data/README.md
CHANGED
|
@@ -635,9 +635,9 @@ The installer guides you through optional feature selection — notifications, p
|
|
|
635
635
|
http://localhost:3000/red
|
|
636
636
|
```
|
|
637
637
|
|
|
638
|
-
Default credentials: `gandalf` / `youshallnotpass
|
|
638
|
+
Default credentials: `gandalf` / `youshallnotpass`, for development and test only.
|
|
639
639
|
|
|
640
|
-
**
|
|
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).
|
|
641
641
|
|
|
642
642
|
### 4. Test it out
|
|
643
643
|
|
|
@@ -654,11 +654,8 @@ raise "Test error from Rails Error Dashboard!"
|
|
|
654
654
|
|
|
655
655
|
```ruby
|
|
656
656
|
RailsErrorDashboard.configure do |config|
|
|
657
|
-
#
|
|
658
|
-
|
|
659
|
-
config.dashboard_password = ENV.fetch('ERROR_DASHBOARD_PASSWORD', 'youshallnotpass')
|
|
660
|
-
|
|
661
|
-
# Or use your existing auth (Devise, Warden, etc.):
|
|
657
|
+
# Dashboard login: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD outside
|
|
658
|
+
# development and test (the gem reads them itself). Or use your existing auth:
|
|
662
659
|
# config.authenticate_with = -> { warden.authenticated? }
|
|
663
660
|
|
|
664
661
|
# Optional features — enable as needed
|
|
@@ -314,6 +314,15 @@ module RailsErrorDashboard
|
|
|
314
314
|
expected_password = RailsErrorDashboard.configuration.dashboard_password
|
|
315
315
|
next false if expected_username.nil? || expected_password.nil?
|
|
316
316
|
|
|
317
|
+
# A blank credential must deny everyone too: "" would equal an empty login.
|
|
318
|
+
next false if RailsErrorDashboard::Configuration.blank_credential?(expected_username) ||
|
|
319
|
+
RailsErrorDashboard::Configuration.blank_credential?(expected_password)
|
|
320
|
+
|
|
321
|
+
# The boot check refuses the published credentials outside development
|
|
322
|
+
# and test, but it is skipped while SECRET_KEY_BASE_DUMMY is set. Refuse
|
|
323
|
+
# them here as well, so that variable left set at runtime reopens nothing.
|
|
324
|
+
next false if RailsErrorDashboard.configuration.refuse_default_credentials?
|
|
325
|
+
|
|
317
326
|
# Non-short-circuit & on purpose: both comparisons always run, so the
|
|
318
327
|
# response time does not reveal which half was wrong.
|
|
319
328
|
ActiveSupport::SecurityUtils.secure_compare(username.to_s, expected_username.to_s) &
|
|
@@ -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 =
|
|
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
|
data/config/locales/ja.yml
CHANGED
|
@@ -23,7 +23,7 @@ ja:
|
|
|
23
23
|
'30': 30日間
|
|
24
24
|
'90': 90日間
|
|
25
25
|
frame_category:
|
|
26
|
-
app:
|
|
26
|
+
app: アプリケーションコード
|
|
27
27
|
gem: Gem
|
|
28
28
|
framework: Rails フレームワーク
|
|
29
29
|
ruby_core: Ruby コア
|
|
@@ -520,7 +520,7 @@ ja:
|
|
|
520
520
|
user_agent: 'User Agent:'
|
|
521
521
|
ip_address: 'IP アドレス:'
|
|
522
522
|
replay: 'リクエストの再現:'
|
|
523
|
-
raw_user_agent: User Agent
|
|
523
|
+
raw_user_agent: User Agent 文字列
|
|
524
524
|
duration_seconds: "%{value}秒"
|
|
525
525
|
duration_ms: "%{value}ミリ秒"
|
|
526
526
|
copy_curl: curl としてコピー
|
|
@@ -530,7 +530,7 @@ ja:
|
|
|
530
530
|
snapshot: 'スナップショット:'
|
|
531
531
|
snapshot_captured_html: '%{time} に取得'
|
|
532
532
|
snapshot_stale: より前の発生から取得 — それ以降の発生はコンテキストを伴いませんでした
|
|
533
|
-
snapshot_fidelity_lite:
|
|
533
|
+
snapshot_fidelity_lite: 限定的な情報取得(storm 保護によりコンテキストの payload を省略)
|
|
534
534
|
snapshot_fidelity_minimal: storm 中にカウントされました — コンテキストは取得されていません
|
|
535
535
|
snapshot_fidelity_partial: 混在したスナップショット — 一部のフィールドは以前の発生時のものです
|
|
536
536
|
snapshot_unknown: RED がスナップショットの出所を記録する前に取得されました
|
|
@@ -667,10 +667,10 @@ ja:
|
|
|
667
667
|
backtrace: Backtrace
|
|
668
668
|
backtrace:
|
|
669
669
|
label: 'Backtrace:'
|
|
670
|
-
frame_counts: "(
|
|
670
|
+
frame_counts: "(アプリケーションコード%{app}件、フレームワーク/gem %{framework}件)"
|
|
671
671
|
copy_full: Backtrace 全体をコピー
|
|
672
672
|
copy_full_title: Backtrace 全体をコピー
|
|
673
|
-
your_code:
|
|
673
|
+
your_code: アプリケーションコード
|
|
674
674
|
framework_code: フレームワーク・gem のコード
|
|
675
675
|
frames:
|
|
676
676
|
other: "%{count}フレーム"
|
|
@@ -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
|
|
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')"
|
|
@@ -383,7 +383,7 @@ module RailsErrorDashboard
|
|
|
383
383
|
return
|
|
384
384
|
end
|
|
385
385
|
|
|
386
|
-
route "mount RailsErrorDashboard::Engine => '/red' # RED (Rails Error Dashboard)
|
|
386
|
+
route "mount RailsErrorDashboard::Engine => '/red' # RED (Rails Error Dashboard)"
|
|
387
387
|
end
|
|
388
388
|
|
|
389
389
|
def show_feature_summary
|
|
@@ -400,7 +400,7 @@ module RailsErrorDashboard
|
|
|
400
400
|
say " ✓ Dashboard UI", :green
|
|
401
401
|
say " ✓ Real-time Updates", :green
|
|
402
402
|
say " ✓ Analytics", :green
|
|
403
|
-
say " ✓ Async Logging (
|
|
403
|
+
say " ✓ Async Logging (runs on your app's Active Job adapter)", :green
|
|
404
404
|
|
|
405
405
|
# Count optional features enabled
|
|
406
406
|
enabled_count = 0
|
|
@@ -477,14 +477,11 @@ module RailsErrorDashboard
|
|
|
477
477
|
say " → Set PAGERDUTY_INTEGRATION_KEY in .env", :yellow if @enable_pagerduty
|
|
478
478
|
say " → Set WEBHOOK_URLS in .env", :yellow if @enable_webhooks
|
|
479
479
|
if @enable_async_logging
|
|
480
|
-
#
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
else
|
|
486
|
-
say " ✓ Async logging uses Rails :async adapter — no extra process needed", :green
|
|
487
|
-
end
|
|
480
|
+
# RED's jobs run on the app's own Active Job adapter; async_adapter only
|
|
481
|
+
# feeds validation and the Settings page. Rails 8 production uses Solid
|
|
482
|
+
# Queue, which needs a worker, so don't promise that none is needed.
|
|
483
|
+
say " → Async logging enqueues on your app's config.active_job.queue_adapter.", :yellow
|
|
484
|
+
say " Run a worker for the default and error_notifications queues, or set config.async_logging = false.", :yellow
|
|
488
485
|
end
|
|
489
486
|
|
|
490
487
|
# Database-specific instructions
|
|
@@ -503,21 +500,22 @@ module RailsErrorDashboard
|
|
|
503
500
|
else
|
|
504
501
|
say " 3. Run: rails db:migrate:error_dashboard"
|
|
505
502
|
end
|
|
506
|
-
say " 4.
|
|
503
|
+
say " 4. Before deploying: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD there"
|
|
507
504
|
say " 5. Restart your Rails server"
|
|
508
505
|
say " 6. Visit http://localhost:3000/red"
|
|
509
506
|
say " 7. Verify: rails error_dashboard:verify"
|
|
510
507
|
else
|
|
511
508
|
say " 1. Run: rails db:migrate"
|
|
512
|
-
say " 2.
|
|
509
|
+
say " 2. Before deploying: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD there"
|
|
513
510
|
say " 3. Restart your Rails server"
|
|
514
511
|
say " 4. Visit http://localhost:3000/red"
|
|
515
512
|
end
|
|
516
513
|
say "Authentication:", :cyan
|
|
517
|
-
say " Default: HTTP Basic Auth
|
|
514
|
+
say " Default: HTTP Basic Auth, gandalf/youshallnotpass in development and test only", :white
|
|
515
|
+
say " Elsewhere: set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD, or the app won't boot", :white
|
|
518
516
|
say " Devise/Warden: config.authenticate_with = -> { warden.authenticated? }", :white
|
|
519
517
|
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#
|
|
518
|
+
say " See: https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/CONFIGURATION.md#dashboard-credentials", :white
|
|
521
519
|
say "\n"
|
|
522
520
|
say "Issue Tracking (optional):", :cyan
|
|
523
521
|
say " Create a dedicated RED (Rails Error Dashboard) bot account on your platform:", :white
|
|
@@ -549,7 +547,7 @@ module RailsErrorDashboard
|
|
|
549
547
|
|
|
550
548
|
def build_quick_defaults
|
|
551
549
|
{
|
|
552
|
-
# Async ON (
|
|
550
|
+
# Async ON (runs on the app's Active Job adapter)
|
|
553
551
|
async_logging: true,
|
|
554
552
|
# All analytics ON — run at query/background time, zero request-path overhead
|
|
555
553
|
baseline_alerts: true,
|
|
@@ -5,9 +5,12 @@ RailsErrorDashboard.configure do |config|
|
|
|
5
5
|
# AUTHENTICATION (Always Required - Cannot Be Disabled)
|
|
6
6
|
# ============================================================================
|
|
7
7
|
|
|
8
|
-
# Dashboard
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# Dashboard login (HTTP Basic Auth). Always enforced, in every environment.
|
|
9
|
+
# gandalf / youshallnotpass work in development and test only. Everywhere
|
|
10
|
+
# else, set ERROR_DASHBOARD_USER and ERROR_DASHBOARD_PASSWORD in the
|
|
11
|
+
# environment, or the app refuses to boot. Don't replace the fallback values
|
|
12
|
+
# below with a real password: it would end up in source control.
|
|
13
|
+
# https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/CONFIGURATION.md#dashboard-credentials
|
|
11
14
|
config.dashboard_username = ENV.fetch("ERROR_DASHBOARD_USER", "gandalf")
|
|
12
15
|
config.dashboard_password = ENV.fetch("ERROR_DASHBOARD_PASSWORD", "youshallnotpass")
|
|
13
16
|
|
|
@@ -160,9 +163,11 @@ RailsErrorDashboard.configure do |config|
|
|
|
160
163
|
|
|
161
164
|
<% if @enable_async_logging -%>
|
|
162
165
|
# Async Error Logging - ENABLED
|
|
163
|
-
# Errors are logged in background jobs
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
+
# Errors are logged in background jobs, off the request path. The jobs run on
|
|
167
|
+
# your app's Active Job adapter (config.active_job.queue_adapter), so in
|
|
168
|
+
# production a worker must process the default and error_notifications queues
|
|
169
|
+
# (Rails 8: bin/jobs for Solid Queue). async_adapter below is only validated
|
|
170
|
+
# and shown on the Settings page; it does not choose the backend.
|
|
166
171
|
config.async_logging = true
|
|
167
172
|
config.async_adapter = :async # Options: :async (built-in), :sidekiq, :solid_queue
|
|
168
173
|
# To disable: Set config.async_logging = false
|
|
@@ -446,7 +451,8 @@ RailsErrorDashboard.configure do |config|
|
|
|
446
451
|
config.enable_internal_logging = false
|
|
447
452
|
|
|
448
453
|
# Log level (default: :silent)
|
|
449
|
-
# Options: :debug, :info, :warn, :error, :silent
|
|
454
|
+
# Options: :debug, :info, :warn, :error, :fatal, :silent
|
|
455
|
+
# (RED has no fatal-level messages, so :fatal logs nothing, like :silent)
|
|
450
456
|
config.log_level = :silent
|
|
451
457
|
|
|
452
458
|
# Example: Enable verbose logging for debugging
|
|
@@ -2,34 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
module RailsErrorDashboard
|
|
4
4
|
module Generators
|
|
5
|
-
#
|
|
5
|
+
# Deprecated: checks the app's Solid Queue config for RED. Writes nothing.
|
|
6
6
|
# Usage: rails generate rails_error_dashboard:solid_queue
|
|
7
|
+
#
|
|
8
|
+
# It used to write a config/queue.yml with workers and no dispatchers.
|
|
9
|
+
# Solid Queue only falls back to its default dispatcher when a section has
|
|
10
|
+
# neither, so that file ran no dispatcher: no delayed job ran, the host
|
|
11
|
+
# app's own included. A Solid Queue config with a "*" worker and a
|
|
12
|
+
# dispatcher already processes RED's queues, so this generator now only
|
|
13
|
+
# checks the file the app has. It never writes Solid Queue's files or
|
|
14
|
+
# points at Solid Queue's installer (decision 0001). To be removed in a
|
|
15
|
+
# later minor release.
|
|
7
16
|
class SolidQueueGenerator < Rails::Generators::Base
|
|
8
|
-
|
|
17
|
+
desc "Deprecated: checks config/queue.yml for problems that stop RED's jobs. Writes nothing."
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
def check_queue_config
|
|
20
|
+
say "\nrails_error_dashboard:solid_queue is deprecated and no longer writes config/queue.yml.", :yellow
|
|
21
|
+
say "A Solid Queue config with a \"*\" worker and a dispatcher already processes RED's queues.\n\n"
|
|
11
22
|
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
check = RailsErrorDashboard::Services::SolidQueueConfigCheck
|
|
24
|
+
path = check.config_path(destination_root)
|
|
25
|
+
relative = path.relative_path_from(Pathname(destination_root)).to_s
|
|
26
|
+
|
|
27
|
+
unless path.exist?
|
|
28
|
+
say "No #{relative}: Solid Queue runs its defaults (a \"*\" worker and a dispatcher), which process RED's queues.", :green
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
environments = check.app_environments(destination_root)
|
|
33
|
+
environments = [ Rails.env.to_s ] if environments.empty?
|
|
34
|
+
result = check.call(path, environments: environments)
|
|
35
|
+
|
|
36
|
+
if result[:skipped]
|
|
37
|
+
say "Could not check #{relative}: #{result[:skipped]}", :yellow
|
|
38
|
+
elsif result[:problems].any?
|
|
39
|
+
say "#{relative} has problems that stop jobs from running:", :red
|
|
40
|
+
result[:problems].each { |problem| say " - #{problem}", :red }
|
|
41
|
+
say ""
|
|
42
|
+
check::FIX.each { |line| say line, :yellow }
|
|
43
|
+
else
|
|
44
|
+
say "#{relative} processes RED's queues (#{check.red_queue_names.join(', ')}) and runs a dispatcher.", :green
|
|
45
|
+
end
|
|
14
46
|
end
|
|
15
47
|
|
|
16
48
|
def show_instructions
|
|
17
|
-
say "\
|
|
18
|
-
say "
|
|
19
|
-
say "=
|
|
20
|
-
say "
|
|
21
|
-
say " 1. Install Solid Queue gem (if not already):", :cyan
|
|
22
|
-
say " bundle add solid_queue", :white
|
|
23
|
-
say "\n 2. Run Solid Queue migrations:", :cyan
|
|
24
|
-
say " bin/rails solid_queue:install", :white
|
|
25
|
-
say "\n 3. Set ActiveJob adapter in config/application.rb:", :cyan
|
|
26
|
-
say " config.active_job.queue_adapter = :solid_queue", :white
|
|
27
|
-
say "\n 4. Start Solid Queue worker:", :cyan
|
|
28
|
-
say " bin/jobs", :white
|
|
29
|
-
say "\n 5. Enable async logging in config/initializers/rails_error_dashboard.rb:", :cyan
|
|
30
|
-
say " config.async_logging = true", :white
|
|
31
|
-
say " config.async_adapter = :solid_queue", :white
|
|
32
|
-
say "\n" + "=" * 80, :green
|
|
49
|
+
say "\nTo run RED's jobs with Solid Queue:", :cyan
|
|
50
|
+
say " config.active_job.queue_adapter = :solid_queue (config/environments/production.rb)", :white
|
|
51
|
+
say " Run a worker: bin/jobs, or SOLID_QUEUE_IN_PUMA=1 with Puma's solid_queue plugin", :white
|
|
52
|
+
say " Check anytime: bin/rails error_dashboard:verify\n", :white
|
|
33
53
|
end
|
|
34
54
|
end
|
|
35
55
|
end
|
|
@@ -5,6 +5,11 @@ module RailsErrorDashboard
|
|
|
5
5
|
class UninstallGenerator < Rails::Generators::Base
|
|
6
6
|
desc "Uninstalls Rails Error Dashboard and removes all associated files and data"
|
|
7
7
|
|
|
8
|
+
MIGRATION_GLOBS = [
|
|
9
|
+
"db/migrate/*rails_error_dashboard*.rb",
|
|
10
|
+
"db/error_dashboard_migrate/*rails_error_dashboard*.rb"
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
8
13
|
class_option :keep_data, type: :boolean, default: false, desc: "Keep error data in database (don't drop tables)"
|
|
9
14
|
class_option :skip_confirmation, type: :boolean, default: false, desc: "Skip confirmation prompts"
|
|
10
15
|
class_option :manual_only, type: :boolean, default: false, desc: "Show manual instructions only, don't perform automated uninstall"
|
|
@@ -35,6 +40,15 @@ module RailsErrorDashboard
|
|
|
35
40
|
say " #{status_icon(@components[:migrations])} Migrations (#{migration_count} files)"
|
|
36
41
|
say " #{status_icon(@components[:tables])} Database tables (#{table_count} tables)"
|
|
37
42
|
say "\n"
|
|
43
|
+
|
|
44
|
+
# Couldn't see the error database (no database.yml entry for it, or the
|
|
45
|
+
# server is down). Carrying on would remove the initializer and leave
|
|
46
|
+
# every table behind, with nothing left that knows where they are.
|
|
47
|
+
if @database_error && !options[:keep_data] && !options[:manual_only]
|
|
48
|
+
raise Thor::Error, "Can't reach the error database, so nothing was removed: #{@database_error}\n" \
|
|
49
|
+
"Fix config/database.yml (or start the database) and run this again, " \
|
|
50
|
+
"or pass --keep-data to remove only the files."
|
|
51
|
+
end
|
|
38
52
|
end
|
|
39
53
|
|
|
40
54
|
def show_manual_instructions
|
|
@@ -42,13 +56,16 @@ module RailsErrorDashboard
|
|
|
42
56
|
say " 📖 Manual Uninstall Instructions", :cyan
|
|
43
57
|
say "=" * 80
|
|
44
58
|
say "\n"
|
|
45
|
-
|
|
46
|
-
say "Step 1: Remove from Gemfile", :yellow
|
|
47
|
-
say " Open: Gemfile"
|
|
48
|
-
say " Remove: gem 'rails_error_dashboard'"
|
|
49
|
-
say " Run: bundle install"
|
|
59
|
+
say "Do these in order: the table drop needs the gem, so the Gemfile comes last.", :yellow
|
|
50
60
|
say "\n"
|
|
51
61
|
|
|
62
|
+
if @components[:tables]
|
|
63
|
+
say "Step 1: Drop database tables (⚠️ DESTRUCTIVE - will delete all error data)", :yellow
|
|
64
|
+
say " Run: bin/rails rails_error_dashboard:db:drop"
|
|
65
|
+
say " It drops every RED table in foreign-key order, on the error database if you use one."
|
|
66
|
+
say "\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
52
69
|
if @components[:initializer]
|
|
53
70
|
say "Step 2: Remove initializer", :yellow
|
|
54
71
|
say " Delete: config/initializers/rails_error_dashboard.rb"
|
|
@@ -58,33 +75,31 @@ module RailsErrorDashboard
|
|
|
58
75
|
if @components[:route]
|
|
59
76
|
say "Step 3: Remove route", :yellow
|
|
60
77
|
say " Open: config/routes.rb"
|
|
61
|
-
say " Remove: mount RailsErrorDashboard::Engine =>
|
|
78
|
+
say " Remove the line: mount RailsErrorDashboard::Engine => ..."
|
|
62
79
|
say "\n"
|
|
63
80
|
end
|
|
64
81
|
|
|
65
82
|
if @components[:migrations]
|
|
66
83
|
say "Step 4: Remove migrations", :yellow
|
|
67
|
-
say " Delete migration files from db/migrate/:"
|
|
68
84
|
migration_files.each do |file|
|
|
69
|
-
say " - #{
|
|
85
|
+
say " - #{file}", :white
|
|
70
86
|
end
|
|
71
87
|
say "\n"
|
|
72
88
|
end
|
|
73
89
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_cascade_patterns')", :white
|
|
81
|
-
say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_error_baselines')", :white
|
|
82
|
-
say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_error_comments')", :white
|
|
83
|
-
say " ActiveRecord::Migration.drop_table(:rails_error_dashboard_error_logs) rescue nil", :white
|
|
84
|
-
say "\n"
|
|
85
|
-
end
|
|
90
|
+
say "Step 5: Separate database only: remove its entry from config/database.yml", :yellow
|
|
91
|
+
say "\n"
|
|
92
|
+
|
|
93
|
+
say "Step 6: Regenerate the schema file", :yellow
|
|
94
|
+
say " Run: bin/rails db:schema:dump (otherwise db:schema:load recreates the tables)"
|
|
95
|
+
say "\n"
|
|
86
96
|
|
|
87
|
-
say "Step
|
|
97
|
+
say "Step 7: Remove from Gemfile", :yellow
|
|
98
|
+
say " Remove: gem 'rails_error_dashboard'"
|
|
99
|
+
say " Run: bundle install"
|
|
100
|
+
say "\n"
|
|
101
|
+
|
|
102
|
+
say "Step 8: Clean up environment variables (optional)", :yellow
|
|
88
103
|
say " Remove from .env or environment:"
|
|
89
104
|
say " - ERROR_DASHBOARD_USER"
|
|
90
105
|
say " - ERROR_DASHBOARD_PASSWORD"
|
|
@@ -96,8 +111,7 @@ module RailsErrorDashboard
|
|
|
96
111
|
say " - DASHBOARD_BASE_URL"
|
|
97
112
|
say "\n"
|
|
98
113
|
|
|
99
|
-
say "Step
|
|
100
|
-
say " Run: rails restart (or restart your server)"
|
|
114
|
+
say "Step 9: Restart your application", :yellow
|
|
101
115
|
say "\n"
|
|
102
116
|
|
|
103
117
|
say "=" * 80
|
|
@@ -165,6 +179,20 @@ module RailsErrorDashboard
|
|
|
165
179
|
say "\n"
|
|
166
180
|
end
|
|
167
181
|
|
|
182
|
+
# Before any file is removed: if the drop fails, the app still boots with
|
|
183
|
+
# its initializer and can reach the error database to try again.
|
|
184
|
+
def drop_database_tables
|
|
185
|
+
return if options[:manual_only]
|
|
186
|
+
return if options[:keep_data]
|
|
187
|
+
return unless @components[:tables]
|
|
188
|
+
|
|
189
|
+
say " Dropping database tables...", :yellow
|
|
190
|
+
dropped = RailsErrorDashboard::Commands::DropAllTables.call(connection: drop_connection)
|
|
191
|
+
say " ✓ Dropped #{dropped.size} database table(s)", :green
|
|
192
|
+
rescue => e
|
|
193
|
+
raise Thor::Error, "Could not drop RED's tables, so no files were removed: #{e.message}"
|
|
194
|
+
end
|
|
195
|
+
|
|
168
196
|
def remove_initializer
|
|
169
197
|
return if options[:manual_only]
|
|
170
198
|
return unless @components[:initializer]
|
|
@@ -182,7 +210,7 @@ module RailsErrorDashboard
|
|
|
182
210
|
say " ✓ Removed route", :green
|
|
183
211
|
rescue => e
|
|
184
212
|
say " ⚠️ Could not automatically remove route: #{e.message}", :yellow
|
|
185
|
-
say " Please
|
|
213
|
+
say " Please remove the mount RailsErrorDashboard::Engine line from config/routes.rb", :yellow
|
|
186
214
|
end
|
|
187
215
|
end
|
|
188
216
|
|
|
@@ -196,35 +224,6 @@ module RailsErrorDashboard
|
|
|
196
224
|
say " ✓ Removed #{migration_count} migration file(s)", :green
|
|
197
225
|
end
|
|
198
226
|
|
|
199
|
-
def drop_database_tables
|
|
200
|
-
return if options[:manual_only]
|
|
201
|
-
return if options[:keep_data]
|
|
202
|
-
return unless @components[:tables]
|
|
203
|
-
|
|
204
|
-
say " Dropping database tables...", :yellow
|
|
205
|
-
|
|
206
|
-
# Drop tables in reverse order (to respect foreign keys)
|
|
207
|
-
tables_to_drop = [
|
|
208
|
-
"rails_error_dashboard_error_comments",
|
|
209
|
-
"rails_error_dashboard_error_occurrences",
|
|
210
|
-
"rails_error_dashboard_cascade_patterns",
|
|
211
|
-
"rails_error_dashboard_error_baselines",
|
|
212
|
-
"rails_error_dashboard_error_logs"
|
|
213
|
-
]
|
|
214
|
-
|
|
215
|
-
dropped_count = 0
|
|
216
|
-
tables_to_drop.each do |table|
|
|
217
|
-
if ActiveRecord::Base.connection.table_exists?(table)
|
|
218
|
-
ActiveRecord::Base.connection.drop_table(table, if_exists: true)
|
|
219
|
-
dropped_count += 1
|
|
220
|
-
end
|
|
221
|
-
rescue => e
|
|
222
|
-
say " ⚠️ Could not drop table #{table}: #{e.message}", :yellow
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
say " ✓ Dropped #{dropped_count} database table(s)", :green
|
|
226
|
-
end
|
|
227
|
-
|
|
228
227
|
def show_completion_message
|
|
229
228
|
return if options[:manual_only]
|
|
230
229
|
|
|
@@ -282,7 +281,7 @@ module RailsErrorDashboard
|
|
|
282
281
|
end
|
|
283
282
|
|
|
284
283
|
def migration_files
|
|
285
|
-
Dir.glob(
|
|
284
|
+
Dir.glob(MIGRATION_GLOBS)
|
|
286
285
|
end
|
|
287
286
|
|
|
288
287
|
def migration_count
|
|
@@ -290,22 +289,27 @@ module RailsErrorDashboard
|
|
|
290
289
|
end
|
|
291
290
|
|
|
292
291
|
def tables_exist?
|
|
293
|
-
|
|
294
|
-
table_names.any? { |table| ActiveRecord::Base.connection.table_exists?(table) rescue false }
|
|
292
|
+
table_names.any?
|
|
295
293
|
end
|
|
296
294
|
|
|
295
|
+
# The tables Queries::UninstallPlan finds on the error database, in drop
|
|
296
|
+
# order. Empty (with the reason shown) when that database can't be reached.
|
|
297
297
|
def table_names
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
"
|
|
303
|
-
|
|
304
|
-
|
|
298
|
+
@table_names ||= begin
|
|
299
|
+
RailsErrorDashboard::Queries::UninstallPlan.call(drop_connection).map { |entry| entry[:table] }
|
|
300
|
+
rescue => e
|
|
301
|
+
@database_error = e.message
|
|
302
|
+
say " ⚠️ Could not inspect the error database: #{e.message}", :yellow
|
|
303
|
+
[]
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def drop_connection
|
|
308
|
+
RailsErrorDashboard::Commands::DropAllTables.connection
|
|
305
309
|
end
|
|
306
310
|
|
|
307
311
|
def table_count
|
|
308
|
-
table_names.count
|
|
312
|
+
table_names.count
|
|
309
313
|
end
|
|
310
314
|
|
|
311
315
|
def gemfile_includes_gem?
|