rails_error_dashboard 0.1.0 → 0.1.1
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 +257 -700
- data/app/controllers/rails_error_dashboard/application_controller.rb +18 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +47 -4
- data/app/helpers/rails_error_dashboard/application_helper.rb +17 -0
- data/app/jobs/rails_error_dashboard/application_job.rb +19 -0
- data/app/jobs/rails_error_dashboard/async_error_logging_job.rb +48 -0
- data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +263 -0
- data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -8
- data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +2 -1
- data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +5 -5
- data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +10 -6
- data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +5 -6
- data/app/mailers/rails_error_dashboard/application_mailer.rb +1 -1
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +1 -1
- data/app/models/rails_error_dashboard/cascade_pattern.rb +74 -0
- data/app/models/rails_error_dashboard/error_baseline.rb +100 -0
- data/app/models/rails_error_dashboard/error_log.rb +326 -3
- data/app/models/rails_error_dashboard/error_occurrence.rb +49 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +150 -9
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +3 -10
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +1 -2
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +76 -0
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +209 -0
- data/app/views/rails_error_dashboard/errors/_stats.html.erb +34 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +19 -39
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +373 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +215 -138
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +388 -0
- data/app/views/rails_error_dashboard/errors/show.html.erb +428 -11
- data/config/routes.rb +2 -0
- data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +66 -0
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +26 -0
- data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +12 -0
- data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +9 -0
- data/db/migrate/20251225100236_create_error_occurrences.rb +31 -0
- data/db/migrate/20251225101920_create_cascade_patterns.rb +33 -0
- data/db/migrate/20251225102500_create_error_baselines.rb +38 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +270 -1
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +251 -37
- data/lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb +36 -0
- data/lib/generators/rails_error_dashboard/solid_queue/templates/queue.yml +55 -0
- data/lib/rails_error_dashboard/commands/log_error.rb +234 -7
- data/lib/rails_error_dashboard/commands/resolve_error.rb +16 -0
- data/lib/rails_error_dashboard/configuration.rb +82 -5
- data/lib/rails_error_dashboard/error_reporter.rb +15 -7
- data/lib/rails_error_dashboard/middleware/error_catcher.rb +17 -10
- data/lib/rails_error_dashboard/plugin.rb +6 -3
- data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +0 -1
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +2 -3
- data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +0 -2
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +44 -6
- data/lib/rails_error_dashboard/queries/baseline_stats.rb +107 -0
- data/lib/rails_error_dashboard/queries/co_occurring_errors.rb +86 -0
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +134 -2
- data/lib/rails_error_dashboard/queries/error_cascades.rb +74 -0
- data/lib/rails_error_dashboard/queries/error_correlation.rb +375 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +52 -11
- data/lib/rails_error_dashboard/queries/filter_options.rb +0 -1
- data/lib/rails_error_dashboard/queries/platform_comparison.rb +254 -0
- data/lib/rails_error_dashboard/queries/similar_errors.rb +93 -0
- data/lib/rails_error_dashboard/services/baseline_alert_throttler.rb +88 -0
- data/lib/rails_error_dashboard/services/baseline_calculator.rb +269 -0
- data/lib/rails_error_dashboard/services/cascade_detector.rb +95 -0
- data/lib/rails_error_dashboard/services/pattern_detector.rb +268 -0
- data/lib/rails_error_dashboard/services/similarity_calculator.rb +144 -0
- data/lib/rails_error_dashboard/value_objects/error_context.rb +27 -1
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +55 -7
- metadata +52 -9
- data/app/models/rails_error_dashboard/application_record.rb +0 -5
- data/lib/rails_error_dashboard/queries/developer_insights.rb +0 -277
- data/lib/rails_error_dashboard/queries/errors_list_v2.rb +0 -149
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.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0.21'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: turbo-rails
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '2.0'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '2.0'
|
|
82
96
|
- !ruby/object:Gem::Dependency
|
|
83
97
|
name: concurrent-ruby
|
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -225,10 +239,11 @@ dependencies:
|
|
|
225
239
|
- - "~>"
|
|
226
240
|
- !ruby/object:Gem::Version
|
|
227
241
|
version: '2.5'
|
|
228
|
-
description:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
242
|
+
description: 'Own your errors. Own your stack. A fully open-source, self-hosted error
|
|
243
|
+
dashboard for solo founders, indie hackers, and small teams. Professional error
|
|
244
|
+
tracking with beautiful UI, multi-channel notifications (Slack, Email, Discord,
|
|
245
|
+
PagerDuty), platform detection (iOS/Android/Web/API), and analytics. 5-minute setup,
|
|
246
|
+
works out-of-the-box. Rails 7.0-8.1 compatible. ⚠️ BETA: API may change before v1.0.0.'
|
|
232
247
|
email:
|
|
233
248
|
- anjan.jagirdar@gmail.com
|
|
234
249
|
executables: []
|
|
@@ -243,6 +258,8 @@ files:
|
|
|
243
258
|
- app/controllers/rails_error_dashboard/errors_controller.rb
|
|
244
259
|
- app/helpers/rails_error_dashboard/application_helper.rb
|
|
245
260
|
- app/jobs/rails_error_dashboard/application_job.rb
|
|
261
|
+
- app/jobs/rails_error_dashboard/async_error_logging_job.rb
|
|
262
|
+
- app/jobs/rails_error_dashboard/baseline_alert_job.rb
|
|
246
263
|
- app/jobs/rails_error_dashboard/discord_error_notification_job.rb
|
|
247
264
|
- app/jobs/rails_error_dashboard/email_error_notification_job.rb
|
|
248
265
|
- app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb
|
|
@@ -250,23 +267,39 @@ files:
|
|
|
250
267
|
- app/jobs/rails_error_dashboard/webhook_error_notification_job.rb
|
|
251
268
|
- app/mailers/rails_error_dashboard/application_mailer.rb
|
|
252
269
|
- app/mailers/rails_error_dashboard/error_notification_mailer.rb
|
|
253
|
-
- app/models/rails_error_dashboard/
|
|
270
|
+
- app/models/rails_error_dashboard/cascade_pattern.rb
|
|
271
|
+
- app/models/rails_error_dashboard/error_baseline.rb
|
|
254
272
|
- app/models/rails_error_dashboard/error_log.rb
|
|
255
273
|
- app/models/rails_error_dashboard/error_logs_record.rb
|
|
274
|
+
- app/models/rails_error_dashboard/error_occurrence.rb
|
|
256
275
|
- app/views/layouts/rails_error_dashboard.html.erb
|
|
257
276
|
- app/views/layouts/rails_error_dashboard/application.html.erb
|
|
258
277
|
- app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb
|
|
259
278
|
- app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb
|
|
279
|
+
- app/views/rails_error_dashboard/errors/_error_row.html.erb
|
|
280
|
+
- app/views/rails_error_dashboard/errors/_pattern_insights.html.erb
|
|
281
|
+
- app/views/rails_error_dashboard/errors/_stats.html.erb
|
|
260
282
|
- app/views/rails_error_dashboard/errors/analytics.html.erb
|
|
283
|
+
- app/views/rails_error_dashboard/errors/correlation.html.erb
|
|
261
284
|
- app/views/rails_error_dashboard/errors/index.html.erb
|
|
285
|
+
- app/views/rails_error_dashboard/errors/platform_comparison.html.erb
|
|
262
286
|
- app/views/rails_error_dashboard/errors/show.html.erb
|
|
263
287
|
- config/routes.rb
|
|
264
288
|
- db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb
|
|
265
289
|
- db/migrate/20251224081522_add_better_tracking_to_error_logs.rb
|
|
266
290
|
- db/migrate/20251224101217_add_controller_action_to_error_logs.rb
|
|
291
|
+
- db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb
|
|
292
|
+
- db/migrate/20251225074653_remove_environment_from_error_logs.rb
|
|
293
|
+
- db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb
|
|
294
|
+
- db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb
|
|
295
|
+
- db/migrate/20251225100236_create_error_occurrences.rb
|
|
296
|
+
- db/migrate/20251225101920_create_cascade_patterns.rb
|
|
297
|
+
- db/migrate/20251225102500_create_error_baselines.rb
|
|
267
298
|
- lib/generators/rails_error_dashboard/install/install_generator.rb
|
|
268
299
|
- lib/generators/rails_error_dashboard/install/templates/README
|
|
269
300
|
- lib/generators/rails_error_dashboard/install/templates/initializer.rb
|
|
301
|
+
- lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb
|
|
302
|
+
- lib/generators/rails_error_dashboard/solid_queue/templates/queue.yml
|
|
270
303
|
- lib/rails_error_dashboard.rb
|
|
271
304
|
- lib/rails_error_dashboard/commands/batch_delete_errors.rb
|
|
272
305
|
- lib/rails_error_dashboard/commands/batch_resolve_errors.rb
|
|
@@ -282,12 +315,21 @@ files:
|
|
|
282
315
|
- lib/rails_error_dashboard/plugins/jira_integration_plugin.rb
|
|
283
316
|
- lib/rails_error_dashboard/plugins/metrics_plugin.rb
|
|
284
317
|
- lib/rails_error_dashboard/queries/analytics_stats.rb
|
|
318
|
+
- lib/rails_error_dashboard/queries/baseline_stats.rb
|
|
319
|
+
- lib/rails_error_dashboard/queries/co_occurring_errors.rb
|
|
285
320
|
- lib/rails_error_dashboard/queries/dashboard_stats.rb
|
|
286
|
-
- lib/rails_error_dashboard/queries/
|
|
321
|
+
- lib/rails_error_dashboard/queries/error_cascades.rb
|
|
322
|
+
- lib/rails_error_dashboard/queries/error_correlation.rb
|
|
287
323
|
- lib/rails_error_dashboard/queries/errors_list.rb
|
|
288
|
-
- lib/rails_error_dashboard/queries/errors_list_v2.rb
|
|
289
324
|
- lib/rails_error_dashboard/queries/filter_options.rb
|
|
325
|
+
- lib/rails_error_dashboard/queries/platform_comparison.rb
|
|
326
|
+
- lib/rails_error_dashboard/queries/similar_errors.rb
|
|
327
|
+
- lib/rails_error_dashboard/services/baseline_alert_throttler.rb
|
|
328
|
+
- lib/rails_error_dashboard/services/baseline_calculator.rb
|
|
329
|
+
- lib/rails_error_dashboard/services/cascade_detector.rb
|
|
330
|
+
- lib/rails_error_dashboard/services/pattern_detector.rb
|
|
290
331
|
- lib/rails_error_dashboard/services/platform_detector.rb
|
|
332
|
+
- lib/rails_error_dashboard/services/similarity_calculator.rb
|
|
291
333
|
- lib/rails_error_dashboard/value_objects/error_context.rb
|
|
292
334
|
- lib/rails_error_dashboard/version.rb
|
|
293
335
|
- lib/tasks/rails_error_dashboard_tasks.rake
|
|
@@ -314,5 +356,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
314
356
|
requirements: []
|
|
315
357
|
rubygems_version: 3.7.2
|
|
316
358
|
specification_version: 4
|
|
317
|
-
summary:
|
|
359
|
+
summary: Self-hosted Rails error monitoring — free, forever. Zero SaaS fees, zero
|
|
360
|
+
lock-in.
|
|
318
361
|
test_files: []
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RailsErrorDashboard
|
|
4
|
-
module Queries
|
|
5
|
-
# Query: Developer-focused insights
|
|
6
|
-
# Provides actionable metrics instead of environment breakdowns
|
|
7
|
-
class DeveloperInsights
|
|
8
|
-
def self.call(days = 7)
|
|
9
|
-
new(days).call
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def initialize(days = 7)
|
|
13
|
-
@days = days
|
|
14
|
-
@start_date = days.days.ago
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def call
|
|
18
|
-
{
|
|
19
|
-
critical_metrics: critical_metrics,
|
|
20
|
-
error_trends: error_trends,
|
|
21
|
-
hot_spots: hot_spots,
|
|
22
|
-
platform_health: platform_health,
|
|
23
|
-
resolution_metrics: resolution_metrics,
|
|
24
|
-
error_velocity: error_velocity,
|
|
25
|
-
top_impacted_users: top_impacted_users,
|
|
26
|
-
recurring_issues: recurring_issues
|
|
27
|
-
}
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def base_query
|
|
33
|
-
@base_query ||= ErrorLog.where("occurred_at >= ?", @start_date)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Critical metrics developers care about
|
|
37
|
-
def critical_metrics
|
|
38
|
-
{
|
|
39
|
-
total_errors: base_query.count,
|
|
40
|
-
unresolved_count: base_query.unresolved.count,
|
|
41
|
-
critical_unresolved: base_query.unresolved.where(
|
|
42
|
-
error_type: critical_error_types
|
|
43
|
-
).count,
|
|
44
|
-
new_error_types: new_error_types_count,
|
|
45
|
-
recurring_errors: base_query.where("occurrence_count > ?", 5).count,
|
|
46
|
-
errors_last_hour: ErrorLog.where("occurred_at >= ?", 1.hour.ago).count,
|
|
47
|
-
errors_trending_up: errors_trending_up?
|
|
48
|
-
}
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Error trends over time
|
|
52
|
-
def error_trends
|
|
53
|
-
{
|
|
54
|
-
hourly: hourly_distribution,
|
|
55
|
-
daily: daily_distribution,
|
|
56
|
-
by_type_over_time: type_trends
|
|
57
|
-
}
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Hot spots - where errors are concentrated
|
|
61
|
-
def hot_spots
|
|
62
|
-
{
|
|
63
|
-
top_error_types: base_query.group(:error_type)
|
|
64
|
-
.order("count_id DESC")
|
|
65
|
-
.limit(10)
|
|
66
|
-
.count,
|
|
67
|
-
most_frequent: base_query.order(occurrence_count: :desc)
|
|
68
|
-
.limit(10)
|
|
69
|
-
.pluck(:error_type, :message, :occurrence_count)
|
|
70
|
-
.map { |type, msg, count|
|
|
71
|
-
{ error_type: type, message: msg.truncate(100), count: count }
|
|
72
|
-
},
|
|
73
|
-
recent_spikes: detect_spikes
|
|
74
|
-
}
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Platform health breakdown
|
|
78
|
-
def platform_health
|
|
79
|
-
platforms = base_query.group(:platform).count
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
by_platform: platforms,
|
|
83
|
-
ios_stability: calculate_stability("iOS"),
|
|
84
|
-
android_stability: calculate_stability("Android"),
|
|
85
|
-
api_stability: calculate_stability("API")
|
|
86
|
-
}
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Resolution metrics
|
|
90
|
-
def resolution_metrics
|
|
91
|
-
total = base_query.count
|
|
92
|
-
resolved = base_query.resolved.count
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
resolution_rate: total.zero? ? 0 : (resolved.to_f / total * 100).round(2),
|
|
96
|
-
average_resolution_time: average_resolution_time,
|
|
97
|
-
unresolved_age: unresolved_age_distribution,
|
|
98
|
-
resolved_today: ErrorLog.resolved
|
|
99
|
-
.where("resolved_at >= ?", Time.current.beginning_of_day)
|
|
100
|
-
.count
|
|
101
|
-
}
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Error velocity - how fast errors are being introduced
|
|
105
|
-
def error_velocity
|
|
106
|
-
current_period = base_query.count
|
|
107
|
-
previous_period = ErrorLog.where(
|
|
108
|
-
"occurred_at >= ? AND occurred_at < ?",
|
|
109
|
-
(@days * 2).days.ago,
|
|
110
|
-
@start_date
|
|
111
|
-
).count
|
|
112
|
-
|
|
113
|
-
change = current_period - previous_period
|
|
114
|
-
change_percent = previous_period.zero? ? 0 : (change.to_f / previous_period * 100).round(2)
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
current_period_count: current_period,
|
|
118
|
-
previous_period_count: previous_period,
|
|
119
|
-
change: change,
|
|
120
|
-
change_percent: change_percent,
|
|
121
|
-
trend: change >= 0 ? "increasing" : "decreasing"
|
|
122
|
-
}
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Users most impacted by errors
|
|
126
|
-
def top_impacted_users
|
|
127
|
-
base_query.where.not(user_id: nil)
|
|
128
|
-
.group(:user_id)
|
|
129
|
-
.order("count_id DESC")
|
|
130
|
-
.limit(10)
|
|
131
|
-
.count
|
|
132
|
-
.transform_keys { |user_id| user_id || "Guest" }
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Recurring issues that keep coming back
|
|
136
|
-
def recurring_issues
|
|
137
|
-
base_query.where("occurrence_count > ?", 3)
|
|
138
|
-
.where("(last_seen_at - first_seen_at) > ?", 1.day.to_i)
|
|
139
|
-
.order(occurrence_count: :desc)
|
|
140
|
-
.limit(10)
|
|
141
|
-
.pluck(:error_type, :message, :occurrence_count, :first_seen_at, :last_seen_at)
|
|
142
|
-
.map { |type, msg, count, first, last|
|
|
143
|
-
{
|
|
144
|
-
error_type: type,
|
|
145
|
-
message: msg.truncate(100),
|
|
146
|
-
occurrence_count: count,
|
|
147
|
-
duration_days: ((last - first) / 1.day).round(1),
|
|
148
|
-
first_seen: first,
|
|
149
|
-
last_seen: last
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Helper methods
|
|
155
|
-
|
|
156
|
-
def critical_error_types
|
|
157
|
-
%w[
|
|
158
|
-
SecurityError
|
|
159
|
-
NoMemoryError
|
|
160
|
-
SystemStackError
|
|
161
|
-
SignalException
|
|
162
|
-
ActiveRecord::StatementInvalid
|
|
163
|
-
]
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def new_error_types_count
|
|
167
|
-
# Error types that first appeared in this period
|
|
168
|
-
current_types = base_query.distinct.pluck(:error_type)
|
|
169
|
-
all_time_types = ErrorLog.where("occurred_at < ?", @start_date)
|
|
170
|
-
.distinct
|
|
171
|
-
.pluck(:error_type)
|
|
172
|
-
|
|
173
|
-
(current_types - all_time_types).count
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def errors_trending_up?
|
|
177
|
-
last_24h = ErrorLog.where("occurred_at >= ?", 24.hours.ago).count
|
|
178
|
-
prev_24h = ErrorLog.where("occurred_at >= ? AND occurred_at < ?",
|
|
179
|
-
48.hours.ago, 24.hours.ago).count
|
|
180
|
-
|
|
181
|
-
last_24h > prev_24h
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def hourly_distribution
|
|
185
|
-
base_query.group("EXTRACT(HOUR FROM occurred_at)")
|
|
186
|
-
.order("EXTRACT(HOUR FROM occurred_at)")
|
|
187
|
-
.count
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def daily_distribution
|
|
191
|
-
base_query.group("DATE(occurred_at)")
|
|
192
|
-
.order("DATE(occurred_at)")
|
|
193
|
-
.count
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def type_trends
|
|
197
|
-
# Get top 5 error types and their trend over days
|
|
198
|
-
top_types = base_query.group(:error_type)
|
|
199
|
-
.order("count_id DESC")
|
|
200
|
-
.limit(5)
|
|
201
|
-
.pluck(:error_type)
|
|
202
|
-
|
|
203
|
-
trends = {}
|
|
204
|
-
top_types.each do |error_type|
|
|
205
|
-
trends[error_type] = base_query.where(error_type: error_type)
|
|
206
|
-
.group("DATE(occurred_at)")
|
|
207
|
-
.count
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
trends
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def calculate_stability(platform)
|
|
214
|
-
total = base_query.where(platform: platform).count
|
|
215
|
-
return 100.0 if total.zero?
|
|
216
|
-
|
|
217
|
-
# Stability = 100 - (errors per 1000 requests ratio)
|
|
218
|
-
# Simplified: just show error rate
|
|
219
|
-
100.0 - [ (total.to_f / 10), 100.0 ].min
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
def average_resolution_time
|
|
223
|
-
resolved_errors = base_query.resolved
|
|
224
|
-
.where.not(resolved_at: nil)
|
|
225
|
-
|
|
226
|
-
return 0 if resolved_errors.count.zero?
|
|
227
|
-
|
|
228
|
-
total_time = resolved_errors.sum { |error|
|
|
229
|
-
(error.resolved_at - error.occurred_at).to_i
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
average_seconds = total_time / resolved_errors.count
|
|
233
|
-
(average_seconds / 3600.0).round(2) # Convert to hours
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def unresolved_age_distribution
|
|
237
|
-
unresolved = base_query.unresolved
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
under_1_hour: unresolved.where("occurred_at >= ?", 1.hour.ago).count,
|
|
241
|
-
"1_24_hours": unresolved.where("occurred_at >= ? AND occurred_at < ?",
|
|
242
|
-
24.hours.ago, 1.hour.ago).count,
|
|
243
|
-
"1_7_days": unresolved.where("occurred_at >= ? AND occurred_at < ?",
|
|
244
|
-
7.days.ago, 24.hours.ago).count,
|
|
245
|
-
over_7_days: unresolved.where("occurred_at < ?", 7.days.ago).count
|
|
246
|
-
}
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def detect_spikes
|
|
250
|
-
# Find error types that suddenly spiked in last 24 hours
|
|
251
|
-
last_24h = ErrorLog.where("occurred_at >= ?", 24.hours.ago)
|
|
252
|
-
prev_24h = ErrorLog.where("occurred_at >= ? AND occurred_at < ?",
|
|
253
|
-
48.hours.ago, 24.hours.ago)
|
|
254
|
-
|
|
255
|
-
current_counts = last_24h.group(:error_type).count
|
|
256
|
-
previous_counts = prev_24h.group(:error_type).count
|
|
257
|
-
|
|
258
|
-
spikes = []
|
|
259
|
-
current_counts.each do |error_type, current_count|
|
|
260
|
-
previous_count = previous_counts[error_type] || 0
|
|
261
|
-
|
|
262
|
-
# Spike if current is > 2x previous AND at least 5 errors
|
|
263
|
-
if current_count > previous_count * 2 && current_count >= 5
|
|
264
|
-
spikes << {
|
|
265
|
-
error_type: error_type,
|
|
266
|
-
current_count: current_count,
|
|
267
|
-
previous_count: previous_count,
|
|
268
|
-
increase_percent: previous_count.zero? ? 999 : ((current_count - previous_count).to_f / previous_count * 100).round(0)
|
|
269
|
-
}
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
spikes.sort_by { |s| -s[:increase_percent] }.first(5)
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
end
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RailsErrorDashboard
|
|
4
|
-
module Queries
|
|
5
|
-
# Query: Fetch errors with improved filtering for developers
|
|
6
|
-
# Removes environment filtering (each env has separate DB)
|
|
7
|
-
# Adds time-based, severity, and frequency filtering
|
|
8
|
-
class ErrorsListV2
|
|
9
|
-
def self.call(filters = {})
|
|
10
|
-
new(filters).call
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def initialize(filters = {})
|
|
14
|
-
@filters = filters
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def call
|
|
18
|
-
query = ErrorLog.order(occurred_at: :desc)
|
|
19
|
-
# Only eager load user if User model exists
|
|
20
|
-
query = query.includes(:user) if defined?(::User)
|
|
21
|
-
query = apply_filters(query)
|
|
22
|
-
query
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def apply_filters(query)
|
|
28
|
-
query = filter_by_timeframe(query)
|
|
29
|
-
query = filter_by_error_type(query)
|
|
30
|
-
query = filter_by_resolved(query)
|
|
31
|
-
query = filter_by_platform(query)
|
|
32
|
-
query = filter_by_severity(query)
|
|
33
|
-
query = filter_by_frequency(query)
|
|
34
|
-
query = filter_by_search(query)
|
|
35
|
-
query
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Time-based filtering (more useful than environment)
|
|
39
|
-
def filter_by_timeframe(query)
|
|
40
|
-
return query unless @filters[:timeframe].present?
|
|
41
|
-
|
|
42
|
-
case @filters[:timeframe]
|
|
43
|
-
when "last_hour"
|
|
44
|
-
query.where("occurred_at >= ?", 1.hour.ago)
|
|
45
|
-
when "today"
|
|
46
|
-
query.where("occurred_at >= ?", Time.current.beginning_of_day)
|
|
47
|
-
when "yesterday"
|
|
48
|
-
query.where("occurred_at >= ? AND occurred_at < ?",
|
|
49
|
-
1.day.ago.beginning_of_day,
|
|
50
|
-
Time.current.beginning_of_day)
|
|
51
|
-
when "last_7_days"
|
|
52
|
-
query.where("occurred_at >= ?", 7.days.ago)
|
|
53
|
-
when "last_30_days"
|
|
54
|
-
query.where("occurred_at >= ?", 30.days.ago)
|
|
55
|
-
when "last_90_days"
|
|
56
|
-
query.where("occurred_at >= ?", 90.days.ago)
|
|
57
|
-
else
|
|
58
|
-
query
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def filter_by_error_type(query)
|
|
63
|
-
return query unless @filters[:error_type].present?
|
|
64
|
-
|
|
65
|
-
query.where(error_type: @filters[:error_type])
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def filter_by_resolved(query)
|
|
69
|
-
return query unless @filters[:unresolved] == "true" || @filters[:unresolved] == true
|
|
70
|
-
|
|
71
|
-
query.unresolved
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def filter_by_platform(query)
|
|
75
|
-
return query unless @filters[:platform].present?
|
|
76
|
-
|
|
77
|
-
query.where(platform: @filters[:platform])
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Filter by severity (based on error type)
|
|
81
|
-
def filter_by_severity(query)
|
|
82
|
-
return query unless @filters[:severity].present?
|
|
83
|
-
|
|
84
|
-
case @filters[:severity]
|
|
85
|
-
when "critical"
|
|
86
|
-
# Security, data loss, crashes
|
|
87
|
-
critical_errors = [
|
|
88
|
-
"SecurityError",
|
|
89
|
-
"ActiveRecord::RecordInvalid",
|
|
90
|
-
"NoMemoryError",
|
|
91
|
-
"SystemStackError",
|
|
92
|
-
"SignalException"
|
|
93
|
-
]
|
|
94
|
-
query.where(error_type: critical_errors)
|
|
95
|
-
when "high"
|
|
96
|
-
# Business logic failures
|
|
97
|
-
high_errors = [
|
|
98
|
-
"ActiveRecord::RecordNotFound",
|
|
99
|
-
"ArgumentError",
|
|
100
|
-
"TypeError",
|
|
101
|
-
"NoMethodError"
|
|
102
|
-
]
|
|
103
|
-
query.where(error_type: high_errors)
|
|
104
|
-
when "medium"
|
|
105
|
-
# Validation, timeouts
|
|
106
|
-
medium_errors = [
|
|
107
|
-
"ActiveRecord::RecordInvalid",
|
|
108
|
-
"Timeout::Error",
|
|
109
|
-
"Net::ReadTimeout"
|
|
110
|
-
]
|
|
111
|
-
query.where(error_type: medium_errors)
|
|
112
|
-
else
|
|
113
|
-
query
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Filter by frequency (how often error occurs)
|
|
118
|
-
def filter_by_frequency(query)
|
|
119
|
-
return query unless @filters[:frequency].present?
|
|
120
|
-
|
|
121
|
-
case @filters[:frequency]
|
|
122
|
-
when "high"
|
|
123
|
-
# Occurs more than 10 times
|
|
124
|
-
query.where("occurrence_count > ?", 10)
|
|
125
|
-
when "medium"
|
|
126
|
-
# Occurs 3-10 times
|
|
127
|
-
query.where("occurrence_count >= ? AND occurrence_count <= ?", 3, 10)
|
|
128
|
-
when "low"
|
|
129
|
-
# Occurs 1-2 times
|
|
130
|
-
query.where("occurrence_count <= ?", 2)
|
|
131
|
-
when "recurring"
|
|
132
|
-
# Seen multiple times over more than 1 hour
|
|
133
|
-
query.where("occurrence_count > ? AND (last_seen_at - first_seen_at) > ?",
|
|
134
|
-
1, 1.hour.to_i)
|
|
135
|
-
else
|
|
136
|
-
query
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def filter_by_search(query)
|
|
141
|
-
return query unless @filters[:search].present?
|
|
142
|
-
|
|
143
|
-
search_term = "%#{@filters[:search]}%"
|
|
144
|
-
query.where("message ILIKE ? OR error_type ILIKE ? OR backtrace ILIKE ?",
|
|
145
|
-
search_term, search_term, search_term)
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
end
|