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: 0061cfa9a544478cab75e78d0b785f3f2fbc319d46cdfe0080d6c2d135499bad
4
- data.tar.gz: e4fedd33e1db8813bc25eda9f842a02866c1b195706e519e3ab6e93a6e4fc9cd
3
+ metadata.gz: 0cfea703e2d74b6771503d098e3bc5f048ec781923ee08a1692e34bbf28c9734
4
+ data.tar.gz: 4b77fb5c2dd00920d2eaca85b92d35dcbb19282408dee936584deffd2e7126a6
5
5
  SHA512:
6
- metadata.gz: 48a726a07f35cf8b7d1f390aa0da7d2e0741a949d4a963da133606b89795d954d7e85f02f08ced7512b6569ab0f40cf0507988b64d840f4f8366f861f4650601
7
- data.tar.gz: 825b1d89ff00f82c270568381740f0798c6d2158cf726883ed12eda5f1e87f8cba2c1dfa91037bcd2f8054b5434129d3957cb6d2f9a0ed1207a9b87f461c7db4
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
- lines << "**Class:** #{self_class}" if self_class
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 << "- **IP:** #{@error.ip_address}" if @error.ip_address.present?
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
- items << "- **Memory:** #{health["process_memory_mb"]} MB RSS" if health["process_memory_mb"]
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
- items << "- **DB Pool:** #{pool["busy"]}/#{pool["size"]} busy" if pool["size"]
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
- gc = health["gc_stats"]
270
+ # GC stats
271
+ gc = health["gc"] || health["gc_stats"]
205
272
  if gc.is_a?(Hash)
206
- items << "- **GC:** #{gc["major_gc_count"]} major cycles" if gc["major_gc_count"]
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
- pct = (r.similarity * 100).round(1)
219
- "- `#{r.error.error_type}` #{r.error.message} (#{pct}% similar, #{r.error.occurrence_count} occurrences)"
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 << "- **Assigned to:** #{@error.assigned_to}" if @error.assigned_to.present?
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
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.5.5"
2
+ VERSION = "0.5.7"
3
3
  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.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.5\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n\U0001F195
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