rails_error_dashboard 0.1.37 → 0.2.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -4
  3. data/app/controllers/rails_error_dashboard/application_controller.rb +2 -5
  4. data/app/controllers/rails_error_dashboard/errors_controller.rb +2 -3
  5. data/app/jobs/rails_error_dashboard/async_error_logging_job.rb +10 -0
  6. data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +19 -15
  7. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +19 -9
  8. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +37 -11
  9. data/app/jobs/rails_error_dashboard/retention_cleanup_job.rb +44 -0
  10. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +38 -16
  11. data/app/models/rails_error_dashboard/error_log.rb +10 -0
  12. data/app/models/rails_error_dashboard/error_logs_record.rb +11 -6
  13. data/app/views/layouts/rails_error_dashboard.html.erb +16 -0
  14. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +3 -0
  15. data/app/views/rails_error_dashboard/errors/_stats.html.erb +12 -4
  16. data/app/views/rails_error_dashboard/errors/index.html.erb +9 -7
  17. data/app/views/rails_error_dashboard/errors/show.html.erb +138 -7
  18. data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +36 -0
  19. data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +1 -1
  20. data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +1 -1
  21. data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +1 -1
  22. data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +1 -1
  23. data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +1 -1
  24. data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +1 -1
  25. data/db/migrate/20251225100236_create_error_occurrences.rb +1 -1
  26. data/db/migrate/20251225101920_create_cascade_patterns.rb +1 -1
  27. data/db/migrate/20251225102500_create_error_baselines.rb +1 -1
  28. data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +1 -1
  29. data/db/migrate/20251226020100_create_error_comments.rb +1 -1
  30. data/db/migrate/20251230075315_cleanup_orphaned_migrations.rb +1 -1
  31. data/db/migrate/20260220000001_add_exception_cause_to_error_logs.rb +9 -0
  32. data/db/migrate/20260220000002_add_enriched_context_to_error_logs.rb +12 -0
  33. data/db/migrate/20260220000003_add_time_series_indexes_to_error_logs.rb +67 -0
  34. data/db/migrate/20260221000001_add_environment_info_to_error_logs.rb +9 -0
  35. data/db/migrate/20260221000002_add_reopened_at_to_error_logs.rb +9 -0
  36. data/lib/generators/rails_error_dashboard/install/install_generator.rb +145 -24
  37. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +12 -8
  38. data/lib/rails_error_dashboard/commands/find_or_increment_error.rb +58 -10
  39. data/lib/rails_error_dashboard/commands/log_error.rb +109 -10
  40. data/lib/rails_error_dashboard/configuration.rb +52 -0
  41. data/lib/rails_error_dashboard/manual_error_reporter.rb +12 -0
  42. data/lib/rails_error_dashboard/middleware/error_catcher.rb +3 -0
  43. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +8 -0
  44. data/lib/rails_error_dashboard/queries/errors_list.rb +8 -0
  45. data/lib/rails_error_dashboard/services/backtrace_parser.rb +31 -0
  46. data/lib/rails_error_dashboard/services/backtrace_processor.rb +31 -1
  47. data/lib/rails_error_dashboard/services/cause_chain_extractor.rb +62 -0
  48. data/lib/rails_error_dashboard/services/environment_snapshot.rb +85 -0
  49. data/lib/rails_error_dashboard/services/error_hash_generator.rb +50 -2
  50. data/lib/rails_error_dashboard/services/notification_throttler.rb +109 -0
  51. data/lib/rails_error_dashboard/services/platform_detector.rb +36 -11
  52. data/lib/rails_error_dashboard/services/sensitive_data_filter.rb +176 -0
  53. data/lib/rails_error_dashboard/value_objects/error_context.rb +81 -4
  54. data/lib/rails_error_dashboard/version.rb +1 -1
  55. data/lib/rails_error_dashboard.rb +11 -6
  56. data/lib/tasks/error_dashboard.rake +158 -2
  57. metadata +14 -60
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Services
5
+ # Pure algorithm: Filter sensitive data from error attributes before storage
6
+ #
7
+ # On by default. Redacts passwords, tokens, credit cards, SSNs, etc. using
8
+ # built-in defaults + Rails' filter_parameters + custom patterns.
9
+ # Set filter_sensitive_data = false to store raw data (you own your database).
10
+ class SensitiveDataFilter
11
+ FILTERED_REPLACEMENT = "[FILTERED]"
12
+
13
+ # Default patterns that ALWAYS apply when filtering is enabled.
14
+ # These cover data that has no debugging value and should never be stored.
15
+ DEFAULT_SENSITIVE_PATTERNS = [
16
+ # Passwords
17
+ :password, :password_confirmation, :passphrase, :passwd,
18
+ # API keys & tokens
19
+ :token, :access_token, :refresh_token, :auth_token, :api_token,
20
+ :api_key, :api_secret, :secret, :secret_key, :private_key,
21
+ # Financial
22
+ :credit_card, :card_number, :cc_number, :cvv, :cvc, :csv,
23
+ # Personal identifiers
24
+ :ssn, :social_security,
25
+ # Session & auth
26
+ :session_id, :session_key, :cookie,
27
+ # 2FA / OTP
28
+ :otp, :totp, :pin
29
+ ].freeze
30
+
31
+ # Regex to detect credit card numbers in free text (4 groups of 4 digits)
32
+ CREDIT_CARD_REGEX = /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/
33
+
34
+ # Filter sensitive data from error attributes hash
35
+ # @param attributes [Hash] Error attributes to filter
36
+ # @return [Hash] Filtered attributes (or original if filtering disabled/fails)
37
+ def self.filter_attributes(attributes)
38
+ return attributes unless RailsErrorDashboard.configuration.filter_sensitive_data
39
+
40
+ filter = parameter_filter
41
+ return attributes unless filter
42
+
43
+ filtered = attributes.dup
44
+ filtered[:request_params] = filter_json_string(filter, filtered[:request_params])
45
+ filtered[:request_url] = filter_url(filter, filtered[:request_url])
46
+ filtered[:message] = filter_message(filter, filtered[:message])
47
+ filtered[:exception_cause] = filter_cause_chain(filter, filtered[:exception_cause])
48
+ filtered
49
+ rescue => e
50
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] SensitiveDataFilter failed: #{e.message}")
51
+ attributes
52
+ end
53
+
54
+ # Build and cache the ParameterFilter instance
55
+ # @return [ActiveSupport::ParameterFilter, nil]
56
+ def self.parameter_filter
57
+ @parameter_filter ||= build_parameter_filter
58
+ end
59
+
60
+ # Clear cached filter (for testing or config changes)
61
+ def self.reset!
62
+ @parameter_filter = nil
63
+ end
64
+
65
+ # Filter a JSON string by parsing, filtering the hash, and re-serializing
66
+ # @param filter [ActiveSupport::ParameterFilter] The filter instance
67
+ # @param json_string [String, nil] JSON string to filter
68
+ # @return [String, nil] Filtered JSON string
69
+ def self.filter_json_string(filter, json_string)
70
+ return json_string if json_string.nil? || json_string.empty?
71
+
72
+ parsed = JSON.parse(json_string)
73
+ filtered = filter.filter(parsed)
74
+ filtered.to_json
75
+ rescue JSON::ParserError
76
+ json_string
77
+ rescue => e
78
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] filter_json_string failed: #{e.message}")
79
+ json_string
80
+ end
81
+ private_class_method :filter_json_string
82
+
83
+ # Filter query string parameters in a URL
84
+ # @param filter [ActiveSupport::ParameterFilter] The filter instance
85
+ # @param url [String, nil] URL to filter
86
+ # @return [String, nil] URL with filtered query parameters
87
+ def self.filter_url(filter, url)
88
+ return url if url.nil? || !url.include?("?")
89
+
90
+ path, query = url.split("?", 2)
91
+ return url if query.nil? || query.empty?
92
+
93
+ params = Rack::Utils.parse_query(query)
94
+ filtered_params = filter.filter(params)
95
+ filtered_query = Rack::Utils.build_query(filtered_params)
96
+ "#{path}?#{filtered_query}"
97
+ rescue => e
98
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] filter_url failed: #{e.message}")
99
+ url
100
+ end
101
+ private_class_method :filter_url
102
+
103
+ # Filter key=value patterns in a message string
104
+ # @param filter [ActiveSupport::ParameterFilter] The filter instance
105
+ # @param message [String, nil] Message to filter
106
+ # @return [String, nil] Message with filtered values
107
+ def self.filter_message(filter, message)
108
+ return message if message.nil? || message.empty?
109
+
110
+ # Extract key=value patterns and filter them
111
+ result = message.gsub(/(\w+)=(\S+)/) do |_match|
112
+ key = Regexp.last_match(1)
113
+ value = Regexp.last_match(2)
114
+ filtered = filter.filter(key => value)
115
+ "#{key}=#{filtered[key]}"
116
+ end
117
+
118
+ # Scrub credit card numbers from free text
119
+ result.gsub(CREDIT_CARD_REGEX, FILTERED_REPLACEMENT)
120
+ rescue => e
121
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] filter_message failed: #{e.message}")
122
+ message
123
+ end
124
+ private_class_method :filter_message
125
+
126
+ # Filter messages within a cause chain JSON string
127
+ # @param filter [ActiveSupport::ParameterFilter] The filter instance
128
+ # @param cause_json [String, nil] JSON cause chain to filter
129
+ # @return [String, nil] Filtered cause chain JSON
130
+ def self.filter_cause_chain(filter, cause_json)
131
+ return cause_json if cause_json.nil? || cause_json.empty?
132
+
133
+ chain = JSON.parse(cause_json)
134
+ return cause_json unless chain.is_a?(Array)
135
+
136
+ filtered_chain = chain.map do |cause|
137
+ cause = cause.dup
138
+ cause["message"] = filter_message(filter, cause["message"]) if cause["message"]
139
+ cause
140
+ end
141
+
142
+ filtered_chain.to_json
143
+ rescue JSON::ParserError
144
+ cause_json
145
+ rescue => e
146
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] filter_cause_chain failed: #{e.message}")
147
+ cause_json
148
+ end
149
+ private_class_method :filter_cause_chain
150
+
151
+ # Build an ActiveSupport::ParameterFilter from Rails config + custom patterns
152
+ # @return [ActiveSupport::ParameterFilter, nil]
153
+ def self.build_parameter_filter
154
+ # Always start with default patterns (passwords, tokens, credit cards, etc.)
155
+ patterns = DEFAULT_SENSITIVE_PATTERNS.dup
156
+
157
+ # Rails' built-in filter_parameters
158
+ if defined?(Rails) && Rails.application&.config&.respond_to?(:filter_parameters)
159
+ patterns.concat(Array(Rails.application.config.filter_parameters))
160
+ end
161
+
162
+ # Custom patterns from gem config
163
+ custom = RailsErrorDashboard.configuration.sensitive_data_patterns
164
+ patterns.concat(Array(custom)) if custom
165
+
166
+ patterns.uniq!
167
+
168
+ ActiveSupport::ParameterFilter.new(patterns)
169
+ rescue => e
170
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] build_parameter_filter failed: #{e.message}")
171
+ nil
172
+ end
173
+ private_class_method :build_parameter_filter
174
+ end
175
+ end
176
+ end
@@ -6,7 +6,8 @@ module RailsErrorDashboard
6
6
  # Extracts and normalizes context information from various sources
7
7
  class ErrorContext
8
8
  attr_reader :user_id, :request_url, :request_params, :user_agent, :ip_address, :platform,
9
- :controller_name, :action_name, :request_id, :session_id
9
+ :controller_name, :action_name, :request_id, :session_id,
10
+ :http_method, :hostname, :content_type, :request_duration_ms
10
11
 
11
12
  def initialize(context, source = nil)
12
13
  @context = context
@@ -22,6 +23,10 @@ module RailsErrorDashboard
22
23
  @action_name = extract_action_name
23
24
  @request_id = extract_request_id
24
25
  @session_id = extract_session_id
26
+ @http_method = extract_http_method
27
+ @hostname = extract_hostname
28
+ @content_type = extract_content_type
29
+ @request_duration_ms = extract_request_duration_ms
25
30
  end
26
31
 
27
32
  def to_h
@@ -33,7 +38,11 @@ module RailsErrorDashboard
33
38
  ip_address: ip_address,
34
39
  platform: platform,
35
40
  controller_name: controller_name,
36
- action_name: action_name
41
+ action_name: action_name,
42
+ http_method: http_method,
43
+ hostname: hostname,
44
+ content_type: content_type,
45
+ request_duration_ms: request_duration_ms
37
46
  }
38
47
  end
39
48
 
@@ -42,7 +51,8 @@ module RailsErrorDashboard
42
51
  def extract_user_id
43
52
  @context[:current_user]&.id ||
44
53
  @context[:user_id] ||
45
- @context[:user]&.id
54
+ @context[:user]&.id ||
55
+ current_attributes_user_id
46
56
  end
47
57
 
48
58
  def build_request_url
@@ -176,7 +186,8 @@ module RailsErrorDashboard
176
186
  return @context[:job]&.job_id if @context[:job]
177
187
  return @context[:jid] if @context[:jid]
178
188
 
179
- nil
189
+ # From CurrentAttributes (if app defines Current.request_id)
190
+ current_attributes_value(:request_id)
180
191
  end
181
192
 
182
193
  def extract_session_id
@@ -188,6 +199,72 @@ module RailsErrorDashboard
188
199
 
189
200
  nil
190
201
  end
202
+
203
+ def extract_http_method
204
+ return @context[:request]&.method if @context[:request]&.respond_to?(:method)
205
+ return @context[:http_method] if @context[:http_method]
206
+
207
+ nil
208
+ end
209
+
210
+ def extract_hostname
211
+ return @context[:request]&.host if @context[:request]&.respond_to?(:host)
212
+ return @context[:hostname] if @context[:hostname]
213
+
214
+ nil
215
+ end
216
+
217
+ def extract_content_type
218
+ if @context[:request]&.respond_to?(:content_type)
219
+ ct = @context[:request].content_type
220
+ # content_type can return a MIME::Type object or string depending on Rails version
221
+ return ct.to_s.presence
222
+ end
223
+
224
+ return @context[:content_type] if @context[:content_type]
225
+
226
+ nil
227
+ end
228
+
229
+ def extract_request_duration_ms
230
+ # Duration is calculated from the start time stored in the Rack env
231
+ if @context[:request]&.respond_to?(:env)
232
+ start_time = @context[:request].env["rails_error_dashboard.request_start"]
233
+ if start_time
234
+ elapsed = (Time.now.to_f - start_time.to_f) * 1000
235
+ return elapsed.round
236
+ end
237
+ end
238
+
239
+ return @context[:request_duration_ms] if @context[:request_duration_ms]
240
+
241
+ nil
242
+ end
243
+
244
+ # Auto-detect user_id from ActiveSupport::CurrentAttributes
245
+ # Checks for common patterns: Current.user, Current.account (with .id)
246
+ # Returns nil if CurrentAttributes is not used or user is not set
247
+ def current_attributes_user_id
248
+ return nil unless defined?(::Current)
249
+ return ::Current.user.id if ::Current.respond_to?(:user) && ::Current.user.respond_to?(:id)
250
+
251
+ nil
252
+ rescue => e
253
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] CurrentAttributes user_id detection failed: #{e.message}")
254
+ nil
255
+ end
256
+
257
+ # Read a single attribute from CurrentAttributes
258
+ # Returns nil if not available
259
+ def current_attributes_value(attribute_name)
260
+ return nil unless defined?(::Current)
261
+ return ::Current.public_send(attribute_name) if ::Current.respond_to?(attribute_name)
262
+
263
+ nil
264
+ rescue => e
265
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] CurrentAttributes #{attribute_name} detection failed: #{e.message}")
266
+ nil
267
+ end
191
268
  end
192
269
  end
193
270
  end
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.1.37"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -5,14 +5,15 @@ require "rails_error_dashboard/configuration"
5
5
  require "rails_error_dashboard/logger"
6
6
  require "rails_error_dashboard/manual_error_reporter"
7
7
 
8
- # External dependencies
8
+ # Required dependencies
9
9
  require "pagy"
10
- require "pagy/extras/bootstrap"
11
- require "browser"
12
10
  require "groupdate"
13
- require "httparty"
14
- require "chartkick"
15
- require "turbo-rails"
11
+
12
+ # Optional dependencies — features degrade gracefully without these
13
+ begin; require "browser"; rescue LoadError; end
14
+ begin; require "httparty"; rescue LoadError; end
15
+ begin; require "chartkick"; rescue LoadError; end
16
+ begin; require "turbo-rails"; rescue LoadError; end
16
17
 
17
18
 
18
19
  # Core library files
@@ -45,6 +46,10 @@ require "rails_error_dashboard/services/statistical_classifier"
45
46
  require "rails_error_dashboard/services/source_code_reader"
46
47
  require "rails_error_dashboard/services/git_blame_reader"
47
48
  require "rails_error_dashboard/services/github_link_generator"
49
+ require "rails_error_dashboard/services/cause_chain_extractor"
50
+ require "rails_error_dashboard/services/environment_snapshot"
51
+ require "rails_error_dashboard/services/sensitive_data_filter"
52
+ require "rails_error_dashboard/services/notification_throttler"
48
53
  require "rails_error_dashboard/queries/co_occurring_errors"
49
54
  require "rails_error_dashboard/queries/error_cascades"
50
55
  require "rails_error_dashboard/queries/baseline_stats"
@@ -1,6 +1,162 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  namespace :error_dashboard do
4
+ desc "Verify Rails Error Dashboard setup (database, tables, configuration)"
5
+ task verify: :environment do
6
+ puts "\n" + "=" * 70
7
+ puts " RAILS ERROR DASHBOARD - SETUP VERIFICATION"
8
+ puts "=" * 70
9
+
10
+ checks_passed = 0
11
+ checks_failed = 0
12
+ warnings = 0
13
+
14
+ # 1. Configuration check
15
+ print "\n Checking configuration... "
16
+ config = RailsErrorDashboard.configuration
17
+ begin
18
+ config.validate!
19
+ puts "OK"
20
+ checks_passed += 1
21
+ rescue RailsErrorDashboard::ConfigurationError => e
22
+ puts "FAILED"
23
+ puts " Error: #{e.message}"
24
+ checks_failed += 1
25
+ end
26
+
27
+ # 2. Database mode
28
+ print " Database mode... "
29
+ if config.use_separate_database
30
+ db_name = config.database || :error_dashboard
31
+ puts "SEPARATE (key: #{db_name})"
32
+ else
33
+ puts "SHARED (using primary database)"
34
+ end
35
+ checks_passed += 1
36
+
37
+ # 3. Database connection
38
+ print " Database connection... "
39
+ begin
40
+ RailsErrorDashboard::ErrorLogsRecord.connection.active?
41
+ adapter = RailsErrorDashboard::ErrorLogsRecord.connection.adapter_name
42
+ puts "OK (#{adapter})"
43
+ checks_passed += 1
44
+ rescue => e
45
+ puts "FAILED"
46
+ puts " Error: #{e.message}"
47
+ if config.use_separate_database
48
+ db_name = config.database || :error_dashboard
49
+ puts " Hint: Make sure '#{db_name}:' is configured in config/database.yml"
50
+ puts " Hint: Run 'rails db:create:#{db_name}' to create the database"
51
+ end
52
+ checks_failed += 1
53
+ end
54
+
55
+ # 4. Tables check
56
+ print " Required tables... "
57
+ required_tables = %w[
58
+ rails_error_dashboard_applications
59
+ rails_error_dashboard_error_logs
60
+ rails_error_dashboard_error_occurrences
61
+ rails_error_dashboard_error_comments
62
+ rails_error_dashboard_error_baselines
63
+ rails_error_dashboard_cascade_patterns
64
+ ]
65
+
66
+ begin
67
+ conn = RailsErrorDashboard::ErrorLogsRecord.connection
68
+ existing = required_tables.select { |t| conn.table_exists?(t) }
69
+ missing = required_tables - existing
70
+
71
+ if missing.empty?
72
+ puts "OK (#{existing.size} tables found)"
73
+ checks_passed += 1
74
+ else
75
+ puts "INCOMPLETE"
76
+ missing.each { |t| puts " Missing: #{t}" }
77
+ if config.use_separate_database
78
+ db_name = config.database || :error_dashboard
79
+ puts " Hint: Run 'rails db:migrate:#{db_name}'"
80
+ else
81
+ puts " Hint: Run 'rails db:migrate'"
82
+ end
83
+ checks_failed += 1
84
+ end
85
+ rescue => e
86
+ puts "SKIPPED (no connection)"
87
+ end
88
+
89
+ # 5. Application registration
90
+ print " Application registration... "
91
+ begin
92
+ app_name = config.application_name ||
93
+ ENV["APPLICATION_NAME"] ||
94
+ (defined?(Rails) && Rails.application.class.module_parent_name) ||
95
+ "Unknown"
96
+
97
+ current_app = RailsErrorDashboard::Application.find_by(name: app_name)
98
+ if current_app
99
+ puts "OK"
100
+ puts " This app: \"#{app_name}\" (ID: #{current_app.id})"
101
+ checks_passed += 1
102
+ else
103
+ puts "PENDING"
104
+ puts " App \"#{app_name}\" will be registered on first error"
105
+ warnings += 1
106
+ end
107
+
108
+ # List other registered apps
109
+ all_apps = RailsErrorDashboard::Application.order(:name).pluck(:name, :id)
110
+ other_apps = all_apps.reject { |name, _| name == app_name }
111
+ if other_apps.any?
112
+ puts " Other apps in this database:"
113
+ other_apps.each { |name, id| puts " - \"#{name}\" (ID: #{id})" }
114
+ end
115
+ rescue => e
116
+ puts "SKIPPED (#{e.message.truncate(60)})"
117
+ end
118
+
119
+ # 6. Error count
120
+ print " Error data... "
121
+ begin
122
+ total = RailsErrorDashboard::ErrorLog.count
123
+ unresolved = RailsErrorDashboard::ErrorLog.where(resolved: false).count
124
+ puts "#{total} total errors (#{unresolved} unresolved)"
125
+ checks_passed += 1
126
+ rescue => e
127
+ puts "SKIPPED (#{e.message.truncate(60)})"
128
+ end
129
+
130
+ # 7. Authentication check
131
+ print " Authentication... "
132
+ if config.dashboard_username == "gandalf" && config.dashboard_password == "youshallnotpass"
133
+ if Rails.env.production?
134
+ puts "WARNING - using default credentials in production!"
135
+ checks_failed += 1
136
+ else
137
+ puts "OK (default credentials - change before production)"
138
+ warnings += 1
139
+ end
140
+ else
141
+ puts "OK (custom credentials)"
142
+ checks_passed += 1
143
+ end
144
+
145
+ # Summary
146
+ puts "\n" + "-" * 70
147
+ puts " Results: #{checks_passed} passed, #{checks_failed} failed, #{warnings} warnings"
148
+
149
+ if checks_failed > 0
150
+ puts " Status: NEEDS ATTENTION"
151
+ elsif warnings > 0
152
+ puts " Status: OK (with warnings)"
153
+ else
154
+ puts " Status: ALL GOOD"
155
+ end
156
+
157
+ puts "=" * 70 + "\n"
158
+ end
159
+
4
160
  desc "List all registered applications with error counts"
5
161
  task list_applications: :environment do
6
162
  puts "\n" + "=" * 80
@@ -24,7 +180,7 @@ namespace :error_dashboard do
24
180
  next
25
181
  end
26
182
 
27
- puts "\n#{apps.count} application(s) registered:\n\n"
183
+ puts "\n#{apps.length} application(s) registered:\n\n"
28
184
 
29
185
  # Calculate column widths using aggregated data (no additional queries)
30
186
  name_width = [ apps.map(&:name).map(&:length).max, 20 ].max
@@ -52,7 +208,7 @@ namespace :error_dashboard do
52
208
  total_unresolved = apps.sum(&:unresolved_errors)
53
209
 
54
210
  puts "\nSUMMARY:"
55
- puts " Total Applications: #{apps.count}"
211
+ puts " Total Applications: #{apps.length}"
56
212
  puts " Total Errors: #{total_errors}"
57
213
  puts " Total Unresolved: #{total_unresolved}"
58
214
  puts " Resolution Rate: #{total_errors.zero? ? 'N/A' : "#{((total_errors - total_unresolved).to_f / total_errors * 100).round(1)}%"}"
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.37
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -29,28 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '9.0'
32
+ version: '43.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '9.0'
40
- - !ruby/object:Gem::Dependency
41
- name: browser
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '6.0'
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '6.0'
39
+ version: '43.0'
54
40
  - !ruby/object:Gem::Dependency
55
41
  name: groupdate
56
42
  requirement: !ruby/object:Gem::Requirement
@@ -65,48 +51,6 @@ dependencies:
65
51
  - - "~>"
66
52
  - !ruby/object:Gem::Version
67
53
  version: '6.0'
68
- - !ruby/object:Gem::Dependency
69
- name: chartkick
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '5.0'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '5.0'
82
- - !ruby/object:Gem::Dependency
83
- name: httparty
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
88
- version: 0.24.0
89
- type: :runtime
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: 0.24.0
96
- - !ruby/object:Gem::Dependency
97
- name: turbo-rails
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: '2.0'
103
- type: :runtime
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '2.0'
110
54
  - !ruby/object:Gem::Dependency
111
55
  name: concurrent-ruby
112
56
  requirement: !ruby/object:Gem::Requirement
@@ -308,6 +252,7 @@ files:
308
252
  - app/jobs/rails_error_dashboard/discord_error_notification_job.rb
309
253
  - app/jobs/rails_error_dashboard/email_error_notification_job.rb
310
254
  - app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb
255
+ - app/jobs/rails_error_dashboard/retention_cleanup_job.rb
311
256
  - app/jobs/rails_error_dashboard/slack_error_notification_job.rb
312
257
  - app/jobs/rails_error_dashboard/webhook_error_notification_job.rb
313
258
  - app/mailers/rails_error_dashboard/application_mailer.rb
@@ -357,6 +302,11 @@ files:
357
302
  - db/migrate/20260106094233_add_application_to_error_logs.rb
358
303
  - db/migrate/20260106094256_backfill_application_for_existing_errors.rb
359
304
  - db/migrate/20260106094318_finalize_application_foreign_key.rb
305
+ - db/migrate/20260220000001_add_exception_cause_to_error_logs.rb
306
+ - db/migrate/20260220000002_add_enriched_context_to_error_logs.rb
307
+ - db/migrate/20260220000003_add_time_series_indexes_to_error_logs.rb
308
+ - db/migrate/20260221000001_add_environment_info_to_error_logs.rb
309
+ - db/migrate/20260221000002_add_reopened_at_to_error_logs.rb
360
310
  - lib/generators/rails_error_dashboard/install/install_generator.rb
361
311
  - lib/generators/rails_error_dashboard/install/templates/README
362
312
  - lib/generators/rails_error_dashboard/install/templates/initializer.rb
@@ -415,7 +365,9 @@ files:
415
365
  - lib/rails_error_dashboard/services/baseline_alert_throttler.rb
416
366
  - lib/rails_error_dashboard/services/baseline_calculator.rb
417
367
  - lib/rails_error_dashboard/services/cascade_detector.rb
368
+ - lib/rails_error_dashboard/services/cause_chain_extractor.rb
418
369
  - lib/rails_error_dashboard/services/discord_payload_builder.rb
370
+ - lib/rails_error_dashboard/services/environment_snapshot.rb
419
371
  - lib/rails_error_dashboard/services/error_broadcaster.rb
420
372
  - lib/rails_error_dashboard/services/error_hash_generator.rb
421
373
  - lib/rails_error_dashboard/services/error_normalizer.rb
@@ -424,11 +376,13 @@ files:
424
376
  - lib/rails_error_dashboard/services/git_blame_reader.rb
425
377
  - lib/rails_error_dashboard/services/github_link_generator.rb
426
378
  - lib/rails_error_dashboard/services/notification_helpers.rb
379
+ - lib/rails_error_dashboard/services/notification_throttler.rb
427
380
  - lib/rails_error_dashboard/services/pagerduty_payload_builder.rb
428
381
  - lib/rails_error_dashboard/services/pattern_detector.rb
429
382
  - lib/rails_error_dashboard/services/pearson_correlation.rb
430
383
  - lib/rails_error_dashboard/services/platform_detector.rb
431
384
  - lib/rails_error_dashboard/services/priority_score_calculator.rb
385
+ - lib/rails_error_dashboard/services/sensitive_data_filter.rb
432
386
  - lib/rails_error_dashboard/services/severity_classifier.rb
433
387
  - lib/rails_error_dashboard/services/similarity_calculator.rb
434
388
  - lib/rails_error_dashboard/services/slack_payload_builder.rb
@@ -449,7 +403,7 @@ metadata:
449
403
  documentation_uri: https://AnjanJ.github.io/rails_error_dashboard
450
404
  bug_tracker_uri: https://github.com/AnjanJ/rails_error_dashboard/issues
451
405
  post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
452
- \ Rails Error Dashboard v0.1.37\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
406
+ \ Rails Error Dashboard v0.2.0\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
453
407
  First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
454
408
  db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
455
409
  => '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n