rails_error_dashboard 0.1.0

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +858 -0
  4. data/Rakefile +3 -0
  5. data/app/assets/stylesheets/rails_error_dashboard/application.css +15 -0
  6. data/app/controllers/rails_error_dashboard/application_controller.rb +12 -0
  7. data/app/controllers/rails_error_dashboard/errors_controller.rb +123 -0
  8. data/app/helpers/rails_error_dashboard/application_helper.rb +4 -0
  9. data/app/jobs/rails_error_dashboard/application_job.rb +4 -0
  10. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +116 -0
  11. data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +19 -0
  12. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +105 -0
  13. data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +166 -0
  14. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +108 -0
  15. data/app/mailers/rails_error_dashboard/application_mailer.rb +8 -0
  16. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +27 -0
  17. data/app/models/rails_error_dashboard/application_record.rb +5 -0
  18. data/app/models/rails_error_dashboard/error_log.rb +185 -0
  19. data/app/models/rails_error_dashboard/error_logs_record.rb +34 -0
  20. data/app/views/layouts/rails_error_dashboard/application.html.erb +17 -0
  21. data/app/views/layouts/rails_error_dashboard.html.erb +351 -0
  22. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +200 -0
  23. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +32 -0
  24. data/app/views/rails_error_dashboard/errors/analytics.html.erb +237 -0
  25. data/app/views/rails_error_dashboard/errors/index.html.erb +334 -0
  26. data/app/views/rails_error_dashboard/errors/show.html.erb +294 -0
  27. data/config/routes.rb +13 -0
  28. data/db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb +40 -0
  29. data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +13 -0
  30. data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +10 -0
  31. data/lib/generators/rails_error_dashboard/install/install_generator.rb +27 -0
  32. data/lib/generators/rails_error_dashboard/install/templates/README +33 -0
  33. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +64 -0
  34. data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +40 -0
  35. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +60 -0
  36. data/lib/rails_error_dashboard/commands/log_error.rb +134 -0
  37. data/lib/rails_error_dashboard/commands/resolve_error.rb +35 -0
  38. data/lib/rails_error_dashboard/configuration.rb +83 -0
  39. data/lib/rails_error_dashboard/engine.rb +20 -0
  40. data/lib/rails_error_dashboard/error_reporter.rb +35 -0
  41. data/lib/rails_error_dashboard/middleware/error_catcher.rb +41 -0
  42. data/lib/rails_error_dashboard/plugin.rb +98 -0
  43. data/lib/rails_error_dashboard/plugin_registry.rb +88 -0
  44. data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +96 -0
  45. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +122 -0
  46. data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +78 -0
  47. data/lib/rails_error_dashboard/queries/analytics_stats.rb +108 -0
  48. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +37 -0
  49. data/lib/rails_error_dashboard/queries/developer_insights.rb +277 -0
  50. data/lib/rails_error_dashboard/queries/errors_list.rb +66 -0
  51. data/lib/rails_error_dashboard/queries/errors_list_v2.rb +149 -0
  52. data/lib/rails_error_dashboard/queries/filter_options.rb +21 -0
  53. data/lib/rails_error_dashboard/services/platform_detector.rb +41 -0
  54. data/lib/rails_error_dashboard/value_objects/error_context.rb +148 -0
  55. data/lib/rails_error_dashboard/version.rb +3 -0
  56. data/lib/rails_error_dashboard.rb +60 -0
  57. data/lib/tasks/rails_error_dashboard_tasks.rake +4 -0
  58. metadata +318 -0
@@ -0,0 +1,277 @@
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
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Queries
5
+ # Query: Fetch errors with filtering and pagination
6
+ # This is a read operation that returns a filtered collection of errors
7
+ class ErrorsList
8
+ def self.call(filters = {})
9
+ new(filters).call
10
+ end
11
+
12
+ def initialize(filters = {})
13
+ @filters = filters
14
+ end
15
+
16
+ def call
17
+ query = ErrorLog.order(occurred_at: :desc)
18
+ # Only eager load user if User model exists
19
+ query = query.includes(:user) if defined?(::User)
20
+ query = apply_filters(query)
21
+ query
22
+ end
23
+
24
+ private
25
+
26
+ def apply_filters(query)
27
+ query = filter_by_environment(query)
28
+ query = filter_by_error_type(query)
29
+ query = filter_by_resolved(query)
30
+ query = filter_by_platform(query)
31
+ query = filter_by_search(query)
32
+ query
33
+ end
34
+
35
+ def filter_by_environment(query)
36
+ return query unless @filters[:environment].present?
37
+
38
+ query.where(environment: @filters[:environment])
39
+ end
40
+
41
+ def filter_by_error_type(query)
42
+ return query unless @filters[:error_type].present?
43
+
44
+ query.where(error_type: @filters[:error_type])
45
+ end
46
+
47
+ def filter_by_resolved(query)
48
+ return query unless @filters[:unresolved] == "true" || @filters[:unresolved] == true
49
+
50
+ query.unresolved
51
+ end
52
+
53
+ def filter_by_platform(query)
54
+ return query unless @filters[:platform].present?
55
+
56
+ query.where(platform: @filters[:platform])
57
+ end
58
+
59
+ def filter_by_search(query)
60
+ return query unless @filters[:search].present?
61
+
62
+ query.where("message ILIKE ?", "%#{@filters[:search]}%")
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,149 @@
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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Queries
5
+ # Query: Fetch available filter options
6
+ # This is a read operation that returns distinct values for filters
7
+ class FilterOptions
8
+ def self.call
9
+ new.call
10
+ end
11
+
12
+ def call
13
+ {
14
+ environments: ErrorLog.distinct.pluck(:environment).compact,
15
+ error_types: ErrorLog.distinct.pluck(:error_type).compact.sort,
16
+ platforms: ErrorLog.distinct.pluck(:platform).compact
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "browser"
4
+
5
+ module RailsErrorDashboard
6
+ module Services
7
+ # Detects the platform (iOS/Android/API) from user agent string
8
+ class PlatformDetector
9
+ def self.detect(user_agent)
10
+ new(user_agent).detect
11
+ end
12
+
13
+ def initialize(user_agent)
14
+ @user_agent = user_agent
15
+ end
16
+
17
+ def detect
18
+ return "API" if @user_agent.blank?
19
+
20
+ browser = Browser.new(@user_agent)
21
+
22
+ if browser.device.iphone? || browser.device.ipad?
23
+ "iOS"
24
+ elsif browser.platform.android?
25
+ "Android"
26
+ elsif @user_agent&.include?("Expo")
27
+ # Expo apps might have specific patterns
28
+ if @user_agent.include?("iOS")
29
+ "iOS"
30
+ elsif @user_agent.include?("Android")
31
+ "Android"
32
+ else
33
+ "Mobile"
34
+ end
35
+ else
36
+ "API"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module ValueObjects
5
+ # Immutable value object representing error context
6
+ # Extracts and normalizes context information from various sources
7
+ class ErrorContext
8
+ attr_reader :user_id, :request_url, :request_params, :user_agent, :ip_address, :platform,
9
+ :controller_name, :action_name
10
+
11
+ def initialize(context, source = nil)
12
+ @context = context
13
+ @source = source
14
+
15
+ @user_id = extract_user_id
16
+ @request_url = build_request_url
17
+ @request_params = extract_params
18
+ @user_agent = extract_user_agent
19
+ @ip_address = extract_ip_address
20
+ @platform = detect_platform
21
+ @controller_name = extract_controller_name
22
+ @action_name = extract_action_name
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ user_id: user_id,
28
+ request_url: request_url,
29
+ request_params: request_params,
30
+ user_agent: user_agent,
31
+ ip_address: ip_address,
32
+ platform: platform,
33
+ controller_name: controller_name,
34
+ action_name: action_name
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ def extract_user_id
41
+ @context[:current_user]&.id ||
42
+ @context[:user_id] ||
43
+ @context[:user]&.id
44
+ end
45
+
46
+ def build_request_url
47
+ return @context[:request]&.fullpath if @context[:request]
48
+ return @context[:request_url] if @context[:request_url]
49
+ return "Background Job: #{@context[:job]&.class}" if @context[:job]
50
+ return "Sidekiq: #{@context[:job_class]}" if @context[:job_class]
51
+ return "Service: #{@context[:service]}" if @context[:service]
52
+ return @source if @source
53
+
54
+ "Rails Application"
55
+ end
56
+
57
+ def extract_params
58
+ params = {}
59
+
60
+ # HTTP request params
61
+ if @context[:request]
62
+ params = @context[:request].params.except(:controller, :action)
63
+ end
64
+
65
+ # Background job params
66
+ if @context[:job]
67
+ params = {
68
+ job_class: @context[:job].class.name,
69
+ job_id: @context[:job].job_id,
70
+ queue: @context[:job].queue_name,
71
+ arguments: @context[:job].arguments,
72
+ executions: @context[:job].executions
73
+ }
74
+ end
75
+
76
+ # Sidekiq params
77
+ if @context[:job_class]
78
+ params = {
79
+ job_class: @context[:job_class],
80
+ job_id: @context[:jid],
81
+ queue: @context[:queue],
82
+ retry_count: @context[:retry_count]
83
+ }
84
+ end
85
+
86
+ # Custom params
87
+ params.merge!(@context[:params]) if @context[:params]
88
+
89
+ # Additional context (from mobile apps, etc.)
90
+ params.merge!(@context[:additional_context]) if @context[:additional_context]
91
+
92
+ params.to_json
93
+ end
94
+
95
+ def extract_user_agent
96
+ return @context[:request]&.user_agent if @context[:request]
97
+ return "Sidekiq Worker" if @source&.to_s&.include?("active_job") || @context[:job]
98
+ return @context[:user_agent] if @context[:user_agent]
99
+
100
+ "Rails Application"
101
+ end
102
+
103
+ def extract_ip_address
104
+ return @context[:request]&.remote_ip if @context[:request]
105
+ return "background_job" if @context[:job]
106
+ return "sidekiq_worker" if @context[:job_class]
107
+ return @context[:ip_address] if @context[:ip_address]
108
+
109
+ "application_layer"
110
+ end
111
+
112
+ def detect_platform
113
+ # Check if it's from a mobile request
114
+ user_agent = extract_user_agent
115
+ return Services::PlatformDetector.detect(user_agent) if @context[:request]
116
+
117
+ # Everything else is API/backend
118
+ "API"
119
+ end
120
+
121
+ def extract_controller_name
122
+ # From Rails request params
123
+ return @context[:request].params[:controller] if @context[:request]&.params&.[](:controller)
124
+
125
+ # From explicit context
126
+ return @context[:controller_name] if @context[:controller_name]
127
+
128
+ # From Rails controller instance
129
+ return @context[:controller]&.class&.name if @context[:controller]
130
+
131
+ nil
132
+ end
133
+
134
+ def extract_action_name
135
+ # From Rails request params
136
+ return @context[:request].params[:action] if @context[:request]&.params&.[](:action)
137
+
138
+ # From explicit context
139
+ return @context[:action_name] if @context[:action_name]
140
+
141
+ # From action parameter
142
+ return @context[:action] if @context[:action]
143
+
144
+ nil
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,3 @@
1
+ module RailsErrorDashboard
2
+ VERSION = "0.1.0"
3
+ end