rails_error_dashboard 0.5.10 → 0.5.12

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -1
  3. data/app/controllers/rails_error_dashboard/errors_controller.rb +31 -0
  4. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +12 -0
  5. data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +40 -0
  6. data/app/mailers/rails_error_dashboard/digest_mailer.rb +23 -0
  7. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +2 -1
  8. data/app/views/layouts/rails_error_dashboard.html.erb +5 -0
  9. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb +172 -0
  10. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb +49 -0
  11. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +10 -10
  12. data/app/views/rails_error_dashboard/errors/_source_code.html.erb +20 -7
  13. data/app/views/rails_error_dashboard/errors/settings.html.erb +7 -3
  14. data/app/views/rails_error_dashboard/errors/show.html.erb +21 -0
  15. data/app/views/rails_error_dashboard/errors/user_impact.html.erb +172 -0
  16. data/config/routes.rb +3 -0
  17. data/lib/generators/rails_error_dashboard/install/install_generator.rb +173 -160
  18. data/lib/generators/rails_error_dashboard/install/templates/README +9 -18
  19. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +15 -10
  20. data/lib/rails_error_dashboard/configuration.rb +83 -26
  21. data/lib/rails_error_dashboard/engine.rb +10 -0
  22. data/lib/rails_error_dashboard/error_reporter.rb +26 -0
  23. data/lib/rails_error_dashboard/middleware/error_catcher.rb +7 -0
  24. data/lib/rails_error_dashboard/middleware/rate_limiter.rb +16 -12
  25. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +2 -1
  26. data/lib/rails_error_dashboard/queries/user_impact_summary.rb +93 -0
  27. data/lib/rails_error_dashboard/services/coverage_tracker.rb +139 -0
  28. data/lib/rails_error_dashboard/services/digest_builder.rb +158 -0
  29. data/lib/rails_error_dashboard/services/notification_helpers.rb +2 -1
  30. data/lib/rails_error_dashboard/subscribers/issue_tracker_subscriber.rb +2 -1
  31. data/lib/rails_error_dashboard/value_objects/error_context.rb +5 -0
  32. data/lib/rails_error_dashboard/version.rb +1 -1
  33. data/lib/rails_error_dashboard.rb +3 -0
  34. data/lib/tasks/error_dashboard.rake +23 -0
  35. metadata +33 -9
@@ -0,0 +1,172 @@
1
+ <% content_for :page_title, "User Impact" %>
2
+
3
+ <div class="container-fluid py-4">
4
+ <div class="d-flex justify-content-between align-items-center mb-4">
5
+ <h1 class="h3 mb-0">
6
+ <i class="bi bi-people me-2"></i>
7
+ User Impact
8
+ </h1>
9
+
10
+ <div class="btn-group" role="group">
11
+ <%= link_to user_impact_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
12
+ 7 Days
13
+ <% end %>
14
+ <%= link_to user_impact_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
15
+ 30 Days
16
+ <% end %>
17
+ <%= link_to user_impact_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
18
+ 90 Days
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+
23
+ <% if @entries.empty? %>
24
+ <div class="text-center py-5">
25
+ <i class="bi bi-people display-1 text-muted mb-3"></i>
26
+ <h4 class="text-muted">No User Impact Data</h4>
27
+ <p class="text-muted">
28
+ No errors with user attribution were found in the last <%= @days %> days.
29
+ </p>
30
+ <div class="card mx-auto" style="max-width: 500px;">
31
+ <div class="card-body text-start">
32
+ <h6>How user impact is tracked:</h6>
33
+ <ul class="mb-0">
34
+ <li>Errors must have a <code>user_id</code> associated</li>
35
+ <li>Auto-detected via <code>CurrentAttributes</code> or <code>current_user</code></li>
36
+ <li>Or set <code>config.user_model = "User"</code> in your initializer</li>
37
+ </ul>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ <% else %>
42
+ <!-- Summary Cards -->
43
+ <div class="row mb-4">
44
+ <div class="col-md-3">
45
+ <div class="card text-center">
46
+ <div class="card-body">
47
+ <div class="display-6 text-primary"><%= @summary[:total_error_types_with_users] %></div>
48
+ <small class="text-muted">Error Types with Users</small>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ <div class="col-md-3">
53
+ <div class="card text-center">
54
+ <div class="card-body">
55
+ <div class="display-6 text-danger"><%= @summary[:total_unique_users_affected] %></div>
56
+ <small class="text-muted">Total Users Affected</small>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ <div class="col-md-3">
61
+ <div class="card text-center">
62
+ <div class="card-body">
63
+ <div class="display-6"><code><%= @summary[:most_impactful] || "N/A" %></code></div>
64
+ <small class="text-muted">Most Impactful Error</small>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <div class="col-md-3">
69
+ <div class="card text-center">
70
+ <div class="card-body">
71
+ <div class="display-6 text-info"><%= @summary[:total_users] ? number_with_delimiter(@summary[:total_users]) : "N/A" %></div>
72
+ <small class="text-muted">Total Users (for %)</small>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- User Impact Table -->
79
+ <div class="card mb-4">
80
+ <div class="card-header bg-white d-flex justify-content-between align-items-center">
81
+ <h5 class="mb-0">
82
+ <i class="bi bi-bar-chart text-primary me-2"></i>
83
+ Errors Ranked by User Impact
84
+ <span class="badge bg-primary"><%= @summary[:total_error_types_with_users] %></span>
85
+ </h5>
86
+ <small class="text-muted"><%== @pagy.info_tag %></small>
87
+ </div>
88
+ <div class="card-body p-0">
89
+ <div class="table-responsive">
90
+ <table class="table table-hover mb-0">
91
+ <thead class="table-light">
92
+ <tr>
93
+ <th width="40">#</th>
94
+ <th>Error Type</th>
95
+ <th width="120">Unique Users</th>
96
+ <th width="120">Occurrences</th>
97
+ <% if @summary[:total_users] %>
98
+ <th width="150">User Impact %</th>
99
+ <% end %>
100
+ <th width="100">Severity</th>
101
+ <th width="140">Last Seen</th>
102
+ <th width="80"></th>
103
+ </tr>
104
+ </thead>
105
+ <tbody>
106
+ <% @entries.each_with_index do |entry, index| %>
107
+ <tr>
108
+ <td><strong><%= (@pagy.from || 1) + index %></strong></td>
109
+ <td>
110
+ <strong><%= entry[:error_type] %></strong>
111
+ <br><small class="text-muted"><%= entry[:message] %></small>
112
+ </td>
113
+ <td>
114
+ <span class="badge bg-danger"><%= entry[:unique_users] %> users</span>
115
+ </td>
116
+ <td>
117
+ <span class="badge bg-secondary"><%= entry[:total_occurrences] %>x</span>
118
+ </td>
119
+ <% if @summary[:total_users] && entry[:impact_percentage] %>
120
+ <td>
121
+ <div class="d-flex align-items-center gap-2">
122
+ <div class="progress flex-grow-1" style="height: 18px;">
123
+ <%
124
+ pct = entry[:impact_percentage]
125
+ bar_class = pct > 10 ? "bg-danger" : (pct > 5 ? "bg-warning" : "bg-info")
126
+ %>
127
+ <div class="progress-bar <%= bar_class %>" style="width: <%= [ pct, 100 ].min %>%;">
128
+ <%= pct %>%
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </td>
133
+ <% end %>
134
+ <td>
135
+ <%
136
+ sev = entry[:severity].to_s
137
+ sev_class = case sev
138
+ when "critical" then "bg-danger"
139
+ when "high" then "bg-warning text-dark"
140
+ when "medium" then "bg-info"
141
+ else "bg-secondary"
142
+ end
143
+ %>
144
+ <span class="badge <%= sev_class %>"><%= sev.capitalize %></span>
145
+ </td>
146
+ <td><small><%= local_time_ago(entry[:last_seen]) %></small></td>
147
+ <td>
148
+ <% if entry[:id] %>
149
+ <%= link_to error_path(entry[:id]), class: "btn btn-sm btn-outline-primary" do %>
150
+ <i class="bi bi-eye"></i>
151
+ <% end %>
152
+ <% end %>
153
+ </td>
154
+ </tr>
155
+ <% end %>
156
+ </tbody>
157
+ </table>
158
+ </div>
159
+ </div>
160
+ <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
161
+ <small class="text-muted">
162
+ <i class="bi bi-info-circle"></i>
163
+ Ranked by unique users affected, not occurrence count.
164
+ An error hitting 1000 users once is worse than hitting 1 user 1000 times.
165
+ </small>
166
+ <div>
167
+ <%== @pagy.series_nav(:bootstrap) if @pagy.pages > 1 %>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ <% end %>
172
+ </div>
data/config/routes.rb CHANGED
@@ -38,8 +38,11 @@ RailsErrorDashboard::Engine.routes.draw do
38
38
  get :actioncable_health_summary
39
39
  get :activestorage_health_summary
40
40
  get :releases
41
+ get :user_impact
41
42
  get :diagnostic_dumps
42
43
  post :create_diagnostic_dump
44
+ post :enable_coverage
45
+ post :disable_coverage
43
46
  post :batch_action
44
47
  end
45
48
  end
@@ -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: false, desc: "Enable async error logging"
18
+ class_option :async_logging, type: :boolean, default: true, desc: "Enable async error logging (default: true, uses Rails :async adapter — no extra infrastructure needed)"
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')"
@@ -35,11 +35,13 @@ module RailsErrorDashboard
35
35
  class_option :swallowed_exceptions, type: :boolean, default: false, desc: "Enable swallowed exception detection (Ruby 3.3+)"
36
36
  class_option :crash_capture, type: :boolean, default: false, desc: "Enable process crash capture via at_exit hook"
37
37
  class_option :diagnostic_dump, type: :boolean, default: false, desc: "Enable on-demand diagnostic dump"
38
+ class_option :quick, type: :boolean, default: false, desc: "Zero-prompt install with sensible defaults (~60 seconds to working dashboard)"
38
39
 
39
40
  def welcome_message
40
41
  say "\n"
41
42
  say "=" * 70
42
- say " 📊 Rails Error Dashboard Installation", :cyan
43
+ say " Rails Error Dashboard Installation", :cyan
44
+ say " Quick mode: zero prompts, working dashboard in ~60 seconds", :yellow if quick_mode?
43
45
  say "=" * 70
44
46
  say "\n"
45
47
  say "Core features will be enabled automatically:", :green
@@ -52,7 +54,15 @@ module RailsErrorDashboard
52
54
  end
53
55
 
54
56
  def select_optional_features
55
- return unless options[:interactive] && behavior == :invoke
57
+ return unless behavior == :invoke
58
+
59
+ if quick_mode?
60
+ @selected_features = build_quick_defaults
61
+ say " Using sensible defaults (analytics ON, notifications OFF, breadcrumbs OFF)", :green
62
+ return
63
+ end
64
+
65
+ return unless options[:interactive]
56
66
  return unless $stdin.tty? # Skip interactive mode if not running in a terminal
57
67
 
58
68
  say "Let's configure optional features...\n", :cyan
@@ -60,166 +70,116 @@ module RailsErrorDashboard
60
70
 
61
71
  @selected_features = {}
62
72
 
63
- # Feature definitions with descriptions - organized by category
64
- # Note: separate_database is handled separately via select_database_mode
65
- features = [
66
- # === NOTIFICATIONS ===
67
- {
68
- key: :slack,
69
- name: "Slack Notifications",
70
- description: "Send error alerts to Slack channels",
71
- category: "Notifications"
72
- },
73
- {
74
- key: :email,
75
- name: "Email Notifications",
76
- description: "Send error alerts via email",
77
- category: "Notifications"
78
- },
79
- {
80
- key: :discord,
81
- name: "Discord Notifications",
82
- description: "Send error alerts to Discord",
83
- category: "Notifications"
84
- },
85
- {
86
- key: :pagerduty,
87
- name: "PagerDuty Integration",
88
- description: "Critical errors to PagerDuty",
89
- category: "Notifications"
90
- },
91
- {
92
- key: :webhooks,
93
- name: "Generic Webhooks",
94
- description: "Send data to custom endpoints",
95
- category: "Notifications"
96
- },
97
-
98
- # === PERFORMANCE & SCALABILITY ===
99
- {
100
- key: :async_logging,
101
- name: "Async Error Logging",
102
- description: "Process errors in background jobs (faster responses)",
103
- category: "Performance"
104
- },
105
- {
106
- key: :error_sampling,
107
- name: "Error Sampling",
108
- description: "Log only % of non-critical errors (reduce volume)",
109
- category: "Performance"
110
- },
111
-
112
- # === ADVANCED ANALYTICS ===
113
- {
114
- key: :baseline_alerts,
115
- name: "Baseline Anomaly Alerts",
116
- description: "Auto-detect unusual error rate spikes",
117
- category: "Advanced Analytics"
118
- },
119
- {
120
- key: :similar_errors,
121
- name: "Fuzzy Error Matching",
122
- description: "Find similar errors across different hashes",
123
- category: "Advanced Analytics"
124
- },
125
- {
126
- key: :co_occurring_errors,
127
- name: "Co-occurring Errors",
128
- description: "Detect errors that happen together",
129
- category: "Advanced Analytics"
130
- },
131
- {
132
- key: :error_cascades,
133
- name: "Error Cascade Detection",
134
- description: "Identify error chains (A causes B causes C)",
135
- category: "Advanced Analytics"
136
- },
137
- {
138
- key: :error_correlation,
139
- name: "Error Correlation Analysis",
140
- description: "Correlate with versions, users, time",
141
- category: "Advanced Analytics"
142
- },
143
- {
144
- key: :platform_comparison,
145
- name: "Platform Comparison",
146
- description: "Compare iOS vs Android vs Web health",
147
- category: "Advanced Analytics"
148
- },
149
- {
150
- key: :occurrence_patterns,
151
- name: "Occurrence Pattern Detection",
152
- description: "Detect cyclical patterns and bursts",
153
- category: "Advanced Analytics"
154
- },
155
-
156
- # === DEVELOPER TOOLS ===
157
- {
158
- key: :source_code_integration,
159
- name: "Source Code Integration (NEW!)",
160
- description: "View source code directly in error details",
161
- category: "Developer Tools"
162
- },
163
- {
164
- key: :git_blame,
165
- name: "Git Blame Integration (NEW!)",
166
- description: "Show git blame info (author, commit, timestamp)",
167
- category: "Developer Tools"
168
- },
169
- {
170
- key: :breadcrumbs,
171
- name: "Breadcrumbs (NEW!)",
172
- description: "Capture request activity trail (SQL, controller, cache events)",
173
- category: "Developer Tools"
174
- },
175
- {
176
- key: :system_health,
177
- name: "System Health Snapshot (NEW!)",
178
- description: "Capture GC, memory, threads, connection pool at error time",
179
- category: "Developer Tools"
180
- },
181
- {
182
- key: :swallowed_exceptions,
183
- name: "Swallowed Exception Detection (Ruby 3.3+)",
184
- description: "Detect exceptions being silently rescued via TracePoint(:rescue)",
185
- category: "Developer Tools"
186
- },
187
- {
188
- key: :crash_capture,
189
- name: "Process Crash Capture",
190
- description: "Capture fatal crashes via at_exit hook (written to disk, imported on next boot)",
191
- category: "Developer Tools"
192
- },
193
- {
194
- key: :diagnostic_dump,
195
- name: "Diagnostic Dump",
196
- description: "On-demand system state snapshot (rake task + dashboard page)",
197
- category: "Developer Tools"
198
- }
199
- ]
200
-
201
- features.each_with_index do |feature, index|
202
- say "\n[#{index + 1}/#{features.length}] #{feature[:name]}", :cyan
203
- say " #{feature[:description]}", :white
204
-
205
- # Check if feature was passed via command line option
206
- if options[feature[:key]]
207
- @selected_features[feature[:key]] = true
208
- say " ✓ Enabled (via --#{feature[:key]} flag)", :green
209
- else
210
- response = ask(" Enable? (y/N):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
211
- @selected_features[feature[:key]] = response.downcase == "y"
73
+ # =====================================================================
74
+ # QUESTION 1: Notifications (gated one y/N opens 5 sub-questions)
75
+ # =====================================================================
76
+ notification_keys = %i[slack email discord pagerduty webhooks]
77
+ any_notification_cli_flag = notification_keys.any? { |k| options[k] }
78
+
79
+ say "[1/3] Notifications [background/dashboard only — zero request overhead]", :cyan
80
+ say " Alert your team via Slack, email, Discord, PagerDuty, or webhooks.", :white
212
81
 
213
- if @selected_features[feature[:key]]
214
- say " ✓ Enabled", :green
215
- else
216
- say " Disabled", :white
82
+ if any_notification_cli_flag
83
+ # Individual CLI flags passed — respect them, skip the gate prompt
84
+ notification_keys.each { |k| @selected_features[k] = options[k] }
85
+ say " Using CLI flags for notification channels", :green
86
+ else
87
+ wants_notifications = ask(" Set up notifications? (y/N):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
88
+ if wants_notifications.downcase == "y"
89
+ notification_channels = [
90
+ { key: :slack, name: "Slack", hint: "SLACK_WEBHOOK_URL" },
91
+ { key: :email, name: "Email", hint: "ERROR_NOTIFICATION_EMAILS" },
92
+ { key: :discord, name: "Discord", hint: "DISCORD_WEBHOOK_URL" },
93
+ { key: :pagerduty, name: "PagerDuty", hint: "PAGERDUTY_INTEGRATION_KEY" },
94
+ { key: :webhooks, name: "Webhooks", hint: "WEBHOOK_URLS" }
95
+ ]
96
+ notification_channels.each do |ch|
97
+ r = ask(" #{ch[:name]}? (ENV[#{ch[:hint]}]) (y/N):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
98
+ @selected_features[ch[:key]] = r.downcase == "y"
217
99
  end
100
+ else
101
+ notification_keys.each { |k| @selected_features[k] = false }
102
+ say " ✓ Notifications off (enable anytime in initializer)", :white
218
103
  end
104
+ end
105
+
106
+ # =====================================================================
107
+ # QUESTION 2: Advanced Analytics (grouped — defaults YES)
108
+ # =====================================================================
109
+ analytics_keys = %i[baseline_alerts similar_errors co_occurring_errors
110
+ error_cascades error_correlation platform_comparison occurrence_patterns]
111
+ any_analytics_cli_flag = analytics_keys.any? { |k| options[k] }
112
+
113
+ say "\n[2/3] Advanced Analytics (7 features) [background/dashboard only]", :cyan
114
+ say " Baseline alerts, fuzzy matching, co-occurring errors, cascade detection,", :white
115
+ say " correlation analysis, platform comparison, occurrence patterns.", :white
116
+ say " All run at query time or as background jobs — zero request-path overhead.", :white
117
+
118
+ if any_analytics_cli_flag
119
+ # Let create_initializer_file read options[] directly via the &.dig fallback
120
+ say " ✓ Using individual CLI flags for analytics", :white
121
+ else
122
+ response = ask(" Enable all? (Y/n):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
123
+ analytics_on = response.downcase != "n"
124
+ analytics_keys.each { |k| @selected_features[k] = analytics_on }
125
+ say analytics_on ? " ✓ All 7 analytics features enabled" : " ✗ Analytics disabled (enable individually in initializer)", analytics_on ? :green : :white
126
+ end
219
127
 
220
- if @selected_features[feature[:key]] && feature[:key] == :swallowed_exceptions && RUBY_VERSION < "3.3"
221
- say " ⚠ Warning: This feature requires Ruby 3.3+ (you have #{RUBY_VERSION})", :yellow
222
- say " The feature will be included in your config but won't activate until you upgrade Ruby.", :white
128
+ # =====================================================================
129
+ # QUESTION 3: Advanced Options (gated defaults NO)
130
+ # =====================================================================
131
+ advanced_keys = %i[async_logging error_sampling breadcrumbs system_health
132
+ source_code_integration git_blame swallowed_exceptions
133
+ crash_capture diagnostic_dump]
134
+ any_advanced_cli_flag = advanced_keys.any? { |k| options[k] }
135
+
136
+ say "\n[3/3] Advanced Options (performance tuning & diagnostics)", :cyan
137
+ say " Async logging, error sampling, breadcrumbs, system health,", :white
138
+ say " source code viewer, git blame, crash capture, and more.", :white
139
+
140
+ if any_advanced_cli_flag
141
+ # CLI flags passed — set them and skip the gate
142
+ advanced_features = [
143
+ { key: :async_logging, name: "Async Logging", desc: "Process errors in background jobs [removes request-path overhead]" },
144
+ { key: :error_sampling, name: "Error Sampling", desc: "Log only % of non-critical errors [error-time only]" },
145
+ { key: :breadcrumbs, name: "Breadcrumbs", desc: "6 AS::Notifications subscribers: SQL, cache, jobs, mailers, Rack::Attack, ActionCable [request-path overhead]" },
146
+ { key: :system_health, name: "System Health Snapshot", desc: "GC, memory, threads, connection pool at error time [error-time only]" },
147
+ { key: :source_code_integration, name: "Source Code Integration", desc: "Inline source viewer in error details [dashboard only]" },
148
+ { key: :git_blame, name: "Git Blame", desc: "Author, commit, timestamp per source line [dashboard only]" },
149
+ { key: :swallowed_exceptions, name: "Swallowed Exception Detection", desc: "TracePoint(:rescue) catches silently rescued exceptions [request-path overhead, Ruby 3.3+]" },
150
+ { key: :crash_capture, name: "Process Crash Capture", desc: "at_exit hook captures fatal crashes [error-time only]" },
151
+ { key: :diagnostic_dump, name: "Diagnostic Dump", desc: "On-demand system snapshot via rake task [background/dashboard only]" }
152
+ ]
153
+ advanced_features.each { |f| @selected_features[f[:key]] = options[f[:key]] }
154
+ say " ✓ Using CLI flags for advanced options", :white
155
+ else
156
+ wants_advanced = ask(" Configure advanced options? (y/N):", :yellow, limited_to: [ "y", "Y", "n", "N", "" ])
157
+ if wants_advanced.downcase == "y"
158
+ advanced_features = [
159
+ { key: :async_logging, name: "Async Logging", desc: "Process errors in background jobs — removes overhead from request path [removes request-path overhead]" },
160
+ { key: :error_sampling, name: "Error Sampling", desc: "Log only % of non-critical errors to reduce volume [error-time only]" },
161
+ { key: :breadcrumbs, name: "Breadcrumbs", desc: "Subscribes 6 AS::Notifications: SQL, cache, jobs, mailers, Rack::Attack, ActionCable [request-path overhead]" },
162
+ { key: :system_health, name: "System Health Snapshot", desc: "GC stats, memory (RSS/peak/swap), threads, connection pool at error time [error-time only]" },
163
+ { key: :source_code_integration, name: "Source Code Integration", desc: "View source code inline in error details (+/- 7 lines context) [dashboard only]" },
164
+ { key: :git_blame, name: "Git Blame Integration", desc: "Show author, commit, timestamp for each source line (requires git) [dashboard only]" },
165
+ { key: :swallowed_exceptions, name: "Swallowed Exception Detection", desc: "Detect silently rescued exceptions via TracePoint(:rescue) [request-path overhead, Ruby 3.3+]" },
166
+ { key: :crash_capture, name: "Process Crash Capture", desc: "Capture fatal crashes via at_exit hook, written to disk [error-time only]" },
167
+ { key: :diagnostic_dump, name: "Diagnostic Dump", desc: "On-demand system state snapshot via rake task or dashboard button [background/dashboard only]" }
168
+ ]
169
+ advanced_features.each do |f|
170
+ say "\n #{f[:name]}", :cyan
171
+ 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"
174
+ if @selected_features[f[:key]] && f[:key] == :swallowed_exceptions && RUBY_VERSION < "3.3"
175
+ say " ⚠ Requires Ruby 3.3+ (you have #{RUBY_VERSION}) — will activate after upgrade", :yellow
176
+ end
177
+ end
178
+ else
179
+ # Set all advanced keys to false EXCEPT async_logging which defaults ON via class_option.
180
+ # We do not set async_logging here so options[:async_logging] (default: true) wins in create_initializer_file.
181
+ (advanced_keys - [ :async_logging ]).each { |k| @selected_features[k] = false }
182
+ say " ✓ Using defaults (async logging ON, everything else OFF)", :white
223
183
  end
224
184
  end
225
185
 
@@ -247,6 +207,13 @@ module RailsErrorDashboard
247
207
  # Skip if existing config already detected database mode
248
208
  return if @existing_install_detected && @database_mode
249
209
 
210
+ # Quick mode: use shared DB, no prompt
211
+ if quick_mode?
212
+ @database_mode = :same
213
+ @application_name = detect_application_name
214
+ return
215
+ end
216
+
250
217
  # Skip if not interactive or if --separate_database was passed via CLI
251
218
  if options[:separate_database]
252
219
  @database_mode = :separate
@@ -398,6 +365,12 @@ module RailsErrorDashboard
398
365
  end
399
366
 
400
367
  def add_route
368
+ routes_path = File.join(destination_root, "config", "routes.rb")
369
+ if File.exist?(routes_path) && File.read(routes_path).include?("RailsErrorDashboard::Engine")
370
+ say_status "skip", "route already exists (RailsErrorDashboard::Engine is already mounted)", :yellow
371
+ return
372
+ end
373
+
401
374
  route "mount RailsErrorDashboard::Engine => '/red' # RED (Rails Error Dashboard) — also works at /error_dashboard"
402
375
  end
403
376
 
@@ -415,6 +388,7 @@ module RailsErrorDashboard
415
388
  say " ✓ Dashboard UI", :green
416
389
  say " ✓ Real-time Updates", :green
417
390
  say " ✓ Analytics", :green
391
+ say " ✓ Async Logging (Rails :async adapter — no extra infrastructure needed)", :green
418
392
 
419
393
  # Count optional features enabled
420
394
  enabled_count = 0
@@ -490,7 +464,16 @@ module RailsErrorDashboard
490
464
  say " → Set DISCORD_WEBHOOK_URL in .env", :yellow if @enable_discord
491
465
  say " → Set PAGERDUTY_INTEGRATION_KEY in .env", :yellow if @enable_pagerduty
492
466
  say " → Set WEBHOOK_URLS in .env", :yellow if @enable_webhooks
493
- say " → Ensure Sidekiq/Solid Queue running", :yellow if @enable_async_logging
467
+ if @enable_async_logging
468
+ # Check which async adapter is in use — only warn about external workers when needed
469
+ async_section = File.exist?(File.join(destination_root, "config/initializers/rails_error_dashboard.rb")) &&
470
+ File.read(File.join(destination_root, "config/initializers/rails_error_dashboard.rb"))
471
+ if async_section && (async_section.include?("async_adapter = :sidekiq") || async_section.include?("async_adapter = :solid_queue"))
472
+ say " → Ensure your background worker (Sidekiq/SolidQueue) is running for async logging", :yellow
473
+ else
474
+ say " ✓ Async logging uses Rails :async adapter — no extra process needed", :green
475
+ end
476
+ end
494
477
 
495
478
  # Database-specific instructions
496
479
  if @enable_separate_database
@@ -548,6 +531,36 @@ module RailsErrorDashboard
548
531
 
549
532
  private
550
533
 
534
+ def quick_mode?
535
+ options[:quick]
536
+ end
537
+
538
+ def build_quick_defaults
539
+ {
540
+ # Async ON (built-in Rails :async adapter, zero infrastructure)
541
+ async_logging: true,
542
+ # All analytics ON — run at query/background time, zero request-path overhead
543
+ baseline_alerts: true,
544
+ similar_errors: true,
545
+ co_occurring_errors: true,
546
+ error_cascades: true,
547
+ error_correlation: true,
548
+ platform_comparison: true,
549
+ occurrence_patterns: true,
550
+ # ON — small overhead, high insight value
551
+ breadcrumbs: true, # ~0.1ms per request — SQL, cache, job trail leading to each error
552
+ system_health: true, # ~1ms per error — GC, memory, threads at exact error moment
553
+ error_sampling: true, # 50% sampling on non-critical errors — halves storage, still shows patterns
554
+ # OFF — require credentials, config, or niche use case
555
+ slack: false, email: false, discord: false, pagerduty: false, webhooks: false,
556
+ source_code_integration: false,
557
+ git_blame: false,
558
+ swallowed_exceptions: false,
559
+ crash_capture: false,
560
+ diagnostic_dump: false
561
+ }
562
+ end
563
+
551
564
  def detect_application_name
552
565
  if defined?(Rails) && Rails.application
553
566
  Rails.application.class.module_parent_name
@@ -1,33 +1,24 @@
1
1
  ===============================================================================
2
2
 
3
- Rails Error Dashboard has been installed!
3
+ RED (Rails Error Dashboard) has been installed!
4
4
 
5
5
  Next steps:
6
6
 
7
7
  1. Run migrations:
8
8
  rails db:migrate
9
9
 
10
- 2. (Optional) If you want to use a separate database for error logs:
11
- - Set USE_SEPARATE_ERROR_DB=true in your .env file
12
- - Configure error_logs database in config/database.yml
13
- - Run: rails db:create:error_logs
14
- - Run: rails db:migrate:error_logs
15
-
16
- 3. Start your Rails server:
10
+ 2. Start your Rails server:
17
11
  rails server
18
12
 
19
- 4. Visit the error dashboard:
20
- http://localhost:3000/error_dashboard
13
+ 3. Visit the dashboard:
14
+ http://localhost:3000/red
21
15
 
22
- 5. Default credentials (change in config/initializers/rails_error_dashboard.rb):
23
- Username: admin
24
- Password: password
16
+ 4. Change the default credentials in:
17
+ config/initializers/rails_error_dashboard.rb
25
18
 
26
- 6. (Optional) Set up Slack notifications:
27
- - Set SLACK_WEBHOOK_URL in your .env file
28
- - Errors will be sent to your Slack channel automatically
19
+ For separate database setup, see:
20
+ https://github.com/AnjanJ/rails_error_dashboard/blob/main/docs/guides/CONFIGURATION.md
29
21
 
30
- For more information, visit:
31
- https://github.com/anjanjagirdar/rails_error_dashboard
22
+ Full docs: https://github.com/AnjanJ/rails_error_dashboard
32
23
 
33
24
  ===============================================================================