rails_error_dashboard 0.5.14 → 0.6.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/rails_error_dashboard/errors_controller.rb +31 -26
  3. data/app/helpers/rails_error_dashboard/application_helper.rb +12 -5
  4. data/app/views/layouts/rails_error_dashboard.html.erb +1218 -1936
  5. data/app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb +4 -4
  6. data/app/views/rails_error_dashboard/errors/_co_occurring_errors.html.erb +1 -1
  7. data/app/views/rails_error_dashboard/errors/_discussion.html.erb +3 -3
  8. data/app/views/rails_error_dashboard/errors/_error_cascades.html.erb +1 -1
  9. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +69 -79
  10. data/app/views/rails_error_dashboard/errors/_instance_variables.html.erb +1 -1
  11. data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +1 -1
  12. data/app/views/rails_error_dashboard/errors/_local_variables.html.erb +1 -1
  13. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +2 -2
  14. data/app/views/rails_error_dashboard/errors/_request_context.html.erb +1 -1
  15. data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +1 -1
  16. data/app/views/rails_error_dashboard/errors/_similar_errors.html.erb +1 -1
  17. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +1 -1
  18. data/app/views/rails_error_dashboard/errors/actioncable_health_summary.html.erb +6 -6
  19. data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +6 -6
  20. data/app/views/rails_error_dashboard/errors/analytics.html.erb +34 -50
  21. data/app/views/rails_error_dashboard/errors/cache_health_summary.html.erb +7 -7
  22. data/app/views/rails_error_dashboard/errors/correlation.html.erb +11 -11
  23. data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +114 -172
  24. data/app/views/rails_error_dashboard/errors/deprecations.html.erb +7 -7
  25. data/app/views/rails_error_dashboard/errors/diagnostic_dumps.html.erb +6 -6
  26. data/app/views/rails_error_dashboard/errors/index.html.erb +294 -622
  27. data/app/views/rails_error_dashboard/errors/job_health_summary.html.erb +7 -7
  28. data/app/views/rails_error_dashboard/errors/n_plus_one_summary.html.erb +7 -7
  29. data/app/views/rails_error_dashboard/errors/overview.html.erb +192 -363
  30. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +11 -11
  31. data/app/views/rails_error_dashboard/errors/rack_attack_summary.html.erb +6 -6
  32. data/app/views/rails_error_dashboard/errors/releases.html.erb +6 -6
  33. data/app/views/rails_error_dashboard/errors/settings.html.erb +32 -52
  34. data/app/views/rails_error_dashboard/errors/show.html.erb +200 -203
  35. data/app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb +7 -7
  36. data/app/views/rails_error_dashboard/errors/user_impact.html.erb +6 -6
  37. data/lib/rails_error_dashboard/commands/log_error.rb +14 -3
  38. data/lib/rails_error_dashboard/configuration.rb +6 -0
  39. data/lib/rails_error_dashboard/error_reporter.rb +11 -1
  40. data/lib/rails_error_dashboard/services/backtrace_parser.rb +10 -4
  41. data/lib/rails_error_dashboard/services/backtrace_processor.rb +44 -2
  42. data/lib/rails_error_dashboard/services/error_broadcaster.rb +19 -4
  43. data/lib/rails_error_dashboard/services/notification_helpers.rb +9 -2
  44. data/lib/rails_error_dashboard/subscribers/issue_tracker_subscriber.rb +21 -0
  45. data/lib/rails_error_dashboard/version.rb +1 -1
  46. metadata +2 -2
@@ -94,13 +94,19 @@ module RailsErrorDashboard
94
94
  end
95
95
 
96
96
  def app_code?(file_path)
97
- # Match /app/, /lib/ directories in the application
98
- file_path.include?("/app/") ||
99
- (file_path.include?("/lib/") && !file_path.include?("/gems/") && !file_path.include?("/ruby/"))
97
+ # Match /app/ or app/ (shortened paths start without leading /)
98
+ return true if file_path.include?("/app/") || file_path.start_with?("app/")
99
+
100
+ # Match /lib/ or lib/ but exclude gems and ruby stdlib
101
+ lib_path = file_path.include?("/lib/") || file_path.start_with?("lib/")
102
+ return false unless lib_path
103
+
104
+ !file_path.include?("/gems/") && !file_path.include?("/ruby/") &&
105
+ !file_path.start_with?("gems/") && !file_path.start_with?("ruby/")
100
106
  end
101
107
 
102
108
  def gem_code?(file_path)
103
- file_path.include?("/gems/") ||
109
+ file_path.include?("/gems/") || file_path.start_with?("gems/") ||
104
110
  file_path.include?("/bundler/gems/") ||
105
111
  file_path.include?("/vendor/bundle/")
106
112
  end
@@ -5,7 +5,15 @@ module RailsErrorDashboard
5
5
  # Pure algorithm service for backtrace processing
6
6
  # Handles truncation and signature generation with no database access.
7
7
  class BacktraceProcessor
8
- # Truncate backtrace to a maximum number of lines
8
+ # Truncate backtrace to a maximum number of lines and shorten gem paths.
9
+ #
10
+ # Gem paths like:
11
+ # /home/user/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/actionpack-8.1.3/lib/...
12
+ # are shortened to:
13
+ # gems/actionpack-8.1.3/lib/...
14
+ #
15
+ # This saves significant disk space without losing debugging value (issue #115).
16
+ #
9
17
  # @param backtrace [Array<String>, nil] The backtrace lines
10
18
  # @param max_lines [Integer] Maximum lines to keep
11
19
  # @return [String, nil] Truncated backtrace as a single string
@@ -14,7 +22,7 @@ module RailsErrorDashboard
14
22
 
15
23
  max_lines ||= RailsErrorDashboard.configuration.max_backtrace_lines
16
24
 
17
- limited_backtrace = backtrace.first(max_lines)
25
+ limited_backtrace = backtrace.first(max_lines).map { |line| shorten_gem_path(line) }
18
26
  result = limited_backtrace.join("\n")
19
27
 
20
28
  if backtrace.length > max_lines
@@ -25,6 +33,40 @@ module RailsErrorDashboard
25
33
  result
26
34
  end
27
35
 
36
+ # Shorten gem/ruby paths to remove user-specific prefixes.
37
+ # Preserves gem name + version for debugging.
38
+ #
39
+ # Examples:
40
+ # /home/user/.gem/ruby/3.4.0/gems/rack-3.2.6/lib/rack/head.rb:15
41
+ # → gems/rack-3.2.6/lib/rack/head.rb:15
42
+ #
43
+ # /home/user/.local/share/mise/installs/ruby/4.0.2/lib/ruby/4.0.0/net/http.rb:1234
44
+ # → ruby/4.0.0/net/http.rb:1234
45
+ #
46
+ # /home/user/myapp/app/controllers/users_controller.rb:10
47
+ # → app/controllers/users_controller.rb:10
48
+ #
49
+ # @param line [String] A single backtrace line
50
+ # @return [String] The line with shortened path
51
+ def self.shorten_gem_path(line)
52
+ # Strip everything before /gems/ (gem code)
53
+ if line.include?("/gems/")
54
+ line.sub(%r{^.*/gems/}, "gems/")
55
+ # Strip everything before /lib/ruby/ (Ruby stdlib)
56
+ elsif line.include?("/lib/ruby/")
57
+ line.sub(%r{^.*/lib/ruby/}, "ruby/")
58
+ # Strip everything before /app/ (application code)
59
+ elsif line.include?("/app/")
60
+ line.sub(%r{^.*/app/}, "app/")
61
+ # Strip everything before /lib/ for app lib code (but not gem/ruby paths already handled)
62
+ elsif line.include?("/lib/")
63
+ line.sub(%r{^.*/lib/}, "lib/")
64
+ else
65
+ line
66
+ end
67
+ end
68
+ # Keep public — used by LogError for cause chain backtrace shortening too.
69
+
28
70
  # Calculate a signature hash from backtrace for fuzzy similarity matching
29
71
  # Extracts file paths and method names, ignoring line numbers,
30
72
  # then produces an order-independent SHA256 digest.
@@ -90,17 +90,32 @@ module RailsErrorDashboard
90
90
  )
91
91
  end
92
92
 
93
- # Check if broadcasting infrastructure is available
93
+ # Check if broadcasting infrastructure is available.
94
+ # Returns false when Turbo/ActionCable isn't loaded, or when the
95
+ # ActionCable pubsub adapter can't be reached (e.g., Redis down).
96
+ # Uses a 60-second cooldown after failure to avoid hammering a
97
+ # dead Redis on every error (issue #114).
94
98
  # @return [Boolean]
95
99
  def self.available?
96
100
  return false unless defined?(Turbo)
97
101
  return false unless defined?(ActionCable)
98
102
 
99
- Rails.cache.write("rails_error_dashboard_broadcast_test", true, expires_in: 1.second)
100
- Rails.cache.delete("rails_error_dashboard_broadcast_test")
103
+ # Circuit breaker: skip broadcast attempts for 60s after a failure
104
+ if @broadcast_unavailable_until && Time.current < @broadcast_unavailable_until
105
+ return false
106
+ end
107
+
108
+ # Verify the pubsub adapter is reachable — without this,
109
+ # broadcast_* calls attempt Redis and fail loudly when it's down
110
+ server = ActionCable.server
111
+ return false unless server.respond_to?(:pubsub)
112
+
113
+ server.pubsub
114
+ @broadcast_unavailable_until = nil
101
115
  true
102
116
  rescue => e
103
- Rails.logger.debug("[RailsErrorDashboard] Broadcast not available: #{e.message}")
117
+ @broadcast_unavailable_until = Time.current + 60
118
+ RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] Broadcast not available (pausing 60s): #{e.message}")
104
119
  false
105
120
  end
106
121
  end
@@ -13,9 +13,16 @@ module RailsErrorDashboard
13
13
  # @param error_log [ErrorLog] The error
14
14
  # @return [String] Full URL to the error detail page
15
15
  def dashboard_url(error_log)
16
- base_url = RailsErrorDashboard.configuration.dashboard_base_url || "http://localhost:3000"
16
+ base_url = (RailsErrorDashboard.configuration.dashboard_base_url || "http://localhost:3000").chomp("/")
17
17
  mount_path = RailsErrorDashboard.configuration.engine_mount_path
18
- "#{base_url}#{mount_path}/errors/#{error_log.id}"
18
+
19
+ # Avoid doubling the mount path when base_url already includes it.
20
+ # e.g., base_url="https://app.com/red" + mount_path="/red" → don't produce "/red/red"
21
+ if mount_path.present? && base_url.end_with?(mount_path.chomp("/"))
22
+ "#{base_url}/errors/#{error_log.id}"
23
+ else
24
+ "#{base_url}#{mount_path}/errors/#{error_log.id}"
25
+ end
19
26
  end
20
27
 
21
28
  # Truncate a message to a maximum length
@@ -53,6 +53,27 @@ module RailsErrorDashboard
53
53
  return false unless config.enable_issue_tracking
54
54
  return false if error_log.external_issue_url.present?
55
55
 
56
+ # Check if another error record with the same hash already has a linked
57
+ # issue. The 24-hour dedup window in FindOrIncrementError can create new
58
+ # ErrorLog records for the same logical error — we must not create
59
+ # duplicate GitHub/GitLab issues for them (issue #114 screenshot).
60
+ existing = ErrorLog
61
+ .where(error_hash: error_log.error_hash, application_id: error_log.application_id)
62
+ .where.not(external_issue_url: [ nil, "" ])
63
+ .where.not(id: error_log.id)
64
+ .order(created_at: :desc)
65
+ .first
66
+
67
+ if existing
68
+ # Link this record to the existing issue instead of creating a new one
69
+ error_log.update_columns(
70
+ external_issue_url: existing.external_issue_url,
71
+ external_issue_number: existing.external_issue_number,
72
+ external_issue_provider: existing.external_issue_provider
73
+ )
74
+ return false
75
+ end
76
+
56
77
  # First occurrence — always auto-create
57
78
  return true if error_log.occurrence_count == 1
58
79
 
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.5.14"
2
+ VERSION = "0.6.0"
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.14
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -496,7 +496,7 @@ metadata:
496
496
  funding_uri: https://github.com/sponsors/AnjanJ
497
497
  post_install_message: |
498
498
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
499
- RED (Rails Error Dashboard) v0.5.14
499
+ RED (Rails Error Dashboard) v0.6.0
500
500
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
501
501
 
502
502
  First install: