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
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
-
#
|
|
8
|
+
# Required dependencies
|
|
9
9
|
require "pagy"
|
|
10
|
-
require "pagy/extras/bootstrap"
|
|
11
|
-
require "browser"
|
|
12
10
|
require "groupdate"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
require "
|
|
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.
|
|
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.
|
|
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.
|
|
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: '
|
|
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: '
|
|
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.
|
|
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
|