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.
- checksums.yaml +4 -4
- data/README.md +20 -4
- data/app/controllers/rails_error_dashboard/application_controller.rb +2 -5
- data/app/controllers/rails_error_dashboard/errors_controller.rb +2 -3
- data/app/jobs/rails_error_dashboard/async_error_logging_job.rb +10 -0
- data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +19 -15
- data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +19 -9
- data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +37 -11
- data/app/jobs/rails_error_dashboard/retention_cleanup_job.rb +44 -0
- data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +38 -16
- data/app/models/rails_error_dashboard/error_log.rb +10 -0
- data/app/models/rails_error_dashboard/error_logs_record.rb +11 -6
- data/app/views/layouts/rails_error_dashboard.html.erb +16 -0
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +3 -0
- data/app/views/rails_error_dashboard/errors/_stats.html.erb +12 -4
- data/app/views/rails_error_dashboard/errors/index.html.erb +9 -7
- data/app/views/rails_error_dashboard/errors/show.html.erb +138 -7
- data/db/migrate/20251223000000_create_rails_error_dashboard_complete_schema.rb +36 -0
- data/db/migrate/20251224081522_add_better_tracking_to_error_logs.rb +1 -1
- data/db/migrate/20251224101217_add_controller_action_to_error_logs.rb +1 -1
- data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +1 -1
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +1 -1
- data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +1 -1
- data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +1 -1
- data/db/migrate/20251225100236_create_error_occurrences.rb +1 -1
- data/db/migrate/20251225101920_create_cascade_patterns.rb +1 -1
- data/db/migrate/20251225102500_create_error_baselines.rb +1 -1
- data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +1 -1
- data/db/migrate/20251226020100_create_error_comments.rb +1 -1
- data/db/migrate/20251230075315_cleanup_orphaned_migrations.rb +1 -1
- data/db/migrate/20260220000001_add_exception_cause_to_error_logs.rb +9 -0
- data/db/migrate/20260220000002_add_enriched_context_to_error_logs.rb +12 -0
- data/db/migrate/20260220000003_add_time_series_indexes_to_error_logs.rb +67 -0
- data/db/migrate/20260221000001_add_environment_info_to_error_logs.rb +9 -0
- data/db/migrate/20260221000002_add_reopened_at_to_error_logs.rb +9 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +145 -24
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +12 -8
- data/lib/rails_error_dashboard/commands/find_or_increment_error.rb +58 -10
- data/lib/rails_error_dashboard/commands/log_error.rb +109 -10
- data/lib/rails_error_dashboard/configuration.rb +52 -0
- data/lib/rails_error_dashboard/manual_error_reporter.rb +12 -0
- data/lib/rails_error_dashboard/middleware/error_catcher.rb +3 -0
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +8 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +8 -0
- data/lib/rails_error_dashboard/services/backtrace_parser.rb +31 -0
- data/lib/rails_error_dashboard/services/backtrace_processor.rb +31 -1
- data/lib/rails_error_dashboard/services/cause_chain_extractor.rb +62 -0
- data/lib/rails_error_dashboard/services/environment_snapshot.rb +85 -0
- data/lib/rails_error_dashboard/services/error_hash_generator.rb +50 -2
- data/lib/rails_error_dashboard/services/notification_throttler.rb +109 -0
- data/lib/rails_error_dashboard/services/platform_detector.rb +36 -11
- data/lib/rails_error_dashboard/services/sensitive_data_filter.rb +176 -0
- data/lib/rails_error_dashboard/value_objects/error_context.rb +81 -4
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +11 -6
- data/lib/tasks/error_dashboard.rake +158 -2
- metadata +14 -60
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
user_id: <%= raw @error.user_id.to_json %>,
|
|
19
19
|
severity: <%= raw @error.severity.to_json %>,
|
|
20
20
|
priority_level: <%= raw (@error.priority_level || 0).to_json %>,
|
|
21
|
+
<% if @error.respond_to?(:exception_cause) && @error.exception_cause.present? %>
|
|
22
|
+
exception_cause: <%= raw @error.exception_cause %>,
|
|
23
|
+
<% end %>
|
|
21
24
|
<% if @error.respond_to?(:app_version) %>
|
|
22
25
|
app_version: <%= raw @error.app_version.to_json %>,
|
|
23
26
|
<% end %>
|
|
@@ -108,6 +111,11 @@
|
|
|
108
111
|
<% if @error.recent? %>
|
|
109
112
|
<span class="badge bg-success ms-2" data-bs-toggle="tooltip" title="Error occurred within the last hour">NEW</span>
|
|
110
113
|
<% end %>
|
|
114
|
+
<% if @error.reopened? %>
|
|
115
|
+
<span class="badge bg-warning text-dark ms-2" data-bs-toggle="tooltip" title="Previously resolved, recurred <%= @error.reopened_at&.strftime('%b %d, %Y %H:%M') %>">
|
|
116
|
+
<i class="bi bi-arrow-counterclockwise"></i> Reopened
|
|
117
|
+
</span>
|
|
118
|
+
<% end %>
|
|
111
119
|
</h5>
|
|
112
120
|
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('<%= j @error.error_type %>', this)" title="Copy error type">
|
|
113
121
|
<i class="bi bi-clipboard"></i>
|
|
@@ -125,6 +133,57 @@
|
|
|
125
133
|
<%= @error.message %>
|
|
126
134
|
</div>
|
|
127
135
|
|
|
136
|
+
<% if @error.respond_to?(:exception_cause) && @error.exception_cause.present? %>
|
|
137
|
+
<% begin %>
|
|
138
|
+
<% cause_chain = JSON.parse(@error.exception_cause) %>
|
|
139
|
+
<% if cause_chain.is_a?(Array) && cause_chain.any? %>
|
|
140
|
+
<div class="mt-4 mb-3">
|
|
141
|
+
<h6 class="text-muted mb-2">
|
|
142
|
+
<i class="bi bi-link-45deg"></i> Exception Cause Chain
|
|
143
|
+
<span class="badge bg-secondary ms-1"><%= cause_chain.length %></span>
|
|
144
|
+
</h6>
|
|
145
|
+
<div class="cause-chain">
|
|
146
|
+
<% cause_chain.each_with_index do |cause, index| %>
|
|
147
|
+
<div class="card mb-2 border-warning">
|
|
148
|
+
<div class="card-header bg-warning bg-opacity-10 py-2">
|
|
149
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
150
|
+
<div>
|
|
151
|
+
<small class="text-muted">Caused by<%= " (##{index + 1})" if cause_chain.length > 1 %>:</small>
|
|
152
|
+
<strong class="ms-1"><code><%= cause["class_name"] %></code></strong>
|
|
153
|
+
</div>
|
|
154
|
+
<% if cause["backtrace"].present? %>
|
|
155
|
+
<button class="btn btn-sm btn-outline-secondary py-0" type="button"
|
|
156
|
+
data-bs-toggle="collapse"
|
|
157
|
+
data-bs-target="#cause-backtrace-<%= index %>"
|
|
158
|
+
aria-expanded="false">
|
|
159
|
+
<small><i class="bi bi-code-slash"></i> Backtrace</small>
|
|
160
|
+
</button>
|
|
161
|
+
<% end %>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="card-body py-2">
|
|
165
|
+
<small><%= cause["message"] %></small>
|
|
166
|
+
</div>
|
|
167
|
+
<% if cause["backtrace"].present? %>
|
|
168
|
+
<div class="collapse" id="cause-backtrace-<%= index %>">
|
|
169
|
+
<div class="card-body p-0 border-top">
|
|
170
|
+
<div class="code-block p-2" style="max-height: 200px; overflow-y: auto; overflow-x: auto; font-size: 0.8rem;">
|
|
171
|
+
<pre class="mb-0"><code><% cause["backtrace"].each do |line| %><%= line %>
|
|
172
|
+
<% end %></code></pre>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<% end %>
|
|
177
|
+
</div>
|
|
178
|
+
<% end %>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
<% end %>
|
|
182
|
+
<% rescue JSON::ParserError %>
|
|
183
|
+
<%# Silently skip invalid JSON — safety first %>
|
|
184
|
+
<% end %>
|
|
185
|
+
<% end %>
|
|
186
|
+
|
|
128
187
|
<div class="d-flex justify-content-between align-items-center mb-2 mt-4">
|
|
129
188
|
<h6 class="text-muted mb-0">
|
|
130
189
|
Backtrace:
|
|
@@ -166,6 +225,7 @@
|
|
|
166
225
|
<!-- Frame header (always visible) -->
|
|
167
226
|
<div class="<%= frame_bg_class(frame[:category]) %> d-flex justify-content-between align-items-center px-2 py-1">
|
|
168
227
|
<div>
|
|
228
|
+
<span class="backtrace-frame-number"><%= index + 1 %></span>
|
|
169
229
|
<span class="<%= frame_color_class(frame[:category]) %>">
|
|
170
230
|
<%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %>
|
|
171
231
|
</span> in <span class="backtrace-method-name">`<%= frame[:method_name] %>'</span>
|
|
@@ -209,7 +269,7 @@
|
|
|
209
269
|
<div id="frameworkBacktrace" class="accordion-collapse collapse" data-bs-parent="#frameworkBacktraceAccordion">
|
|
210
270
|
<div class="accordion-body p-0">
|
|
211
271
|
<div class="code-block p-3" style="max-height: 400px; overflow-y: auto; overflow-x: auto;">
|
|
212
|
-
<pre class="mb-0"><code><% framework_frames.
|
|
272
|
+
<pre class="mb-0"><code><% framework_frames.each_with_index do |frame, index| %><span class="<%= frame_bg_class(frame[:category]) %> d-block px-2 py-1"><span class="backtrace-frame-number"><%= index + 1 %></span><span class="<%= frame_color_class(frame[:category]) %>"><%= frame_icon(frame[:category]) %> <%= frame[:short_path] %>:<%= frame[:line_number] %></span> in <span class="backtrace-method-name">`<%= frame[:method_name] %>'</span></span>
|
|
213
273
|
<% end %></code></pre>
|
|
214
274
|
</div>
|
|
215
275
|
</div>
|
|
@@ -232,7 +292,7 @@
|
|
|
232
292
|
<% end %>
|
|
233
293
|
|
|
234
294
|
<!-- Request Context -->
|
|
235
|
-
<% cache [@error, '
|
|
295
|
+
<% cache [@error, 'request_context_v2'] do %>
|
|
236
296
|
<div class="card mb-4">
|
|
237
297
|
<div class="card-header bg-white">
|
|
238
298
|
<h5 class="mb-0"><i class="bi bi-globe"></i> Request Context</h5>
|
|
@@ -241,8 +301,36 @@
|
|
|
241
301
|
<table class="table table-sm">
|
|
242
302
|
<tr>
|
|
243
303
|
<th width="200">Request URL:</th>
|
|
244
|
-
<td
|
|
304
|
+
<td>
|
|
305
|
+
<% if @error.respond_to?(:http_method) && @error.http_method.present? %>
|
|
306
|
+
<span class="badge bg-primary me-1"><%= @error.http_method %></span>
|
|
307
|
+
<% end %>
|
|
308
|
+
<code><%= @error.request_url || 'N/A' %></code>
|
|
309
|
+
</td>
|
|
245
310
|
</tr>
|
|
311
|
+
<% if @error.respond_to?(:hostname) && @error.hostname.present? %>
|
|
312
|
+
<tr>
|
|
313
|
+
<th>Hostname:</th>
|
|
314
|
+
<td><code><%= @error.hostname %></code></td>
|
|
315
|
+
</tr>
|
|
316
|
+
<% end %>
|
|
317
|
+
<% if @error.respond_to?(:content_type) && @error.content_type.present? %>
|
|
318
|
+
<tr>
|
|
319
|
+
<th>Content Type:</th>
|
|
320
|
+
<td><code><%= @error.content_type %></code></td>
|
|
321
|
+
</tr>
|
|
322
|
+
<% end %>
|
|
323
|
+
<% if @error.respond_to?(:request_duration_ms) && @error.request_duration_ms.present? %>
|
|
324
|
+
<tr>
|
|
325
|
+
<th>Request Duration:</th>
|
|
326
|
+
<td>
|
|
327
|
+
<% duration = @error.request_duration_ms %>
|
|
328
|
+
<span class="badge bg-<%= duration > 5000 ? 'danger' : duration > 1000 ? 'warning' : 'success' %>">
|
|
329
|
+
<%= duration > 1000 ? "#{(duration / 1000.0).round(1)}s" : "#{duration}ms" %>
|
|
330
|
+
</span>
|
|
331
|
+
</td>
|
|
332
|
+
</tr>
|
|
333
|
+
<% end %>
|
|
246
334
|
<tr>
|
|
247
335
|
<th>Request Params:</th>
|
|
248
336
|
<td>
|
|
@@ -789,6 +877,20 @@
|
|
|
789
877
|
<% end %>
|
|
790
878
|
</div>
|
|
791
879
|
|
|
880
|
+
<!-- Reopened indicator -->
|
|
881
|
+
<% if @error.reopened? %>
|
|
882
|
+
<div class="mb-3">
|
|
883
|
+
<small class="text-muted d-block mb-1">Reopened</small>
|
|
884
|
+
<span class="badge bg-warning text-dark">
|
|
885
|
+
<i class="bi bi-arrow-counterclockwise"></i> Reopened
|
|
886
|
+
</span>
|
|
887
|
+
<br>
|
|
888
|
+
<small class="text-muted mt-1 d-block">
|
|
889
|
+
<%= local_time(@error.reopened_at, format: :full) %>
|
|
890
|
+
</small>
|
|
891
|
+
</div>
|
|
892
|
+
<% end %>
|
|
893
|
+
|
|
792
894
|
<!-- Phase 3: Assignment -->
|
|
793
895
|
<% if @error.respond_to?(:assigned_to) %>
|
|
794
896
|
<div class="mb-3">
|
|
@@ -893,22 +995,51 @@
|
|
|
893
995
|
</div>
|
|
894
996
|
<% end %>
|
|
895
997
|
|
|
998
|
+
<% env_info = @error.respond_to?(:environment_info) && @error.environment_info.present? ?
|
|
999
|
+
JSON.parse(@error.environment_info, symbolize_names: true) : nil rescue nil %>
|
|
1000
|
+
|
|
896
1001
|
<div class="mb-1">
|
|
897
1002
|
<small class="text-muted">Rails:</small>
|
|
898
|
-
<code class="ms-1"><%= Rails.version %></code>
|
|
1003
|
+
<code class="ms-1"><%= env_info&.dig(:rails_version) || Rails.version %></code>
|
|
899
1004
|
</div>
|
|
900
1005
|
|
|
901
1006
|
<div class="mb-1">
|
|
902
1007
|
<small class="text-muted">Ruby:</small>
|
|
903
|
-
<code class="ms-1"><%= RUBY_VERSION %></code>
|
|
1008
|
+
<code class="ms-1"><%= env_info&.dig(:ruby_version) || RUBY_VERSION %></code>
|
|
904
1009
|
</div>
|
|
905
1010
|
|
|
906
1011
|
<div class="mb-1">
|
|
907
1012
|
<small class="text-muted">Environment:</small>
|
|
908
|
-
|
|
909
|
-
|
|
1013
|
+
<% env_name = env_info&.dig(:rails_env) || Rails.env.to_s %>
|
|
1014
|
+
<span class="badge bg-<%= env_name == 'production' ? 'danger' : env_name == 'development' ? 'success' : 'warning' %> ms-1">
|
|
1015
|
+
<%= env_name.titleize %>
|
|
910
1016
|
</span>
|
|
911
1017
|
</div>
|
|
1018
|
+
|
|
1019
|
+
<% if env_info&.dig(:server).present? && env_info[:server] != "unknown" %>
|
|
1020
|
+
<div class="mb-1">
|
|
1021
|
+
<small class="text-muted">Server:</small>
|
|
1022
|
+
<code class="ms-1"><%= env_info[:server].capitalize %></code>
|
|
1023
|
+
</div>
|
|
1024
|
+
<% end %>
|
|
1025
|
+
|
|
1026
|
+
<% if env_info&.dig(:database_adapter).present? && env_info[:database_adapter] != "unknown" %>
|
|
1027
|
+
<div class="mb-1">
|
|
1028
|
+
<small class="text-muted">Database:</small>
|
|
1029
|
+
<code class="ms-1"><%= env_info[:database_adapter] %></code>
|
|
1030
|
+
</div>
|
|
1031
|
+
<% end %>
|
|
1032
|
+
|
|
1033
|
+
<% if env_info&.dig(:gem_versions)&.any? %>
|
|
1034
|
+
<div class="mb-1">
|
|
1035
|
+
<small class="text-muted d-block">Key Gems:</small>
|
|
1036
|
+
<div class="ps-2">
|
|
1037
|
+
<% env_info[:gem_versions].each do |name, version| %>
|
|
1038
|
+
<small><code><%= name %> <%= version %></code></small><br>
|
|
1039
|
+
<% end %>
|
|
1040
|
+
</div>
|
|
1041
|
+
</div>
|
|
1042
|
+
<% end %>
|
|
912
1043
|
</div>
|
|
913
1044
|
|
|
914
1045
|
<div>
|
|
@@ -68,6 +68,21 @@ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
|
|
|
68
68
|
# Application association (from 20260106094233)
|
|
69
69
|
t.integer :application_id, null: false
|
|
70
70
|
|
|
71
|
+
# Exception cause chain (from 20260220000001)
|
|
72
|
+
t.text :exception_cause
|
|
73
|
+
|
|
74
|
+
# Enriched request context (from 20260220000002)
|
|
75
|
+
t.string :http_method, limit: 10
|
|
76
|
+
t.string :hostname, limit: 255
|
|
77
|
+
t.string :content_type, limit: 100
|
|
78
|
+
t.integer :request_duration_ms
|
|
79
|
+
|
|
80
|
+
# Environment info snapshot (from 20260221000001)
|
|
81
|
+
t.text :environment_info
|
|
82
|
+
|
|
83
|
+
# Auto-reopen tracking (from 20260221000002)
|
|
84
|
+
t.datetime :reopened_at
|
|
85
|
+
|
|
71
86
|
t.timestamps
|
|
72
87
|
end
|
|
73
88
|
|
|
@@ -170,6 +185,27 @@ class CreateRailsErrorDashboardCompleteSchema < ActiveRecord::Migration[7.0]
|
|
|
170
185
|
add_index :rails_error_dashboard_error_comments, :error_log_id
|
|
171
186
|
add_index :rails_error_dashboard_error_comments, [ :error_log_id, :created_at ], name: "index_error_comments_on_error_and_time"
|
|
172
187
|
|
|
188
|
+
# PostgreSQL-specific indexes (BRIN + functional for time-series optimization)
|
|
189
|
+
if ActiveRecord::Base.connection.adapter_name.downcase == "postgresql"
|
|
190
|
+
execute <<-SQL
|
|
191
|
+
CREATE INDEX index_error_logs_on_occurred_at_brin
|
|
192
|
+
ON rails_error_dashboard_error_logs
|
|
193
|
+
USING brin (occurred_at)
|
|
194
|
+
SQL
|
|
195
|
+
|
|
196
|
+
execute <<-SQL
|
|
197
|
+
CREATE INDEX index_error_logs_on_occurred_at_day
|
|
198
|
+
ON rails_error_dashboard_error_logs
|
|
199
|
+
(DATE_TRUNC('day', occurred_at))
|
|
200
|
+
SQL
|
|
201
|
+
|
|
202
|
+
execute <<-SQL
|
|
203
|
+
CREATE INDEX index_error_logs_on_occurred_at_hour
|
|
204
|
+
ON rails_error_dashboard_error_logs
|
|
205
|
+
(DATE_TRUNC('hour', occurred_at))
|
|
206
|
+
SQL
|
|
207
|
+
end
|
|
208
|
+
|
|
173
209
|
# Add foreign keys
|
|
174
210
|
add_foreign_key :rails_error_dashboard_error_logs, :rails_error_dashboard_applications, column: :application_id
|
|
175
211
|
add_foreign_key :rails_error_dashboard_error_occurrences, :rails_error_dashboard_error_logs, column: :error_log_id
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class AddBetterTrackingToErrorLogs < ActiveRecord::Migration[
|
|
1
|
+
class AddBetterTrackingToErrorLogs < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
# Skip if squashed migration already added these columns
|
|
4
4
|
return if column_exists?(:rails_error_dashboard_error_logs, :error_hash)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class AddControllerActionToErrorLogs < ActiveRecord::Migration[
|
|
1
|
+
class AddControllerActionToErrorLogs < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
# Skip if squashed migration already added these columns
|
|
4
4
|
return if column_exists?(:rails_error_dashboard_error_logs, :controller_name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class AddOptimizedIndexesToErrorLogs < ActiveRecord::Migration[
|
|
3
|
+
class AddOptimizedIndexesToErrorLogs < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
5
|
# Skip if squashed migration already added these indexes
|
|
6
6
|
return if index_exists?(:rails_error_dashboard_error_logs, [ :resolved, :occurred_at ],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class RemoveEnvironmentFromErrorLogs < ActiveRecord::Migration[
|
|
1
|
+
class RemoveEnvironmentFromErrorLogs < ActiveRecord::Migration[7.0]
|
|
2
2
|
def up
|
|
3
3
|
# Skip if squashed migration ran (column never existed) or already removed
|
|
4
4
|
return unless column_exists?(:rails_error_dashboard_error_logs, :environment)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class AddEnhancedMetricsToErrorLogs < ActiveRecord::Migration[
|
|
1
|
+
class AddEnhancedMetricsToErrorLogs < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
# Skip if squashed migration already added these columns
|
|
4
4
|
return if column_exists?(:rails_error_dashboard_error_logs, :app_version)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class AddSimilarityTrackingToErrorLogs < ActiveRecord::Migration[
|
|
1
|
+
class AddSimilarityTrackingToErrorLogs < ActiveRecord::Migration[7.0]
|
|
2
2
|
def change
|
|
3
3
|
# Skip if squashed migration already added these columns
|
|
4
4
|
return if column_exists?(:rails_error_dashboard_error_logs, :similarity_score)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CreateErrorOccurrences < ActiveRecord::Migration[
|
|
3
|
+
class CreateErrorOccurrences < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
5
|
# Skip if squashed migration already created this table
|
|
6
6
|
return if table_exists?(:rails_error_dashboard_error_occurrences)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CreateCascadePatterns < ActiveRecord::Migration[
|
|
3
|
+
class CreateCascadePatterns < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
5
|
# Skip if squashed migration already created this table
|
|
6
6
|
return if table_exists?(:rails_error_dashboard_cascade_patterns)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CreateErrorBaselines < ActiveRecord::Migration[
|
|
3
|
+
class CreateErrorBaselines < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
5
|
# Skip if squashed migration already created this table
|
|
6
6
|
return if table_exists?(:rails_error_dashboard_error_baselines)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class AddWorkflowFieldsToErrorLogs < ActiveRecord::Migration[
|
|
3
|
+
class AddWorkflowFieldsToErrorLogs < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
5
|
# Skip if squashed migration already added these columns
|
|
6
6
|
return if column_exists?(:rails_error_dashboard_error_logs, :status)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CreateErrorComments < ActiveRecord::Migration[
|
|
3
|
+
class CreateErrorComments < ActiveRecord::Migration[7.0]
|
|
4
4
|
def change
|
|
5
5
|
# Skip if squashed migration already created this table
|
|
6
6
|
return if table_exists?(:rails_error_dashboard_error_comments)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddExceptionCauseToErrorLogs < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
unless column_exists?(:rails_error_dashboard_error_logs, :exception_cause)
|
|
6
|
+
add_column :rails_error_dashboard_error_logs, :exception_cause, :text
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddEnrichedContextToErrorLogs < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
unless column_exists?(:rails_error_dashboard_error_logs, :http_method)
|
|
6
|
+
add_column :rails_error_dashboard_error_logs, :http_method, :string, limit: 10
|
|
7
|
+
add_column :rails_error_dashboard_error_logs, :hostname, :string, limit: 255
|
|
8
|
+
add_column :rails_error_dashboard_error_logs, :content_type, :string, limit: 100
|
|
9
|
+
add_column :rails_error_dashboard_error_logs, :request_duration_ms, :integer
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Tier 0 time-series optimization: BRIN and functional indexes
|
|
4
|
+
#
|
|
5
|
+
# BRIN (Block Range Index) on occurred_at:
|
|
6
|
+
# - 99.9% smaller than B-tree (72KB vs 676MB on 100M rows)
|
|
7
|
+
# - Nearly identical query performance for time-range scans
|
|
8
|
+
# - Perfect for INSERT-heavy tables with naturally ordered timestamps
|
|
9
|
+
#
|
|
10
|
+
# Functional indexes for Groupdate:
|
|
11
|
+
# - Pre-compute DATE_TRUNC expressions used by group_by_day/group_by_hour
|
|
12
|
+
# - Up to 70x speedup on analytics dashboard queries
|
|
13
|
+
#
|
|
14
|
+
# All PostgreSQL-specific — gracefully skipped on SQLite/MySQL.
|
|
15
|
+
class AddTimeSeriesIndexesToErrorLogs < ActiveRecord::Migration[7.0]
|
|
16
|
+
def up
|
|
17
|
+
return unless postgresql?
|
|
18
|
+
|
|
19
|
+
# BRIN index on occurred_at for time-range scans
|
|
20
|
+
# Replaces expensive B-tree sequential scans on large tables
|
|
21
|
+
unless index_exists?(:rails_error_dashboard_error_logs, :occurred_at, name: "index_error_logs_on_occurred_at_brin")
|
|
22
|
+
execute <<-SQL
|
|
23
|
+
CREATE INDEX CONCURRENTLY index_error_logs_on_occurred_at_brin
|
|
24
|
+
ON rails_error_dashboard_error_logs
|
|
25
|
+
USING brin (occurred_at)
|
|
26
|
+
SQL
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Functional index for daily grouping (used by group_by_day)
|
|
30
|
+
unless index_exists_by_name?("index_error_logs_on_occurred_at_day")
|
|
31
|
+
execute <<-SQL
|
|
32
|
+
CREATE INDEX CONCURRENTLY index_error_logs_on_occurred_at_day
|
|
33
|
+
ON rails_error_dashboard_error_logs
|
|
34
|
+
(DATE_TRUNC('day', occurred_at))
|
|
35
|
+
SQL
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Functional index for hourly grouping (used by group_by_hour)
|
|
39
|
+
unless index_exists_by_name?("index_error_logs_on_occurred_at_hour")
|
|
40
|
+
execute <<-SQL
|
|
41
|
+
CREATE INDEX CONCURRENTLY index_error_logs_on_occurred_at_hour
|
|
42
|
+
ON rails_error_dashboard_error_logs
|
|
43
|
+
(DATE_TRUNC('hour', occurred_at))
|
|
44
|
+
SQL
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def down
|
|
49
|
+
return unless postgresql?
|
|
50
|
+
|
|
51
|
+
execute "DROP INDEX IF EXISTS index_error_logs_on_occurred_at_brin"
|
|
52
|
+
execute "DROP INDEX IF EXISTS index_error_logs_on_occurred_at_day"
|
|
53
|
+
execute "DROP INDEX IF EXISTS index_error_logs_on_occurred_at_hour"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def postgresql?
|
|
59
|
+
ActiveRecord::Base.connection.adapter_name.downcase == "postgresql"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def index_exists_by_name?(name)
|
|
63
|
+
ActiveRecord::Base.connection.execute(
|
|
64
|
+
"SELECT 1 FROM pg_indexes WHERE indexname = '#{name}'"
|
|
65
|
+
).any?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddEnvironmentInfoToErrorLogs < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
unless column_exists?(:rails_error_dashboard_error_logs, :environment_info)
|
|
6
|
+
add_column :rails_error_dashboard_error_logs, :environment_info, :text
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddReopenedAtToErrorLogs < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
unless column_exists?(:rails_error_dashboard_error_logs, :reopened_at)
|
|
6
|
+
add_column :rails_error_dashboard_error_logs, :reopened_at, :datetime
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|