rails_error_dashboard 0.1.21 → 0.1.22
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 +180 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +7 -11
- data/app/models/rails_error_dashboard/application.rb +30 -0
- data/app/models/rails_error_dashboard/error_log.rb +36 -7
- data/app/views/layouts/rails_error_dashboard.html.erb +77 -7
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +7 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +26 -1
- data/app/views/rails_error_dashboard/errors/settings.html.erb +4 -10
- data/db/migrate/20260106094220_create_rails_error_dashboard_applications.rb +13 -0
- data/db/migrate/20260106094233_add_application_to_error_logs.rb +24 -0
- data/db/migrate/20260106094256_backfill_application_for_existing_errors.rb +23 -0
- data/db/migrate/20260106094318_finalize_application_foreign_key.rb +17 -0
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +2 -7
- data/lib/rails_error_dashboard/commands/log_error.rb +26 -10
- data/lib/rails_error_dashboard/configuration.rb +10 -6
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +16 -7
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +59 -47
- data/lib/rails_error_dashboard/queries/errors_list.rb +8 -0
- data/lib/rails_error_dashboard/queries/filter_options.rb +2 -1
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/tasks/error_dashboard.rake +272 -0
- metadata +8 -2
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :error_dashboard do
|
|
4
|
+
desc "List all registered applications with error counts"
|
|
5
|
+
task list_applications: :environment do
|
|
6
|
+
puts "\n" + "=" * 80
|
|
7
|
+
puts "RAILS ERROR DASHBOARD - REGISTERED APPLICATIONS"
|
|
8
|
+
puts "=" * 80
|
|
9
|
+
|
|
10
|
+
# Use single SQL query with aggregates to avoid N+1 queries
|
|
11
|
+
# This fetches all app data + error counts in one query instead of 6N queries
|
|
12
|
+
apps = RailsErrorDashboard::Application
|
|
13
|
+
.select('rails_error_dashboard_applications.*')
|
|
14
|
+
.select('COUNT(rails_error_dashboard_error_logs.id) as total_errors')
|
|
15
|
+
.select('COALESCE(SUM(CASE WHEN NOT rails_error_dashboard_error_logs.resolved THEN 1 ELSE 0 END), 0) as unresolved_errors')
|
|
16
|
+
.joins('LEFT JOIN rails_error_dashboard_error_logs ON rails_error_dashboard_error_logs.application_id = rails_error_dashboard_applications.id')
|
|
17
|
+
.group('rails_error_dashboard_applications.id')
|
|
18
|
+
.order(:name)
|
|
19
|
+
|
|
20
|
+
if apps.empty?
|
|
21
|
+
puts "\nNo applications registered yet."
|
|
22
|
+
puts "Applications are auto-registered when they log their first error."
|
|
23
|
+
puts "\n" + "=" * 80 + "\n"
|
|
24
|
+
next
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
puts "\n#{apps.count} application(s) registered:\n\n"
|
|
28
|
+
|
|
29
|
+
# Calculate column widths using aggregated data (no additional queries)
|
|
30
|
+
name_width = [apps.map(&:name).map(&:length).max, 20].max
|
|
31
|
+
total_width = [apps.map(&:total_errors).map(&:to_s).map(&:length).max, 5].max
|
|
32
|
+
unresolved_width = [apps.map(&:unresolved_errors).map(&:to_s).map(&:length).max, 10].max
|
|
33
|
+
|
|
34
|
+
# Print header
|
|
35
|
+
printf "%-#{name_width}s %#{total_width}s %#{unresolved_width}s %s\n",
|
|
36
|
+
"APPLICATION", "TOTAL", "UNRESOLVED", "CREATED"
|
|
37
|
+
puts "-" * 80
|
|
38
|
+
|
|
39
|
+
# Print each application (total_errors and unresolved_errors are already loaded as attributes)
|
|
40
|
+
apps.each do |app|
|
|
41
|
+
printf "%-#{name_width}s %#{total_width}d %#{unresolved_width}d %s\n",
|
|
42
|
+
app.name,
|
|
43
|
+
app.total_errors.to_i,
|
|
44
|
+
app.unresolved_errors.to_i,
|
|
45
|
+
app.created_at.strftime("%Y-%m-%d %H:%M")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
puts "\n" + "=" * 80
|
|
49
|
+
|
|
50
|
+
# Summary stats using already-loaded aggregates (no additional queries)
|
|
51
|
+
total_errors = apps.sum(&:total_errors)
|
|
52
|
+
total_unresolved = apps.sum(&:unresolved_errors)
|
|
53
|
+
|
|
54
|
+
puts "\nSUMMARY:"
|
|
55
|
+
puts " Total Applications: #{apps.count}"
|
|
56
|
+
puts " Total Errors: #{total_errors}"
|
|
57
|
+
puts " Total Unresolved: #{total_unresolved}"
|
|
58
|
+
puts " Resolution Rate: #{total_errors.zero? ? 'N/A' : "#{((total_errors - total_unresolved).to_f / total_errors * 100).round(1)}%"}"
|
|
59
|
+
|
|
60
|
+
puts "\n" + "=" * 80 + "\n"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc "Backfill application_id for existing errors"
|
|
64
|
+
task backfill_application: :environment do
|
|
65
|
+
app_name = ENV['APP_NAME'] || ENV['APPLICATION_NAME'] ||
|
|
66
|
+
(defined?(Rails) && Rails.application.class.module_parent_name) ||
|
|
67
|
+
'Legacy Application'
|
|
68
|
+
|
|
69
|
+
puts "\n" + "=" * 80
|
|
70
|
+
puts "RAILS ERROR DASHBOARD - BACKFILL APPLICATION"
|
|
71
|
+
puts "=" * 80
|
|
72
|
+
puts "\nApplication Name: #{app_name}"
|
|
73
|
+
|
|
74
|
+
# Check if there are any errors without application
|
|
75
|
+
orphaned_count = RailsErrorDashboard::ErrorLog.where(application_id: nil).count
|
|
76
|
+
|
|
77
|
+
if orphaned_count.zero?
|
|
78
|
+
puts "\n✓ No errors found without application_id"
|
|
79
|
+
puts " All errors are already associated with an application."
|
|
80
|
+
puts "\n" + "=" * 80 + "\n"
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
puts "Errors to backfill: #{orphaned_count}"
|
|
85
|
+
|
|
86
|
+
# Find or create application
|
|
87
|
+
app = RailsErrorDashboard::Application.find_or_create_by!(name: app_name) do |a|
|
|
88
|
+
a.description = "Backfilled via rake task on #{Time.current.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
puts "Using application: #{app.name} (ID: #{app.id})"
|
|
92
|
+
|
|
93
|
+
# Confirm before proceeding
|
|
94
|
+
print "\nProceed with backfill? (y/N): "
|
|
95
|
+
confirmation = $stdin.gets.chomp.downcase
|
|
96
|
+
|
|
97
|
+
unless confirmation == 'y' || confirmation == 'yes'
|
|
98
|
+
puts "\n✗ Backfill cancelled"
|
|
99
|
+
puts "\n" + "=" * 80 + "\n"
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
puts "\nBackfilling errors..."
|
|
104
|
+
count = 0
|
|
105
|
+
start_time = Time.current
|
|
106
|
+
|
|
107
|
+
# Process in batches with progress indicator
|
|
108
|
+
RailsErrorDashboard::ErrorLog.where(application_id: nil).find_in_batches(batch_size: 1000) do |batch|
|
|
109
|
+
batch.each do |error|
|
|
110
|
+
error.update_column(:application_id, app.id)
|
|
111
|
+
count += 1
|
|
112
|
+
print "." if count % 100 == 0
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
elapsed = (Time.current - start_time).round(2)
|
|
117
|
+
|
|
118
|
+
puts "\n\n✓ Backfill complete!"
|
|
119
|
+
puts " Processed: #{count} errors"
|
|
120
|
+
puts " Time elapsed: #{elapsed} seconds"
|
|
121
|
+
puts " Rate: #{(count / elapsed).round(0)} errors/sec"
|
|
122
|
+
|
|
123
|
+
puts "\n" + "=" * 80 + "\n"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
desc "Show application statistics and health metrics"
|
|
127
|
+
task app_stats: :environment do
|
|
128
|
+
app_id = ENV['APP_ID']
|
|
129
|
+
app_name = ENV['APP_NAME']
|
|
130
|
+
|
|
131
|
+
puts "\n" + "=" * 80
|
|
132
|
+
puts "RAILS ERROR DASHBOARD - APPLICATION STATISTICS"
|
|
133
|
+
puts "=" * 80
|
|
134
|
+
|
|
135
|
+
# Find application
|
|
136
|
+
app = if app_id
|
|
137
|
+
RailsErrorDashboard::Application.find_by(id: app_id)
|
|
138
|
+
elsif app_name
|
|
139
|
+
RailsErrorDashboard::Application.find_by(name: app_name)
|
|
140
|
+
else
|
|
141
|
+
puts "\n✗ Please specify APP_ID or APP_NAME"
|
|
142
|
+
puts "\nExamples:"
|
|
143
|
+
puts " rails error_dashboard:app_stats APP_NAME='My App'"
|
|
144
|
+
puts " rails error_dashboard:app_stats APP_ID=1"
|
|
145
|
+
puts "\n" + "=" * 80 + "\n"
|
|
146
|
+
next
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
unless app
|
|
150
|
+
puts "\n✗ Application not found"
|
|
151
|
+
puts "\n" + "=" * 80 + "\n"
|
|
152
|
+
next
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Display application info
|
|
156
|
+
puts "\nApplication: #{app.name}"
|
|
157
|
+
puts "Created: #{app.created_at.strftime('%Y-%m-%d %H:%M')}"
|
|
158
|
+
puts "Description: #{app.description || 'N/A'}"
|
|
159
|
+
puts "\n" + "-" * 80
|
|
160
|
+
|
|
161
|
+
# Error counts
|
|
162
|
+
errors = app.error_logs
|
|
163
|
+
puts "\nERROR COUNTS:"
|
|
164
|
+
puts " Total: #{errors.count}"
|
|
165
|
+
puts " Unresolved: #{errors.unresolved.count}"
|
|
166
|
+
puts " Resolved: #{errors.resolved.count}"
|
|
167
|
+
puts " Resolution Rate: #{errors.count.zero? ? 'N/A' : "#{(errors.resolved.count.to_f / errors.count * 100).round(1)}%"}"
|
|
168
|
+
|
|
169
|
+
# Time-based stats
|
|
170
|
+
puts "\nTIME-BASED STATS:"
|
|
171
|
+
puts " Last 24 hours: #{errors.where('occurred_at >= ?', 24.hours.ago).count}"
|
|
172
|
+
puts " Last 7 days: #{errors.where('occurred_at >= ?', 7.days.ago).count}"
|
|
173
|
+
puts " Last 30 days: #{errors.where('occurred_at >= ?', 30.days.ago).count}"
|
|
174
|
+
|
|
175
|
+
# Top error types
|
|
176
|
+
puts "\nTOP 5 ERROR TYPES:"
|
|
177
|
+
top_errors = errors.group(:error_type).count.sort_by { |_, count| -count }.first(5)
|
|
178
|
+
if top_errors.any?
|
|
179
|
+
top_errors.each_with_index do |(error_type, count), index|
|
|
180
|
+
puts " #{index + 1}. #{error_type}: #{count}"
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
puts " No errors logged yet"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Platform breakdown
|
|
187
|
+
platforms = errors.group(:platform).count
|
|
188
|
+
if platforms.any?
|
|
189
|
+
puts "\nPLATFORM BREAKDOWN:"
|
|
190
|
+
platforms.sort_by { |_, count| -count }.each do |platform, count|
|
|
191
|
+
platform_name = platform || "Unknown"
|
|
192
|
+
percentage = (count.to_f / errors.count * 100).round(1)
|
|
193
|
+
puts " #{platform_name}: #{count} (#{percentage}%)"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Recent activity
|
|
198
|
+
recent = errors.order(occurred_at: :desc).limit(5)
|
|
199
|
+
if recent.any?
|
|
200
|
+
puts "\nRECENT ERRORS:"
|
|
201
|
+
recent.each do |error|
|
|
202
|
+
time_ago = Time.current - error.occurred_at
|
|
203
|
+
time_str = if time_ago < 3600
|
|
204
|
+
"#{(time_ago / 60).to_i}m ago"
|
|
205
|
+
elsif time_ago < 86400
|
|
206
|
+
"#{(time_ago / 3600).to_i}h ago"
|
|
207
|
+
else
|
|
208
|
+
"#{(time_ago / 86400).to_i}d ago"
|
|
209
|
+
end
|
|
210
|
+
puts " • #{error.error_type} (#{time_str}) - #{error.message.truncate(50)}"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
puts "\n" + "=" * 80 + "\n"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
desc "Clean up old resolved errors"
|
|
218
|
+
task cleanup_resolved: :environment do
|
|
219
|
+
days = ENV['DAYS']&.to_i || 90
|
|
220
|
+
app_name = ENV['APP_NAME']
|
|
221
|
+
|
|
222
|
+
puts "\n" + "=" * 80
|
|
223
|
+
puts "RAILS ERROR DASHBOARD - CLEANUP RESOLVED ERRORS"
|
|
224
|
+
puts "=" * 80
|
|
225
|
+
puts "\nCleaning up resolved errors older than #{days} days"
|
|
226
|
+
|
|
227
|
+
scope = RailsErrorDashboard::ErrorLog.resolved
|
|
228
|
+
.where('resolved_at < ?', days.days.ago)
|
|
229
|
+
|
|
230
|
+
if app_name
|
|
231
|
+
app = RailsErrorDashboard::Application.find_by(name: app_name)
|
|
232
|
+
unless app
|
|
233
|
+
puts "\n✗ Application '#{app_name}' not found"
|
|
234
|
+
puts "\n" + "=" * 80 + "\n"
|
|
235
|
+
next
|
|
236
|
+
end
|
|
237
|
+
scope = scope.where(application: app)
|
|
238
|
+
puts "Filtering to application: #{app_name}"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
count = scope.count
|
|
242
|
+
|
|
243
|
+
if count.zero?
|
|
244
|
+
puts "\n✓ No resolved errors found older than #{days} days"
|
|
245
|
+
puts "\n" + "=" * 80 + "\n"
|
|
246
|
+
next
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
puts "Found #{count} resolved errors to delete"
|
|
250
|
+
|
|
251
|
+
# Confirm before proceeding
|
|
252
|
+
print "\nProceed with deletion? (y/N): "
|
|
253
|
+
confirmation = $stdin.gets.chomp.downcase
|
|
254
|
+
|
|
255
|
+
unless confirmation == 'y' || confirmation == 'yes'
|
|
256
|
+
puts "\n✗ Cleanup cancelled"
|
|
257
|
+
puts "\n" + "=" * 80 + "\n"
|
|
258
|
+
next
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
puts "\nDeleting errors..."
|
|
262
|
+
start_time = Time.current
|
|
263
|
+
deleted = scope.delete_all
|
|
264
|
+
elapsed = (Time.current - start_time).round(2)
|
|
265
|
+
|
|
266
|
+
puts "\n✓ Cleanup complete!"
|
|
267
|
+
puts " Deleted: #{deleted} errors"
|
|
268
|
+
puts " Time elapsed: #{elapsed} seconds"
|
|
269
|
+
|
|
270
|
+
puts "\n" + "=" * 80 + "\n"
|
|
271
|
+
end
|
|
272
|
+
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.1.
|
|
4
|
+
version: 0.1.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -290,6 +290,7 @@ files:
|
|
|
290
290
|
- app/jobs/rails_error_dashboard/webhook_error_notification_job.rb
|
|
291
291
|
- app/mailers/rails_error_dashboard/application_mailer.rb
|
|
292
292
|
- app/mailers/rails_error_dashboard/error_notification_mailer.rb
|
|
293
|
+
- app/models/rails_error_dashboard/application.rb
|
|
293
294
|
- app/models/rails_error_dashboard/cascade_pattern.rb
|
|
294
295
|
- app/models/rails_error_dashboard/error_baseline.rb
|
|
295
296
|
- app/models/rails_error_dashboard/error_comment.rb
|
|
@@ -327,6 +328,10 @@ files:
|
|
|
327
328
|
- db/migrate/20251226020100_create_error_comments.rb
|
|
328
329
|
- db/migrate/20251229111223_add_additional_performance_indexes.rb
|
|
329
330
|
- db/migrate/20251230075315_cleanup_orphaned_migrations.rb
|
|
331
|
+
- db/migrate/20260106094220_create_rails_error_dashboard_applications.rb
|
|
332
|
+
- db/migrate/20260106094233_add_application_to_error_logs.rb
|
|
333
|
+
- db/migrate/20260106094256_backfill_application_for_existing_errors.rb
|
|
334
|
+
- db/migrate/20260106094318_finalize_application_foreign_key.rb
|
|
330
335
|
- lib/generators/rails_error_dashboard/install/install_generator.rb
|
|
331
336
|
- lib/generators/rails_error_dashboard/install/templates/README
|
|
332
337
|
- lib/generators/rails_error_dashboard/install/templates/initializer.rb
|
|
@@ -371,6 +376,7 @@ files:
|
|
|
371
376
|
- lib/rails_error_dashboard/services/similarity_calculator.rb
|
|
372
377
|
- lib/rails_error_dashboard/value_objects/error_context.rb
|
|
373
378
|
- lib/rails_error_dashboard/version.rb
|
|
379
|
+
- lib/tasks/error_dashboard.rake
|
|
374
380
|
- lib/tasks/rails_error_dashboard_tasks.rake
|
|
375
381
|
homepage: https://github.com/AnjanJ/rails_error_dashboard
|
|
376
382
|
licenses:
|
|
@@ -380,7 +386,7 @@ metadata:
|
|
|
380
386
|
source_code_uri: https://github.com/AnjanJ/rails_error_dashboard
|
|
381
387
|
changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
|
|
382
388
|
post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
|
|
383
|
-
\ Rails Error Dashboard v0.1.
|
|
389
|
+
\ Rails Error Dashboard v0.1.22\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
|
|
384
390
|
First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
|
|
385
391
|
db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
|
|
386
392
|
=> '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n
|