rails_error_dashboard 0.5.11 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e91844bfccd6f88a89ad933cb4f100bb79e941f5b43a78155b6b21a46a58fffb
4
- data.tar.gz: fffcde71a4ed1859cf70ef17ed7eca18531ac9d6be9f17fb3d1a9deceba4e0ca
3
+ metadata.gz: 2c512995247150c6d418591b4ea85b40778b37c5b484666cf2af2226cbad9da7
4
+ data.tar.gz: 72283fdf63f2ea4fac3cdde62098bc247c4262862fbd8dd2ef80b5b804fcd626
5
5
  SHA512:
6
- metadata.gz: ab28a1843285439c9874a8d3552e5dceda81f4ced4cbc1d8d0ee6950a772758a1769f0a323bc207f2884c991c22d3938551b5b0a25f693ab19c6c5d0d0da23a7
7
- data.tar.gz: a921a83a5fafceb78323dc3f2c48d559c722efbbf79b2708113f5c9d482ace9c2347700415aeee63e1af864d11a036bdc08ba43cfd860f2f28ea0621c8c7eb30
6
+ metadata.gz: 5a66093c81993d0af82915235a2da1bbb5d0ba6779a9882d2e87bb90b4d98012d9d3f4be91703c570ac9253cd346d17f53a5eb8e21f9cc6bf44728e72ec23ef1
7
+ data.tar.gz: '06290bf609e62810e613530eceff8dcc86936a8b39f7e27c5ac0b8bd2ba5090f05e2df7ed4b86f1ca6d732fa849cfeea6d3fd361f4ef79e9a304fd010239f8ad'
@@ -2,7 +2,7 @@
2
2
  <td onclick="event.stopPropagation();">
3
3
  <input type="checkbox" class="error-checkbox form-check-input" value="<%= error.id %>" data-error-id="<%= error.id %>">
4
4
  </td>
5
- <td onclick="window.location='<%= error_path(error) %>';">
5
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
6
6
  <% severity = error.severity %>
7
7
  <% if severity == :critical %>
8
8
  <span class="badge bg-danger" data-bs-toggle="tooltip" title="Critical severity - requires immediate attention">CRITICAL</span>
@@ -17,7 +17,7 @@
17
17
  <br><small class="text-muted" data-bs-toggle="tooltip" title="Priority score (0-100) based on severity, frequency, recency, and user impact">P<%= error.priority_score %></small>
18
18
  <% end %>
19
19
  </td>
20
- <td onclick="window.location='<%= error_path(error) %>';">
20
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
21
21
  <code class="text-danger" data-bs-toggle="tooltip" title="<%= error.error_type %>"><%= error.error_type.split('::').last %></code>
22
22
  <% if error.recent? %>
23
23
  <span class="badge bg-success ms-1" data-bs-toggle="tooltip" title="Error occurred within the last hour">NEW</span>
@@ -26,29 +26,29 @@
26
26
  <br><small class="badge bg-light text-dark" data-bs-toggle="tooltip" title="App version when error occurred">v<%= error.app_version %></small>
27
27
  <% end %>
28
28
  </td>
29
- <td onclick="window.location='<%= error_path(error) %>';">
29
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
30
30
  <div class="text-truncate" style="max-width: 300px;" title="<%= error.message %>">
31
31
  <%= error.message %>
32
32
  </div>
33
33
  </td>
34
- <td onclick="window.location='<%= error_path(error) %>';">
34
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
35
35
  <span class="badge bg-primary"><%= error.occurrence_count %>x</span>
36
36
  </td>
37
- <td onclick="window.location='<%= error_path(error) %>';">
37
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
38
38
  <small>
39
39
  <strong>First:</strong> <%= local_time(error.first_seen_at, format: :short) %><br>
40
40
  <strong>Last:</strong> <%= local_time(error.last_seen_at, format: :short) %>
41
41
  </small>
42
42
  </td>
43
43
  <% if local_assigns[:show_application] %>
44
- <td onclick="window.location='<%= error_path(error) %>';">
44
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
45
45
  <span class="badge bg-info">
46
46
  <%= error.application&.name || 'Unknown' %>
47
47
  </span>
48
48
  </td>
49
49
  <% end %>
50
50
  <% if local_assigns[:show_platform] %>
51
- <td onclick="window.location='<%= error_path(error) %>';">
51
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
52
52
  <% if error.platform == 'iOS' %>
53
53
  <span class="badge badge-ios"><i class="bi bi-apple"></i> iOS</span>
54
54
  <% elsif error.platform == 'Android' %>
@@ -60,7 +60,7 @@
60
60
  <% end %>
61
61
  </td>
62
62
  <% end %>
63
- <td onclick="window.location='<%= error_path(error) %>';">
63
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
64
64
  <% if error.user_id %>
65
65
  <small data-bs-toggle="tooltip" title="User affected by this error">User #<%= error.user_id %></small>
66
66
  <% if error.respond_to?(:user_impact_percentage) %>
@@ -73,7 +73,7 @@
73
73
  <small class="text-muted" data-bs-toggle="tooltip" title="No specific user affected">-</small>
74
74
  <% end %>
75
75
  </td>
76
- <td onclick="window.location='<%= error_path(error) %>';">
76
+ <td onclick="window.location='<%= rails_error_dashboard.error_path(error) %>';">
77
77
  <% if error.resolved? %>
78
78
  <i class="bi bi-check-circle-fill text-success"
79
79
  data-bs-toggle="tooltip"
@@ -95,7 +95,7 @@
95
95
  <% end %>
96
96
  </td>
97
97
  <td onclick="event.stopPropagation();">
98
- <%= link_to error_path(error), class: "btn btn-sm btn-outline-primary" do %>
98
+ <%= link_to rails_error_dashboard.error_path(error), class: "btn btn-sm btn-outline-primary" do %>
99
99
  <i class="bi bi-eye"></i>
100
100
  <% end %>
101
101
  </td>
@@ -205,7 +205,7 @@
205
205
  should_show = show_if_key.nil? || @config.send(show_if_key)
206
206
  next unless should_show
207
207
 
208
- value = @config.send(setting_key)
208
+ value = @config.send(setting_meta[:method] || setting_key)
209
209
  %>
210
210
  <div class="col-md-6 mb-3">
211
211
  <div class="d-flex justify-content-between align-items-center">
@@ -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
219
105
 
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
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
127
+
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
@@ -421,6 +388,7 @@ module RailsErrorDashboard
421
388
  say " ✓ Dashboard UI", :green
422
389
  say " ✓ Real-time Updates", :green
423
390
  say " ✓ Analytics", :green
391
+ say " ✓ Async Logging (Rails :async adapter — no extra infrastructure needed)", :green
424
392
 
425
393
  # Count optional features enabled
426
394
  enabled_count = 0
@@ -496,7 +464,16 @@ module RailsErrorDashboard
496
464
  say " → Set DISCORD_WEBHOOK_URL in .env", :yellow if @enable_discord
497
465
  say " → Set PAGERDUTY_INTEGRATION_KEY in .env", :yellow if @enable_pagerduty
498
466
  say " → Set WEBHOOK_URLS in .env", :yellow if @enable_webhooks
499
- 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
500
477
 
501
478
  # Database-specific instructions
502
479
  if @enable_separate_database
@@ -554,6 +531,36 @@ module RailsErrorDashboard
554
531
 
555
532
  private
556
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
+
557
564
  def detect_application_name
558
565
  if defined?(Rails) && Rails.application
559
566
  Rails.application.class.module_parent_name
@@ -138,17 +138,19 @@ RailsErrorDashboard.configure do |config|
138
138
 
139
139
  <% if @enable_async_logging -%>
140
140
  # Async Error Logging - ENABLED
141
- # Errors will be logged in background jobs for better performance
141
+ # Errors are logged in background jobs zero impact on request response time.
142
+ # Default adapter is :async (Rails built-in, no extra infrastructure needed).
143
+ # Swap to :sidekiq or :solid_queue when you have a background worker running.
142
144
  config.async_logging = true
143
- config.async_adapter = :sidekiq # Options: :sidekiq, :solid_queue, :async
145
+ config.async_adapter = :async # Options: :async (built-in), :sidekiq, :solid_queue
144
146
  # To disable: Set config.async_logging = false
145
147
 
146
148
  <% else -%>
147
149
  # Async Error Logging - DISABLED
148
- # Errors are logged synchronously (blocking)
149
- # To enable: Set config.async_logging = true and configure adapter
150
+ # Errors are logged synchronously (adds ~1-5ms to requests that trigger errors)
151
+ # To enable: Set config.async_logging = true
150
152
  config.async_logging = false
151
- # config.async_adapter = :sidekiq # Options: :sidekiq, :solid_queue, :async
153
+ # config.async_adapter = :async # Options: :async (built-in), :sidekiq, :solid_queue
152
154
 
153
155
  <% end -%>
154
156
  # Backtrace size limiting (100 lines is industry standard: Rollbar, Airbrake, Bugsnag)
@@ -156,15 +158,18 @@ RailsErrorDashboard.configure do |config|
156
158
 
157
159
  <% if @enable_error_sampling -%>
158
160
  # Error Sampling - ENABLED
159
- # Reduce volume by logging only a percentage of non-critical errors
160
- # Critical errors are ALWAYS logged regardless of sampling rate
161
- config.sampling_rate = 0.1 # 10% - Adjust as needed (0.0 to 1.0)
161
+ # Samples non-critical errors to reduce storage volume.
162
+ # Critical and high severity errors are ALWAYS logged at 100% regardless of this setting.
163
+ # 0.5 = log 50% of non-critical occurrences halves storage while keeping
164
+ # occurrence counts meaningful and error patterns visible.
165
+ # Tune lower (e.g. 0.1) if one error is flooding the DB; set to 1.0 to log everything.
166
+ config.sampling_rate = 0.5 # 50% of non-critical errors
162
167
  # To disable: Set config.sampling_rate = 1.0 (100%)
163
168
 
164
169
  <% else -%>
165
170
  # Error Sampling - DISABLED
166
- # All errors are logged (100% sampling rate)
167
- # To enable: Set config.sampling_rate < 1.0 (e.g., 0.1 for 10%)
171
+ # All errors are logged (100% sampling rate).
172
+ # To enable: Set config.sampling_rate < 1.0 (e.g., 0.5 for 50%, 0.1 for 10%)
168
173
  config.sampling_rate = 1.0
169
174
 
170
175
  <% end -%>
@@ -511,44 +511,56 @@ module RailsErrorDashboard
511
511
  @enable_activestorage_tracking = false
512
512
  end
513
513
 
514
+ # Skip credential/service-dependent validations during Docker builds.
515
+ # SECRET_KEY_BASE_DUMMY=1 means no credentials or external services available.
516
+ build_env = ENV["SECRET_KEY_BASE_DUMMY"].present?
517
+
514
518
  # Validate issue tracking configuration
515
- if enable_issue_tracking && effective_issue_tracker_token.blank?
516
- warnings << "enable_issue_tracking is true but no token configured. " \
517
- "Set issue_tracker_token or RED_BOT_TOKEN env var. " \
518
- "Tip: Create a dedicated RED (Rails Error Dashboard) bot account on your platform."
519
- end
519
+ unless build_env
520
+ if enable_issue_tracking && effective_issue_tracker_token.blank?
521
+ warnings << "enable_issue_tracking is true but no token configured. " \
522
+ "Set issue_tracker_token or RED_BOT_TOKEN env var. " \
523
+ "Tip: Create a dedicated RED (Rails Error Dashboard) bot account on your platform."
524
+ end
520
525
 
521
- if enable_issue_tracking && effective_issue_tracker_provider.nil?
522
- warnings << "enable_issue_tracking is true but provider could not be detected. " \
523
- "Set issue_tracker_provider or git_repository_url."
526
+ if enable_issue_tracking && effective_issue_tracker_provider.nil?
527
+ warnings << "enable_issue_tracking is true but provider could not be detected. " \
528
+ "Set issue_tracker_provider or git_repository_url."
529
+ end
524
530
  end
525
531
 
526
- # Validate crash capture path (must exist if custom path specified)
527
- if enable_crash_capture && crash_capture_path
532
+ # Validate crash capture path auto-create if missing
533
+ if enable_crash_capture && crash_capture_path && !build_env
528
534
  unless Dir.exist?(crash_capture_path)
529
- errors << "crash_capture_path '#{crash_capture_path}' does not exist"
535
+ begin
536
+ FileUtils.mkdir_p(crash_capture_path)
537
+ rescue => e
538
+ errors << "crash_capture_path '#{crash_capture_path}' could not be created: #{e.message}"
539
+ end
530
540
  end
531
541
  end
532
542
 
533
- # Validate notification dependencies
534
- if enable_slack_notifications && (slack_webhook_url.nil? || slack_webhook_url.strip.empty?)
535
- errors << "slack_webhook_url is required when enable_slack_notifications is true"
536
- end
543
+ # Validate notification dependencies (skip during builds — credentials unavailable)
544
+ unless build_env
545
+ if enable_slack_notifications && (slack_webhook_url.nil? || slack_webhook_url.strip.empty?)
546
+ errors << "slack_webhook_url is required when enable_slack_notifications is true"
547
+ end
537
548
 
538
- if enable_email_notifications && notification_email_recipients.empty?
539
- errors << "notification_email_recipients is required when enable_email_notifications is true"
540
- end
549
+ if enable_email_notifications && notification_email_recipients.empty?
550
+ errors << "notification_email_recipients is required when enable_email_notifications is true"
551
+ end
541
552
 
542
- if enable_discord_notifications && (discord_webhook_url.nil? || discord_webhook_url.strip.empty?)
543
- errors << "discord_webhook_url is required when enable_discord_notifications is true"
544
- end
553
+ if enable_discord_notifications && (discord_webhook_url.nil? || discord_webhook_url.strip.empty?)
554
+ errors << "discord_webhook_url is required when enable_discord_notifications is true"
555
+ end
545
556
 
546
- if enable_pagerduty_notifications && (pagerduty_integration_key.nil? || pagerduty_integration_key.strip.empty?)
547
- errors << "pagerduty_integration_key is required when enable_pagerduty_notifications is true"
548
- end
557
+ if enable_pagerduty_notifications && (pagerduty_integration_key.nil? || pagerduty_integration_key.strip.empty?)
558
+ errors << "pagerduty_integration_key is required when enable_pagerduty_notifications is true"
559
+ end
549
560
 
550
- if enable_webhook_notifications && webhook_urls.empty?
551
- errors << "webhook_urls is required when enable_webhook_notifications is true"
561
+ if enable_webhook_notifications && webhook_urls.empty?
562
+ errors << "webhook_urls is required when enable_webhook_notifications is true"
563
+ end
552
564
  end
553
565
 
554
566
  # Validate separate database configuration
@@ -61,6 +61,16 @@ module RailsErrorDashboard
61
61
 
62
62
  # Subscribe to Rails error reporter
63
63
  config.after_initialize do
64
+ # Skip all runtime features during Docker asset precompilation.
65
+ # SECRET_KEY_BASE_DUMMY=1 signals a build environment — no database,
66
+ # no credentials, no external services are available. Activating
67
+ # TracePoint hooks, error subscribers, or background jobs here causes
68
+ # infinite retry loops and connection failures (issues #1-5).
69
+ if ENV["SECRET_KEY_BASE_DUMMY"].present?
70
+ Rails.logger.info "[Rails Error Dashboard] Build environment detected (SECRET_KEY_BASE_DUMMY) — skipping runtime features."
71
+ next
72
+ end
73
+
64
74
  if RailsErrorDashboard.configuration.enable_error_subscriber
65
75
  Rails.error.subscribe(RailsErrorDashboard::ErrorReporter.new)
66
76
  end
@@ -24,6 +24,32 @@ module RailsErrorDashboard
24
24
 
25
25
  # CRITICAL: Wrap entire process in rescue to ensure failures don't break the app
26
26
  begin
27
+ # Enrich context with request data from Thread.current when available.
28
+ # Rails internals (ActionDispatch::Executor) report errors with
29
+ # source: "application.action_dispatch" but pass NO request object,
30
+ # resulting in placeholder values ("Rails Application", "{}", etc.).
31
+ # Our middleware stores the Rack env in Thread.current so we can
32
+ # build a proper request here — fixing issue #106.
33
+ if context[:request].nil? && Thread.current[:rails_error_dashboard_request_env]
34
+ env = Thread.current[:rails_error_dashboard_request_env]
35
+ context = context.merge(request: ActionDispatch::Request.new(env))
36
+ end
37
+
38
+ # Skip duplicate reports from our own middleware when the subscriber
39
+ # already captured this error with full request context above.
40
+ # Without this, async logging enqueues two jobs for one exception —
41
+ # and non-deterministic job ordering can overwrite good data.
42
+ if source == "rack.middleware" &&
43
+ Thread.current[:rails_error_dashboard_reported_errors]&.include?(error.object_id)
44
+ return
45
+ end
46
+
47
+ # Track that we've reported this error (for dedup with middleware)
48
+ if Thread.current[:rails_error_dashboard_request_env]
49
+ Thread.current[:rails_error_dashboard_reported_errors] ||= Set.new
50
+ Thread.current[:rails_error_dashboard_reported_errors].add(error.object_id)
51
+ end
52
+
27
53
  # Extract context information
28
54
  error_context = ValueObjects::ErrorContext.new(context, source)
29
55
 
@@ -23,6 +23,11 @@ module RailsErrorDashboard
23
23
  # Record request start time for duration calculation
24
24
  env["rails_error_dashboard.request_start"] = Time.now.to_f
25
25
 
26
+ # Store request env in thread-local so the ErrorReporter subscriber
27
+ # can access request context when Rails.error.report fires from
28
+ # Rails internals (e.g., ActionDispatch::ShowExceptions)
29
+ Thread.current[:rails_error_dashboard_request_env] = env
30
+
26
31
  # Initialize breadcrumb buffer for this request
27
32
  if RailsErrorDashboard.configuration.enable_breadcrumbs
28
33
  RailsErrorDashboard::Services::BreadcrumbCollector.init_buffer
@@ -52,6 +57,8 @@ module RailsErrorDashboard
52
57
  raise exception
53
58
  ensure
54
59
  # CRITICAL: Always clean up thread-local storage (Puma reuses threads)
60
+ Thread.current[:rails_error_dashboard_request_env] = nil
61
+ Thread.current[:rails_error_dashboard_reported_errors] = nil
55
62
  RailsErrorDashboard::Services::BreadcrumbCollector.clear_buffer
56
63
  end
57
64
  end
@@ -110,6 +110,11 @@ module RailsErrorDashboard
110
110
  # Additional context (from mobile apps, etc.)
111
111
  params.merge!(@context[:additional_context]) if @context[:additional_context]
112
112
 
113
+ # Pre-serialized params (from async logging or double-ErrorContext path).
114
+ # LogError creates a second ErrorContext from error_context.to_h which
115
+ # has :request_params as a JSON string but no :request object.
116
+ return @context[:request_params] if params.empty? && @context[:request_params].present?
117
+
113
118
  params.to_json
114
119
  end
115
120
 
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.5.11"
2
+ VERSION = "0.5.12"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_error_dashboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.11
4
+ version: 0.5.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -496,7 +496,7 @@ metadata:
496
496
  funding_uri: https://github.com/sponsors/AnjanJ
497
497
  post_install_message: |
498
498
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
499
- RED (Rails Error Dashboard) v0.5.11
499
+ RED (Rails Error Dashboard) v0.5.12
500
500
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
501
501
 
502
502
  First install: