rails_error_dashboard 0.5.5 → 0.5.7
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0cfea703e2d74b6771503d098e3bc5f048ec781923ee08a1692e34bbf28c9734
|
|
4
|
+
data.tar.gz: 4b77fb5c2dd00920d2eaca85b92d35dcbb19282408dee936584deffd2e7126a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1bcc5f0f868d3e864deccdfbe23a0a3c3e1da03f3cfb49208724f61deceaf7047bba7c1d3f19358a75e1a254336059894a55bec79656f67e55217eec18c34d19
|
|
7
|
+
data.tar.gz: 810aa6c9a5db5300d4f6f5964ce92aa06dada34dfc7b513e782a914de8a3f0baf7e35b8b03056b9a1060b018b8dca0d6b51534f528211e779c008a77060fa10d
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<button type="button" class="btn btn-outline-secondary" onclick="downloadErrorJSON(event)" title="Download error details as JSON">
|
|
33
33
|
<i class="bi bi-download"></i> Export JSON
|
|
34
34
|
</button>
|
|
35
|
-
<button type="button" class="btn btn-outline-secondary" onclick="copyToClipboard(this.dataset.markdown, this)" data-markdown="<%= j @error_markdown %>" title="Copy error details as Markdown for LLM debugging">
|
|
35
|
+
<button type="button" class="btn btn-outline-secondary" onclick="copyToClipboard(this.dataset.markdown.replace(/\\n/g, '\n'), this)" data-markdown="<%= j @error_markdown %>" title="Copy error details as Markdown for LLM debugging">
|
|
36
36
|
<i class="bi bi-clipboard"></i> Copy for LLM
|
|
37
37
|
</button>
|
|
38
38
|
<% if @error.respond_to?(:muted?) && @error.muted? %>
|
|
@@ -36,6 +36,7 @@ module RailsErrorDashboard
|
|
|
36
36
|
|
|
37
37
|
sections << heading_section
|
|
38
38
|
sections << backtrace_section
|
|
39
|
+
sections << source_code_section
|
|
39
40
|
sections << cause_chain_section
|
|
40
41
|
sections << local_variables_section
|
|
41
42
|
sections << instance_variables_section
|
|
@@ -69,6 +70,45 @@ module RailsErrorDashboard
|
|
|
69
70
|
"## Backtrace\n\n```\n#{app_lines.join("\n")}\n```"
|
|
70
71
|
end
|
|
71
72
|
|
|
73
|
+
def source_code_section
|
|
74
|
+
return nil unless defined?(RailsErrorDashboard) &&
|
|
75
|
+
RailsErrorDashboard.configuration.enable_source_code_integration
|
|
76
|
+
|
|
77
|
+
raw = @error.backtrace
|
|
78
|
+
return nil if raw.blank?
|
|
79
|
+
|
|
80
|
+
lines = raw.split("\n")
|
|
81
|
+
app_lines = lines.reject { |l| l.include?("/gems/") || l.include?("/ruby/") || l.include?("/vendor/") }
|
|
82
|
+
return nil if app_lines.empty?
|
|
83
|
+
|
|
84
|
+
snippets = []
|
|
85
|
+
app_lines.first(3).each do |frame|
|
|
86
|
+
# Parse "file:line:in 'method'" format
|
|
87
|
+
match = frame.match(/^(.+?):(\d+)/)
|
|
88
|
+
next unless match
|
|
89
|
+
|
|
90
|
+
file_path = match[1]
|
|
91
|
+
line_number = match[2].to_i
|
|
92
|
+
|
|
93
|
+
reader = Services::SourceCodeReader.new(file_path, line_number)
|
|
94
|
+
source_lines = reader.read_lines(context: 3)
|
|
95
|
+
next unless source_lines&.any?
|
|
96
|
+
|
|
97
|
+
snippet = source_lines.map { |sl|
|
|
98
|
+
marker = sl[:highlight] ? ">" : " "
|
|
99
|
+
"#{marker} #{sl[:number].to_s.rjust(4)} | #{sl[:content]}"
|
|
100
|
+
}.join("\n")
|
|
101
|
+
|
|
102
|
+
snippets << "**#{file_path}:#{line_number}**\n```ruby\n#{snippet}\n```"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
return nil if snippets.empty?
|
|
106
|
+
|
|
107
|
+
"## Source Code\n\n#{snippets.join("\n\n")}"
|
|
108
|
+
rescue => e
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
|
|
72
112
|
def cause_chain_section
|
|
73
113
|
raw = @error.exception_cause
|
|
74
114
|
return nil if raw.blank?
|
|
@@ -112,7 +152,10 @@ module RailsErrorDashboard
|
|
|
112
152
|
return nil if vars.empty? && self_class.nil?
|
|
113
153
|
|
|
114
154
|
lines = []
|
|
115
|
-
|
|
155
|
+
if self_class
|
|
156
|
+
class_name = self_class.is_a?(Hash) ? self_class["value"] : self_class
|
|
157
|
+
lines << "**Class:** #{class_name}"
|
|
158
|
+
end
|
|
116
159
|
|
|
117
160
|
if vars.any?
|
|
118
161
|
rows = vars.first(MAX_VARIABLES).map { |name, info|
|
|
@@ -132,12 +175,18 @@ module RailsErrorDashboard
|
|
|
132
175
|
return nil if @error.request_url.blank?
|
|
133
176
|
|
|
134
177
|
items = []
|
|
178
|
+
items << "- **Controller:** #{@error.controller_name}##{@error.action_name}" if @error.controller_name.present?
|
|
135
179
|
items << "- **Method:** #{@error.http_method}" if @error.http_method.present?
|
|
136
180
|
items << "- **URL:** #{@error.request_url}"
|
|
137
181
|
items << "- **Hostname:** #{@error.hostname}" if @error.hostname.present?
|
|
138
182
|
items << "- **Content-Type:** #{@error.content_type}" if @error.content_type.present?
|
|
139
183
|
items << "- **Duration:** #{@error.request_duration_ms}ms" if @error.request_duration_ms.present?
|
|
140
|
-
items << "- **
|
|
184
|
+
items << "- **User-Agent:** #{@error.user_agent}" if @error.user_agent.present?
|
|
185
|
+
|
|
186
|
+
params = parse_json(@error.request_params) if @error.request_params.present?
|
|
187
|
+
if params.is_a?(Hash) && params.any?
|
|
188
|
+
items << "\n**Request Params:**\n```json\n#{JSON.pretty_generate(params)}\n```"
|
|
189
|
+
end
|
|
141
190
|
|
|
142
191
|
"## Request Context\n\n#{items.join("\n")}"
|
|
143
192
|
end
|
|
@@ -193,19 +242,81 @@ module RailsErrorDashboard
|
|
|
193
242
|
return nil unless health.is_a?(Hash) && health.any?
|
|
194
243
|
|
|
195
244
|
items = []
|
|
196
|
-
|
|
245
|
+
|
|
246
|
+
# Process memory
|
|
247
|
+
mem = health["process_memory"]
|
|
248
|
+
if mem.is_a?(Hash)
|
|
249
|
+
parts = []
|
|
250
|
+
parts << "#{mem["rss_mb"]} MB RSS" if mem["rss_mb"]
|
|
251
|
+
parts << "peak #{mem["rss_peak_mb"]} MB" if mem["rss_peak_mb"]
|
|
252
|
+
parts << "swap #{mem["swap_mb"]} MB" if mem["swap_mb"] && mem["swap_mb"] > 0
|
|
253
|
+
parts << "#{mem["os_threads"]} OS threads" if mem["os_threads"]
|
|
254
|
+
items << "- **Memory:** #{parts.join(", ")}" if parts.any?
|
|
255
|
+
elsif health["process_memory_mb"]
|
|
256
|
+
items << "- **Memory:** #{health["process_memory_mb"]} MB RSS"
|
|
257
|
+
end
|
|
258
|
+
|
|
197
259
|
items << "- **Threads:** #{health["thread_count"]}" if health["thread_count"]
|
|
198
260
|
|
|
261
|
+
# DB connection pool
|
|
199
262
|
pool = health["connection_pool"]
|
|
200
|
-
if pool.is_a?(Hash)
|
|
201
|
-
|
|
263
|
+
if pool.is_a?(Hash) && pool["size"]
|
|
264
|
+
pool_parts = [ "#{pool["busy"]}/#{pool["size"]} busy" ]
|
|
265
|
+
pool_parts << "#{pool["dead"]} dead" if pool["dead"].to_i > 0
|
|
266
|
+
pool_parts << "#{pool["waiting"]} waiting" if pool["waiting"].to_i > 0
|
|
267
|
+
items << "- **DB Pool:** #{pool_parts.join(", ")}"
|
|
202
268
|
end
|
|
203
269
|
|
|
204
|
-
|
|
270
|
+
# GC stats
|
|
271
|
+
gc = health["gc"] || health["gc_stats"]
|
|
205
272
|
if gc.is_a?(Hash)
|
|
206
|
-
|
|
273
|
+
gc_parts = []
|
|
274
|
+
gc_parts << "#{gc["major_gc_count"]} major" if gc["major_gc_count"]
|
|
275
|
+
gc_parts << "#{gc["heap_live_slots"]} live slots" if gc["heap_live_slots"]
|
|
276
|
+
gc_parts << "#{gc["total_allocated_objects"]} total allocated" if gc["total_allocated_objects"]
|
|
277
|
+
items << "- **GC:** #{gc_parts.join(", ")}" if gc_parts.any?
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# GC latest
|
|
281
|
+
gc_latest = health["gc_latest"]
|
|
282
|
+
if gc_latest.is_a?(Hash)
|
|
283
|
+
latest_parts = []
|
|
284
|
+
latest_parts << "triggered by #{gc_latest["gc_by"]}" if gc_latest["gc_by"]
|
|
285
|
+
latest_parts << "state: #{gc_latest["state"]}" if gc_latest["state"]
|
|
286
|
+
items << "- **Last GC:** #{latest_parts.join(", ")}" if latest_parts.any?
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Note: Puma and job queue stats are omitted — they are server-wide metrics,
|
|
290
|
+
# not error-specific. The LLM can infer job context from the backtrace.
|
|
291
|
+
|
|
292
|
+
# File descriptors
|
|
293
|
+
fd = health["file_descriptors"]
|
|
294
|
+
if fd.is_a?(Hash) && fd["open"]
|
|
295
|
+
items << "- **File Descriptors:** #{fd["open"]}/#{fd["limit"]} (#{fd["utilization_pct"]}%)"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# System load
|
|
299
|
+
load = health["system_load"]
|
|
300
|
+
if load.is_a?(Hash) && load["load_1m"]
|
|
301
|
+
items << "- **System Load:** #{load["load_1m"]}/#{load["load_5m"]}/#{load["load_15m"]} (#{load["cpu_count"]} CPUs)"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# System memory
|
|
305
|
+
sys_mem = health["system_memory"]
|
|
306
|
+
if sys_mem.is_a?(Hash) && sys_mem["total_mb"]
|
|
307
|
+
items << "- **System Memory:** #{sys_mem["available_mb"]}/#{sys_mem["total_mb"]} MB available (#{sys_mem["used_pct"]}% used)"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# TCP connections
|
|
311
|
+
tcp = health["tcp_connections"]
|
|
312
|
+
if tcp.is_a?(Hash) && tcp.values.any? { |v| v.to_i > 0 }
|
|
313
|
+
tcp_parts = tcp.map { |state, count| "#{state}: #{count}" if count.to_i > 0 }.compact
|
|
314
|
+
items << "- **TCP:** #{tcp_parts.join(", ")}"
|
|
207
315
|
end
|
|
208
316
|
|
|
317
|
+
# Note: RubyVM, YJIT, and ActionCable stats are omitted — they are process-wide
|
|
318
|
+
# counters, not error-specific context. They add noise for LLM debugging.
|
|
319
|
+
|
|
209
320
|
return nil if items.empty?
|
|
210
321
|
|
|
211
322
|
"## System Health at Error Time\n\n#{items.join("\n")}"
|
|
@@ -215,8 +326,14 @@ module RailsErrorDashboard
|
|
|
215
326
|
return nil if @related_errors.nil? || @related_errors.empty?
|
|
216
327
|
|
|
217
328
|
items = @related_errors.map { |r|
|
|
218
|
-
|
|
219
|
-
|
|
329
|
+
# related_errors can be plain ErrorLog objects or wrapped objects with .error/.similarity
|
|
330
|
+
error = r.respond_to?(:error) ? r.error : r
|
|
331
|
+
if r.respond_to?(:similarity)
|
|
332
|
+
pct = (r.similarity * 100).round(1)
|
|
333
|
+
"- `#{error.error_type}` — #{error.message} (#{pct}% similar, #{error.occurrence_count} occurrences)"
|
|
334
|
+
else
|
|
335
|
+
"- `#{error.error_type}` — #{error.message} (#{error.occurrence_count} occurrences)"
|
|
336
|
+
end
|
|
220
337
|
}
|
|
221
338
|
|
|
222
339
|
"## Related Errors\n\n#{items.join("\n")}"
|
|
@@ -224,13 +341,12 @@ module RailsErrorDashboard
|
|
|
224
341
|
|
|
225
342
|
def metadata_section
|
|
226
343
|
items = []
|
|
227
|
-
items << "- **Severity:** #{@error.severity}" if @error.severity.present?
|
|
228
|
-
items << "- **Status:** #{@error.status}" if @error.status.present?
|
|
229
|
-
items << "- **Priority:** P#{3 - @error.priority_level}" if @error.priority_level.present?
|
|
230
344
|
items << "- **Platform:** #{@error.platform}" if @error.platform.present?
|
|
231
345
|
items << "- **First seen:** #{@error.first_seen_at&.utc&.strftime("%Y-%m-%d %H:%M:%S UTC")}" if @error.first_seen_at
|
|
232
346
|
items << "- **Occurrences:** #{@error.occurrence_count}" if @error.occurrence_count
|
|
233
|
-
items << "- **
|
|
347
|
+
items << "- **User ID:** #{@error.user_id}" if @error.user_id.present?
|
|
348
|
+
|
|
349
|
+
return nil if items.empty?
|
|
234
350
|
|
|
235
351
|
"## Metadata\n\n#{items.join("\n")}"
|
|
236
352
|
end
|
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.5.
|
|
4
|
+
version: 0.5.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -467,7 +467,7 @@ metadata:
|
|
|
467
467
|
bug_tracker_uri: https://github.com/AnjanJ/rails_error_dashboard/issues
|
|
468
468
|
funding_uri: https://buymeacoffee.com/anjanj
|
|
469
469
|
post_install_message: "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n
|
|
470
|
-
\ Rails Error Dashboard v0.5.
|
|
470
|
+
\ Rails Error Dashboard v0.5.7\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
|
|
471
471
|
First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
|
|
472
472
|
db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
|
|
473
473
|
=> '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n
|