Class: RailsErrorDashboard::Services::CrashCapture
- Inherits:
-
Object
- Object
- RailsErrorDashboard::Services::CrashCapture
- Defined in:
- lib/rails_error_dashboard/services/crash_capture.rb
Overview
Last-resort crash capture via Ruby’s ‘at_exit` hook.
When the Rails process dies from an unhandled exception, the error never reaches the error subscriber or middleware. This service registers an ‘at_exit` hook that captures `$!` (the fatal exception) and writes it to disk as JSON. On the next boot, `import!` reads crash files and creates ErrorLog records with severity “fatal”.
Safety contract:
-
Default OFF (opt-in via config.enable_crash_capture)
-
Writes to tmpfile, NOT the database (connection pool may be closed)
-
Timeout: 1 second max for file write, then give up
-
Skips clean exits (SystemExit.success?, SignalException)
-
Every operation wrapped in rescue (crash capture must never itself crash)
-
Zero runtime overhead — hook only fires during process shutdown
Constant Summary collapse
- FILE_PREFIX =
"red_crash_"
Class Method Summary collapse
-
.capture!(exception) ⇒ Object
Capture a fatal exception to disk.
-
.disable! ⇒ Object
Disable crash capture.
-
.enable! ⇒ true
Enable crash capture.
-
.enabled? ⇒ Boolean
Whether crash capture is enabled.
-
.import! ⇒ Object
Import crash files from disk into the database.
-
.reset! ⇒ Object
Reset internal state (for testing).
Class Method Details
.capture!(exception) ⇒ Object
Capture a fatal exception to disk. Called from the ‘at_exit` hook.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/rails_error_dashboard/services/crash_capture.rb', line 54 def capture!(exception) return unless @enabled return unless exception return if exception.is_a?(SystemExit) && exception.success? return if exception.is_a?(SignalException) crash_data = build_crash_data(exception) path = crash_file_path Timeout.timeout(1) do FileUtils.mkdir_p(File.dirname(path)) File.write(path, JSON.generate(crash_data)) end rescue => e # Crash capture must NEVER itself crash the exit. # Best-effort stderr warning (may not be visible). $stderr.puts "[RailsErrorDashboard] CrashCapture.capture! failed: #{e.class} - #{e.}" rescue nil end |
.disable! ⇒ Object
Disable crash capture. The ‘at_exit` hook remains registered but will no-op because `@enabled` is false.
43 44 45 |
# File 'lib/rails_error_dashboard/services/crash_capture.rb', line 43 def disable! @enabled = false end |
.enable! ⇒ true
Enable crash capture. Registers the ‘at_exit` hook and records boot time.
30 31 32 33 34 35 36 37 38 39 |
# File 'lib/rails_error_dashboard/services/crash_capture.rb', line 30 def enable! return true if enabled? @boot_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) @enabled = true at_exit { capture!($!) } true end |
.enabled? ⇒ Boolean
Returns whether crash capture is enabled.
48 49 50 |
# File 'lib/rails_error_dashboard/services/crash_capture.rb', line 48 def enabled? @enabled == true end |
.import! ⇒ Object
Import crash files from disk into the database. Called during boot (config.after_initialize) BEFORE enable! so old crashes are processed first.
75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/rails_error_dashboard/services/crash_capture.rb', line 75 def import! dir = crash_capture_dir return unless Dir.exist?(dir) pattern = File.join(dir, "#{FILE_PREFIX}*.json") Dir.glob(pattern).each do |file| import_crash_file(file) end rescue => e RailsErrorDashboard::Logger.debug( "[RailsErrorDashboard] CrashCapture.import! failed: #{e.class} - #{e.}" ) end |
.reset! ⇒ Object
Reset internal state (for testing)
90 91 92 93 |
# File 'lib/rails_error_dashboard/services/crash_capture.rb', line 90 def reset! @enabled = false @boot_time = nil end |