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.
- checksums.yaml +4 -4
- data/README.md +92 -21
- data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +107 -0
- data/app/assets/stylesheets/rails_error_dashboard/_components.scss +625 -0
- data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +257 -0
- data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +203 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.css.map +7 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.scss +61 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +135 -1
- data/app/helpers/rails_error_dashboard/application_helper.rb +80 -4
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
- data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
- data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -0
- data/app/models/rails_error_dashboard/error_comment.rb +27 -0
- data/app/models/rails_error_dashboard/error_log.rb +159 -0
- data/app/views/layouts/rails_error_dashboard/application.html.erb +39 -1
- data/app/views/layouts/rails_error_dashboard.html.erb +796 -299
- data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +2 -0
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +4 -4
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +167 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +439 -22
- data/app/views/rails_error_dashboard/errors/index.html.erb +127 -11
- data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +29 -18
- data/app/views/rails_error_dashboard/errors/show.html.erb +353 -54
- data/config/routes.rb +11 -1
- data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
- data/db/migrate/20251226020100_create_error_comments.rb +18 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +8 -2
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +21 -0
- data/lib/generators/rails_error_dashboard/uninstall/uninstall_generator.rb +317 -0
- data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +1 -1
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +47 -9
- data/lib/rails_error_dashboard/commands/resolve_error.rb +1 -1
- data/lib/rails_error_dashboard/configuration.rb +8 -0
- data/lib/rails_error_dashboard/error_reporter.rb +4 -4
- data/lib/rails_error_dashboard/logger.rb +105 -0
- data/lib/rails_error_dashboard/middleware/error_catcher.rb +2 -2
- data/lib/rails_error_dashboard/plugin.rb +3 -3
- data/lib/rails_error_dashboard/plugin_registry.rb +2 -2
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +1 -1
- data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -1
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +109 -1
- data/lib/rails_error_dashboard/queries/errors_list.rb +134 -7
- data/lib/rails_error_dashboard/queries/mttr_stats.rb +111 -0
- data/lib/rails_error_dashboard/queries/recurring_issues.rb +97 -0
- data/lib/rails_error_dashboard/services/backtrace_parser.rb +113 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +5 -0
- data/lib/tasks/rails_error_dashboard_tasks.rake +85 -4
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|