rails_error_dashboard 0.1.1 → 0.1.4

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -21
  3. data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +107 -0
  4. data/app/assets/stylesheets/rails_error_dashboard/_components.scss +625 -0
  5. data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +257 -0
  6. data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +203 -0
  7. data/app/assets/stylesheets/rails_error_dashboard/application.css.map +7 -0
  8. data/app/assets/stylesheets/rails_error_dashboard/application.scss +61 -0
  9. data/app/controllers/rails_error_dashboard/errors_controller.rb +135 -1
  10. data/app/helpers/rails_error_dashboard/application_helper.rb +80 -4
  11. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
  12. data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
  13. data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -0
  14. data/app/models/rails_error_dashboard/error_comment.rb +27 -0
  15. data/app/models/rails_error_dashboard/error_log.rb +159 -0
  16. data/app/views/layouts/rails_error_dashboard/application.html.erb +39 -1
  17. data/app/views/layouts/rails_error_dashboard.html.erb +796 -299
  18. data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
  19. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +2 -0
  20. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +4 -4
  21. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +167 -0
  22. data/app/views/rails_error_dashboard/errors/analytics.html.erb +439 -22
  23. data/app/views/rails_error_dashboard/errors/index.html.erb +127 -11
  24. data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
  25. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +29 -18
  26. data/app/views/rails_error_dashboard/errors/show.html.erb +353 -54
  27. data/config/routes.rb +11 -1
  28. data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
  29. data/db/migrate/20251226020100_create_error_comments.rb +18 -0
  30. data/lib/generators/rails_error_dashboard/install/install_generator.rb +8 -2
  31. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +21 -0
  32. data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +317 -0
  33. data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +1 -1
  34. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
  35. data/lib/rails_error_dashboard/commands/log_error.rb +47 -9
  36. data/lib/rails_error_dashboard/commands/resolve_error.rb +1 -1
  37. data/lib/rails_error_dashboard/configuration.rb +8 -0
  38. data/lib/rails_error_dashboard/error_reporter.rb +4 -4
  39. data/lib/rails_error_dashboard/logger.rb +105 -0
  40. data/lib/rails_error_dashboard/middleware/error_catcher.rb +2 -2
  41. data/lib/rails_error_dashboard/plugin.rb +3 -3
  42. data/lib/rails_error_dashboard/plugin_registry.rb +2 -2
  43. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +1 -1
  44. data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -1
  45. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +109 -1
  46. data/lib/rails_error_dashboard/queries/errors_list.rb +134 -7
  47. data/lib/rails_error_dashboard/queries/mttr_stats.rb +111 -0
  48. data/lib/rails_error_dashboard/queries/recurring_issues.rb +97 -0
  49. data/lib/rails_error_dashboard/services/backtrace_parser.rb +113 -0
  50. data/lib/rails_error_dashboard/version.rb +1 -1
  51. data/lib/rails_error_dashboard.rb +5 -0
  52. data/lib/tasks/rails_error_dashboard_tasks.rake +85 -4
  53. metadata +36 -2
@@ -0,0 +1,317 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ module Generators
5
+ class UninstallGenerator < Rails::Generators::Base
6
+ desc "Uninstalls Rails Error Dashboard and removes all associated files and data"
7
+
8
+ class_option :keep_data, type: :boolean, default: false, desc: "Keep error data in database (don't drop tables)"
9
+ class_option :skip_confirmation, type: :boolean, default: false, desc: "Skip confirmation prompts"
10
+ class_option :manual_only, type: :boolean, default: false, desc: "Show manual instructions only, don't perform automated uninstall"
11
+
12
+ def welcome_message
13
+ say "\n"
14
+ say "=" * 80
15
+ say " 🗑️ Rails Error Dashboard - Uninstall", :red
16
+ say "=" * 80
17
+ say "\n"
18
+ say "This will remove Rails Error Dashboard from your application.", :yellow
19
+ say "\n"
20
+ end
21
+
22
+ def detect_installed_components
23
+ @components = {
24
+ initializer: File.exist?("config/initializers/rails_error_dashboard.rb"),
25
+ route: route_mounted?,
26
+ migrations: migrations_exist?,
27
+ tables: tables_exist?,
28
+ gemfile: gemfile_includes_gem?
29
+ }
30
+
31
+ say "Detected components:", :cyan
32
+ say " #{status_icon(@components[:gemfile])} Gemfile entry"
33
+ say " #{status_icon(@components[:initializer])} Initializer (config/initializers/rails_error_dashboard.rb)"
34
+ say " #{status_icon(@components[:route])} Route (mount RailsErrorDashboard::Engine)"
35
+ say " #{status_icon(@components[:migrations])} Migrations (#{migration_count} files)"
36
+ say " #{status_icon(@components[:tables])} Database tables (#{table_count} tables)"
37
+ say "\n"
38
+ end
39
+
40
+ def show_manual_instructions
41
+ say "=" * 80
42
+ say " 📖 Manual Uninstall Instructions", :cyan
43
+ say "=" * 80
44
+ say "\n"
45
+
46
+ say "Step 1: Remove from Gemfile", :yellow
47
+ say " Open: Gemfile"
48
+ say " Remove: gem 'rails_error_dashboard'"
49
+ say " Run: bundle install"
50
+ say "\n"
51
+
52
+ if @components[:initializer]
53
+ say "Step 2: Remove initializer", :yellow
54
+ say " Delete: config/initializers/rails_error_dashboard.rb"
55
+ say "\n"
56
+ end
57
+
58
+ if @components[:route]
59
+ say "Step 3: Remove route", :yellow
60
+ say " Open: config/routes.rb"
61
+ say " Remove: mount RailsErrorDashboard::Engine => '/error_dashboard'"
62
+ say "\n"
63
+ end
64
+
65
+ if @components[:migrations]
66
+ say "Step 4: Remove migrations", :yellow
67
+ say " Delete migration files from db/migrate/:"
68
+ migration_files.each do |file|
69
+ say " - #{File.basename(file)}", :light_black
70
+ end
71
+ say "\n"
72
+ end
73
+
74
+ if @components[:tables]
75
+ say "Step 5: Drop database tables (⚠️ DESTRUCTIVE - will delete all error data)", :yellow
76
+ say " Run: rails rails_error_dashboard:db:drop"
77
+ say " Or manually in rails console:"
78
+ say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_error_logs')", :light_black
79
+ say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_error_occurrences')", :light_black
80
+ say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_cascade_patterns')", :light_black
81
+ say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_error_baselines')", :light_black
82
+ say " ActiveRecord::Base.connection.execute('DROP TABLE rails_error_dashboard_error_comments')", :light_black
83
+ say " ActiveRecord::Migration.drop_table(:rails_error_dashboard_error_logs) rescue nil", :light_black
84
+ say "\n"
85
+ end
86
+
87
+ say "Step 6: Clean up environment variables (optional)", :yellow
88
+ say " Remove from .env or environment:"
89
+ say " - ERROR_DASHBOARD_USER"
90
+ say " - ERROR_DASHBOARD_PASSWORD"
91
+ say " - SLACK_WEBHOOK_URL"
92
+ say " - ERROR_NOTIFICATION_EMAILS"
93
+ say " - DISCORD_WEBHOOK_URL"
94
+ say " - PAGERDUTY_INTEGRATION_KEY"
95
+ say " - WEBHOOK_URLS"
96
+ say " - DASHBOARD_BASE_URL"
97
+ say "\n"
98
+
99
+ say "Step 7: Restart your application", :yellow
100
+ say " Run: rails restart (or restart your server)"
101
+ say "\n"
102
+
103
+ say "=" * 80
104
+ say "\n"
105
+ end
106
+
107
+ def confirm_automated_uninstall
108
+ return if options[:manual_only]
109
+ return if options[:skip_confirmation]
110
+
111
+ say "Would you like to run the automated uninstall? (recommended)", :cyan
112
+ say "This will:", :yellow
113
+ say " ✓ Remove initializer file"
114
+ say " ✓ Remove route from config/routes.rb"
115
+ say " ✓ Remove migration files"
116
+ if options[:keep_data]
117
+ say " ✗ Keep database tables and data (--keep-data flag set)", :green
118
+ else
119
+ say " ⚠️ Drop all database tables (deletes all error data!)", :red
120
+ end
121
+ say "\n"
122
+
123
+ response = ask("Proceed with automated uninstall? (yes/no):", :yellow, limited_to: [ "yes", "no", "y", "n" ])
124
+
125
+ if response.downcase == "no" || response.downcase == "n"
126
+ say "\n"
127
+ say "Automated uninstall cancelled.", :yellow
128
+ say "Follow the manual instructions above to uninstall.", :cyan
129
+ say "\n"
130
+ exit 0
131
+ end
132
+
133
+ say "\n"
134
+ end
135
+
136
+ def final_data_warning
137
+ return if options[:manual_only]
138
+ return if options[:keep_data]
139
+ return unless @components[:tables]
140
+
141
+ say "=" * 80
142
+ say " ⚠️ FINAL WARNING - Data Deletion", :red
143
+ say "=" * 80
144
+ say "\n"
145
+ say "You are about to PERMANENTLY DELETE all error tracking data!", :red
146
+ say "\n"
147
+ say "Database tables to be dropped:", :yellow
148
+ table_names.each do |table|
149
+ say " • #{table}", :light_black
150
+ end
151
+ say "\n"
152
+ say "This action CANNOT be undone!", :red
153
+ say "\n"
154
+
155
+ response = ask("Type 'DELETE ALL DATA' to confirm:", :red)
156
+
157
+ if response != "DELETE ALL DATA"
158
+ say "\n"
159
+ say "Data deletion cancelled. Database tables will be kept.", :green
160
+ say "Use --keep-data flag to skip this warning in the future.", :cyan
161
+ @components[:tables] = false # Don't drop tables
162
+ say "\n"
163
+ end
164
+
165
+ say "\n"
166
+ end
167
+
168
+ def remove_initializer
169
+ return if options[:manual_only]
170
+ return unless @components[:initializer]
171
+
172
+ remove_file "config/initializers/rails_error_dashboard.rb"
173
+ say " ✓ Removed initializer", :green
174
+ end
175
+
176
+ def remove_route
177
+ return if options[:manual_only]
178
+ return unless @components[:route]
179
+
180
+ begin
181
+ gsub_file "config/routes.rb", /mount RailsErrorDashboard::Engine.*\n/, ""
182
+ say " ✓ Removed route", :green
183
+ rescue => e
184
+ say " ⚠️ Could not automatically remove route: #{e.message}", :yellow
185
+ say " Please manually remove: mount RailsErrorDashboard::Engine => '/error_dashboard'", :yellow
186
+ end
187
+ end
188
+
189
+ def remove_migrations
190
+ return if options[:manual_only]
191
+ return unless @components[:migrations]
192
+
193
+ migration_files.each do |file|
194
+ remove_file file
195
+ end
196
+ say " ✓ Removed #{migration_count} migration file(s)", :green
197
+ end
198
+
199
+ def drop_database_tables
200
+ return if options[:manual_only]
201
+ return if options[:keep_data]
202
+ return unless @components[:tables]
203
+
204
+ say " Dropping database tables...", :yellow
205
+
206
+ # Drop tables in reverse order (to respect foreign keys)
207
+ tables_to_drop = [
208
+ "rails_error_dashboard_error_comments",
209
+ "rails_error_dashboard_error_occurrences",
210
+ "rails_error_dashboard_cascade_patterns",
211
+ "rails_error_dashboard_error_baselines",
212
+ "rails_error_dashboard_error_logs"
213
+ ]
214
+
215
+ dropped_count = 0
216
+ tables_to_drop.each do |table|
217
+ if ActiveRecord::Base.connection.table_exists?(table)
218
+ ActiveRecord::Base.connection.drop_table(table, if_exists: true)
219
+ dropped_count += 1
220
+ end
221
+ rescue => e
222
+ say " ⚠️ Could not drop table #{table}: #{e.message}", :yellow
223
+ end
224
+
225
+ say " ✓ Dropped #{dropped_count} database table(s)", :green
226
+ end
227
+
228
+ def show_completion_message
229
+ return if options[:manual_only]
230
+
231
+ say "\n"
232
+ say "=" * 80
233
+ say " ✅ Uninstall Complete!", :green
234
+ say "=" * 80
235
+ say "\n"
236
+
237
+ say "Remaining manual steps:", :cyan
238
+ say "\n"
239
+
240
+ say "1. Remove from Gemfile:", :yellow
241
+ say " Open: Gemfile"
242
+ say " Remove: gem 'rails_error_dashboard'"
243
+ say " Run: bundle install"
244
+ say "\n"
245
+
246
+ say "2. Restart your application:", :yellow
247
+ say " Run: rails restart"
248
+ say " Or: kill and restart your server process"
249
+ say "\n"
250
+
251
+ if options[:keep_data]
252
+ say "3. Database tables were kept (--keep-data flag)", :green
253
+ say " To remove data later, run:", :yellow
254
+ say " rails generate rails_error_dashboard:uninstall", :yellow
255
+ say "\n"
256
+ end
257
+
258
+ say "Clean up environment variables (optional):", :yellow
259
+ say " • ERROR_DASHBOARD_USER, ERROR_DASHBOARD_PASSWORD"
260
+ say " • SLACK_WEBHOOK_URL, ERROR_NOTIFICATION_EMAILS"
261
+ say " • DISCORD_WEBHOOK_URL, PAGERDUTY_INTEGRATION_KEY"
262
+ say " • WEBHOOK_URLS, DASHBOARD_BASE_URL"
263
+ say "\n"
264
+
265
+ say "Thank you for using Rails Error Dashboard! 👋", :cyan
266
+ say "\n"
267
+ end
268
+
269
+ private
270
+
271
+ def status_icon(present)
272
+ present ? "✓" : "✗"
273
+ end
274
+
275
+ def route_mounted?
276
+ return false unless File.exist?("config/routes.rb")
277
+ File.read("config/routes.rb").include?("RailsErrorDashboard::Engine")
278
+ end
279
+
280
+ def migrations_exist?
281
+ migration_files.any?
282
+ end
283
+
284
+ def migration_files
285
+ Dir.glob("db/migrate/*rails_error_dashboard*.rb")
286
+ end
287
+
288
+ def migration_count
289
+ migration_files.count
290
+ end
291
+
292
+ def tables_exist?
293
+ return false unless defined?(ActiveRecord::Base)
294
+ table_names.any? { |table| ActiveRecord::Base.connection.table_exists?(table) rescue false }
295
+ end
296
+
297
+ def table_names
298
+ [
299
+ "rails_error_dashboard_error_logs",
300
+ "rails_error_dashboard_error_occurrences",
301
+ "rails_error_dashboard_cascade_patterns",
302
+ "rails_error_dashboard_error_baselines",
303
+ "rails_error_dashboard_error_comments"
304
+ ]
305
+ end
306
+
307
+ def table_count
308
+ table_names.count { |table| ActiveRecord::Base.connection.table_exists?(table) rescue false }
309
+ end
310
+
311
+ def gemfile_includes_gem?
312
+ return false unless File.exist?("Gemfile")
313
+ File.read("Gemfile").match?(/gem\s+['"]rails_error_dashboard['"]/)
314
+ end
315
+ end
316
+ end
317
+ end
@@ -32,7 +32,7 @@ module RailsErrorDashboard
32
32
  errors: []
33
33
  }
34
34
  rescue => e
35
- Rails.logger.error("Batch delete failed: #{e.message}")
35
+ RailsErrorDashboard::Logger.error("Batch delete failed: #{e.message}")
36
36
  { success: false, count: 0, total: @error_ids.size, errors: [ e.message ] }
37
37
  end
38
38
  end
@@ -37,7 +37,7 @@ module RailsErrorDashboard
37
37
  resolved_errors << error
38
38
  rescue => e
39
39
  failed_ids << error.id
40
- Rails.logger.error("Failed to resolve error #{error.id}: #{e.message}")
40
+ RailsErrorDashboard::Logger.error("Failed to resolve error #{error.id}: #{e.message}")
41
41
  end
42
42
  end
43
43
 
@@ -52,7 +52,7 @@ module RailsErrorDashboard
52
52
  errors: failed_ids.empty? ? [] : [ "Failed to resolve #{failed_ids.size} error(s)" ]
53
53
  }
54
54
  rescue => e
55
- Rails.logger.error("Batch resolve failed: #{e.message}")
55
+ RailsErrorDashboard::Logger.error("Batch resolve failed: #{e.message}")
56
56
  { success: false, count: 0, total: @error_ids.size, errors: [ e.message ] }
57
57
  end
58
58
  end
@@ -71,6 +71,21 @@ module RailsErrorDashboard
71
71
  attributes[:backtrace_signature] = calculate_backtrace_signature_from_backtrace(truncated_backtrace)
72
72
  end
73
73
 
74
+ # Add git/release info if columns exist
75
+ if ErrorLog.column_names.include?("git_sha")
76
+ attributes[:git_sha] = RailsErrorDashboard.configuration.git_sha ||
77
+ ENV["GIT_SHA"] ||
78
+ ENV["HEROKU_SLUG_COMMIT"] ||
79
+ ENV["RENDER_GIT_COMMIT"] ||
80
+ detect_git_sha_from_command
81
+ end
82
+
83
+ if ErrorLog.column_names.include?("app_version")
84
+ attributes[:app_version] = RailsErrorDashboard.configuration.app_version ||
85
+ ENV["APP_VERSION"] ||
86
+ detect_version_from_file
87
+ end
88
+
74
89
  # Find existing error or create new one
75
90
  # This ensures accurate occurrence tracking
76
91
  error_log = ErrorLog.find_or_increment_by_hash(error_hash, attributes.merge(error_hash: error_hash))
@@ -86,7 +101,7 @@ module RailsErrorDashboard
86
101
  session_id: error_context.session_id
87
102
  )
88
103
  rescue => e
89
- Rails.logger.error("Failed to create error occurrence: #{e.message}")
104
+ RailsErrorDashboard::Logger.error("Failed to create error occurrence: #{e.message}")
90
105
  end
91
106
  end
92
107
 
@@ -112,10 +127,14 @@ module RailsErrorDashboard
112
127
  rescue => e
113
128
  # Don't let error logging cause more errors - fail silently
114
129
  # CRITICAL: Log but never propagate exception
130
+ # Log to Rails logger for visibility during development
115
131
  Rails.logger.error("[RailsErrorDashboard] LogError command failed: #{e.class} - #{e.message}")
116
- Rails.logger.error("Original exception: #{@exception.class} - #{@exception.message}") if @exception
117
- Rails.logger.error("Context: #{@context.inspect}") if @context
118
- Rails.logger.error(e.backtrace&.first(5)&.join("\n")) if e.backtrace
132
+ Rails.logger.error("Backtrace: #{e.backtrace&.first(10)&.join("\n")}")
133
+
134
+ RailsErrorDashboard::Logger.error("[RailsErrorDashboard] LogError command failed: #{e.class} - #{e.message}")
135
+ RailsErrorDashboard::Logger.error("Original exception: #{@exception.class} - #{@exception.message}") if @exception
136
+ RailsErrorDashboard::Logger.error("Context: #{@context.inspect}") if @context
137
+ RailsErrorDashboard::Logger.error(e.backtrace&.first(5)&.join("\n")) if e.backtrace
119
138
  nil # Explicitly return nil, never raise
120
139
  end
121
140
 
@@ -127,7 +146,7 @@ module RailsErrorDashboard
127
146
  RailsErrorDashboard.configuration.notification_callbacks[:error_logged].each do |callback|
128
147
  callback.call(error_log)
129
148
  rescue => e
130
- Rails.logger.error("Error in error_logged callback: #{e.message}")
149
+ RailsErrorDashboard::Logger.error("Error in error_logged callback: #{e.message}")
131
150
  end
132
151
 
133
152
  # Trigger critical_error callbacks if this is a critical error
@@ -135,7 +154,7 @@ module RailsErrorDashboard
135
154
  RailsErrorDashboard.configuration.notification_callbacks[:critical_error].each do |callback|
136
155
  callback.call(error_log)
137
156
  rescue => e
138
- Rails.logger.error("Error in critical_error callback: #{e.message}")
157
+ RailsErrorDashboard::Logger.error("Error in critical_error callback: #{e.message}")
139
158
  end
140
159
  end
141
160
  end
@@ -210,7 +229,7 @@ module RailsErrorDashboard
210
229
  end
211
230
  rescue NameError
212
231
  # Handle invalid class names in configuration
213
- Rails.logger.warn("Invalid ignored exception class: #{ignored}")
232
+ RailsErrorDashboard::Logger.warn("Invalid ignored exception class: #{ignored}")
214
233
  false
215
234
  end
216
235
  end
@@ -348,13 +367,32 @@ module RailsErrorDashboard
348
367
  # Enqueue alert job (which will handle throttling)
349
368
  BaselineAlertJob.perform_later(error_log.id, anomaly)
350
369
 
351
- Rails.logger.info(
370
+ RailsErrorDashboard::Logger.info(
352
371
  "Baseline alert queued for #{error_log.error_type} on #{error_log.platform}: " \
353
372
  "#{anomaly[:level]} (#{anomaly[:std_devs_above]&.round(1)}σ above baseline)"
354
373
  )
355
374
  rescue => e
356
375
  # Don't let baseline alerting cause errors
357
- Rails.logger.error("Failed to check baseline anomaly: #{e.message}")
376
+ RailsErrorDashboard::Logger.error("Failed to check baseline anomaly: #{e.message}")
377
+ end
378
+
379
+ # Detect git SHA from git command (fallback)
380
+ def detect_git_sha_from_command
381
+ return nil unless File.exist?(Rails.root.join(".git"))
382
+ `git rev-parse --short HEAD 2>/dev/null`.strip.presence
383
+ rescue => e
384
+ RailsErrorDashboard::Logger.debug("Could not detect git SHA: #{e.message}")
385
+ nil
386
+ end
387
+
388
+ # Detect app version from VERSION file (fallback)
389
+ def detect_version_from_file
390
+ version_file = Rails.root.join("VERSION")
391
+ return File.read(version_file).strip if File.exist?(version_file)
392
+ nil
393
+ rescue => e
394
+ RailsErrorDashboard::Logger.debug("Could not detect version: #{e.message}")
395
+ nil
358
396
  end
359
397
  end
360
398
  end
@@ -32,7 +32,7 @@ module RailsErrorDashboard
32
32
  RailsErrorDashboard.configuration.notification_callbacks[:error_resolved].each do |callback|
33
33
  callback.call(error)
34
34
  rescue => e
35
- Rails.logger.error("Error in error_resolved callback: #{e.message}")
35
+ RailsErrorDashboard::Logger.error("Error in error_resolved callback: #{e.message}")
36
36
  end
37
37
 
38
38
  # Emit ActiveSupport::Notifications instrumentation event
@@ -82,6 +82,10 @@ module RailsErrorDashboard
82
82
  # Notification callbacks (managed via helper methods, not set directly)
83
83
  attr_reader :notification_callbacks
84
84
 
85
+ # Internal logging configuration
86
+ attr_accessor :enable_internal_logging
87
+ attr_accessor :log_level
88
+
85
89
  def initialize
86
90
  # Default values
87
91
  @dashboard_username = ENV.fetch("ERROR_DASHBOARD_USER", "gandalf")
@@ -145,6 +149,10 @@ module RailsErrorDashboard
145
149
  @baseline_alert_severities = [ :critical, :high ] # Alert on critical and high severity anomalies
146
150
  @baseline_alert_cooldown_minutes = ENV.fetch("BASELINE_ALERT_COOLDOWN", "120").to_i
147
151
 
152
+ # Internal logging defaults - SILENT by default
153
+ @enable_internal_logging = false # Opt-in for debugging
154
+ @log_level = :silent # Silent by default, use :debug, :info, :warn, :error, or :silent
155
+
148
156
  @notification_callbacks = {
149
157
  error_logged: [],
150
158
  critical_error: [],
@@ -32,10 +32,10 @@ module RailsErrorDashboard
32
32
  rescue => e
33
33
  # Don't let error logging cause more errors - fail silently
34
34
  # Log failure for debugging but NEVER propagate exception
35
- Rails.logger.error("[RailsErrorDashboard] ErrorReporter failed: #{e.class} - #{e.message}")
36
- Rails.logger.error("Original error: #{error.class} - #{error.message}") if error
37
- Rails.logger.error("Context: #{context.inspect}") if context
38
- Rails.logger.error(e.backtrace&.first(5)&.join("\n")) if e.backtrace
35
+ RailsErrorDashboard::Logger.error("[RailsErrorDashboard] ErrorReporter failed: #{e.class} - #{e.message}")
36
+ RailsErrorDashboard::Logger.error("Original error: #{error.class} - #{error.message}") if error
37
+ RailsErrorDashboard::Logger.error("Context: #{context.inspect}") if context
38
+ RailsErrorDashboard::Logger.error(e.backtrace&.first(5)&.join("\n")) if e.backtrace
39
39
  nil # Explicitly return nil, never raise
40
40
  end
41
41
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsErrorDashboard
4
+ # Internal logger wrapper for Rails Error Dashboard
5
+ #
6
+ # By default, all logging is SILENT to keep production logs clean.
7
+ # Users can opt-in to verbose logging for debugging.
8
+ #
9
+ # @example Enable logging for debugging
10
+ # RailsErrorDashboard.configure do |config|
11
+ # config.enable_internal_logging = true
12
+ # config.log_level = :debug
13
+ # end
14
+ #
15
+ # @example Production troubleshooting (errors only)
16
+ # RailsErrorDashboard.configure do |config|
17
+ # config.enable_internal_logging = true
18
+ # config.log_level = :error
19
+ # end
20
+ module Logger
21
+ LOG_LEVELS = {
22
+ debug: 0,
23
+ info: 1,
24
+ warn: 2,
25
+ error: 3,
26
+ silent: 4
27
+ }.freeze
28
+
29
+ class << self
30
+ # Log debug message (only if internal logging enabled)
31
+ #
32
+ # @param message [String] The message to log
33
+ # @example
34
+ # RailsErrorDashboard::Logger.debug("Processing error #123")
35
+ def debug(message)
36
+ return unless logging_enabled?
37
+ return unless log_level_enabled?(:debug)
38
+
39
+ Rails.logger.debug(formatted_message(message))
40
+ end
41
+
42
+ # Log info message (only if internal logging enabled)
43
+ #
44
+ # @param message [String] The message to log
45
+ # @example
46
+ # RailsErrorDashboard::Logger.info("Registered plugin: MyPlugin")
47
+ def info(message)
48
+ return unless logging_enabled?
49
+ return unless log_level_enabled?(:info)
50
+
51
+ Rails.logger.info(formatted_message(message))
52
+ end
53
+
54
+ # Log warning message (only if internal logging enabled)
55
+ #
56
+ # @param message [String] The message to log
57
+ # @example
58
+ # RailsErrorDashboard::Logger.warn("Plugin already registered")
59
+ def warn(message)
60
+ return unless logging_enabled?
61
+ return unless log_level_enabled?(:warn)
62
+
63
+ Rails.logger.warn(formatted_message(message))
64
+ end
65
+
66
+ # Log error message
67
+ # Errors are logged by default unless log_level is :silent
68
+ #
69
+ # @param message [String] The message to log
70
+ # @example
71
+ # RailsErrorDashboard::Logger.error("Failed to save error log")
72
+ def error(message)
73
+ return unless log_level_enabled?(:error)
74
+
75
+ Rails.logger.error(formatted_message(message))
76
+ end
77
+
78
+ private
79
+
80
+ # Check if internal logging is enabled
81
+ #
82
+ # @return [Boolean]
83
+ def logging_enabled?
84
+ RailsErrorDashboard.configuration.enable_internal_logging
85
+ end
86
+
87
+ # Check if the given log level is enabled
88
+ #
89
+ # @param level [Symbol] The log level to check (:debug, :info, :warn, :error)
90
+ # @return [Boolean]
91
+ def log_level_enabled?(level)
92
+ config_level = RailsErrorDashboard.configuration.log_level || :silent
93
+ LOG_LEVELS[level] >= LOG_LEVELS[config_level]
94
+ end
95
+
96
+ # Format message with gem prefix
97
+ #
98
+ # @param message [String] The message to format
99
+ # @return [String] Formatted message with prefix
100
+ def formatted_message(message)
101
+ "[RailsErrorDashboard] #{message}"
102
+ end
103
+ end
104
+ end
105
+ end
@@ -36,8 +36,8 @@ module RailsErrorDashboard
36
36
  )
37
37
  rescue => e
38
38
  # If error reporting fails, log it but DON'T break the app
39
- Rails.logger.error("[RailsErrorDashboard] Middleware error reporting failed: #{e.class} - #{e.message}")
40
- Rails.logger.error(e.backtrace&.first(5)&.join("\n")) if e.backtrace
39
+ RailsErrorDashboard::Logger.error("[RailsErrorDashboard] Middleware error reporting failed: #{e.class} - #{e.message}")
40
+ RailsErrorDashboard::Logger.error(e.backtrace&.first(5)&.join("\n")) if e.backtrace
41
41
  end
42
42
 
43
43
  # Re-raise original exception to let Rails handle the response
@@ -92,9 +92,9 @@ module RailsErrorDashboard
92
92
  send(method_name, *args)
93
93
  rescue => e
94
94
  # Log plugin failures but never propagate - plugins must not break the app
95
- Rails.logger.error("[RailsErrorDashboard] Plugin '#{name}' failed in #{method_name}: #{e.class} - #{e.message}")
96
- Rails.logger.error("Plugin version: #{version}")
97
- Rails.logger.error(e.backtrace&.first(10)&.join("\n")) if e.backtrace
95
+ RailsErrorDashboard::Logger.error("[RailsErrorDashboard] Plugin '#{name}' failed in #{method_name}: #{e.class} - #{e.message}")
96
+ RailsErrorDashboard::Logger.error("Plugin version: #{version}")
97
+ RailsErrorDashboard::Logger.error(e.backtrace&.first(10)&.join("\n")) if e.backtrace
98
98
  nil # Explicitly return nil, never raise
99
99
  end
100
100
  end
@@ -18,13 +18,13 @@ module RailsErrorDashboard
18
18
  end
19
19
 
20
20
  if plugins.any? { |p| p.name == plugin.name }
21
- Rails.logger.warn("Plugin '#{plugin.name}' is already registered, skipping")
21
+ RailsErrorDashboard::Logger.warn("Plugin '#{plugin.name}' is already registered, skipping")
22
22
  return false
23
23
  end
24
24
 
25
25
  plugins << plugin
26
26
  plugin.on_register
27
- Rails.logger.info("Registered plugin: #{plugin.name} (#{plugin.version})")
27
+ RailsErrorDashboard::Logger.info("Registered plugin: #{plugin.name} (#{plugin.version})")
28
28
  true
29
29
  end
30
30
 
@@ -62,7 +62,7 @@ module RailsErrorDashboard
62
62
  labels: [ "rails-error-dashboard", error_log.platform ].compact
63
63
  }
64
64
 
65
- Rails.logger.info("Would create Jira ticket: #{ticket_data.to_json}")
65
+ RailsErrorDashboard::Logger.info("Would create Jira ticket: #{ticket_data.to_json}")
66
66
 
67
67
  # Actual implementation:
68
68
  # require 'httparty'
@@ -56,7 +56,7 @@ module RailsErrorDashboard
56
56
  # Datadog::Statsd.increment(metric_name, tags: metric_tags(data))
57
57
 
58
58
  # For demonstration, just log
59
- Rails.logger.info("Metrics: #{metric_name} - #{data.is_a?(Hash) ? data : data.class.name}")
59
+ RailsErrorDashboard::Logger.info("Metrics: #{metric_name} - #{data.is_a?(Hash) ? data : data.class.name}")
60
60
  end
61
61
 
62
62
  def metric_tags(data)