Module: AllStak::GlobalHandler
- Defined in:
- lib/allstak/global_handler.rb
Overview
Global uncaught-exception capture.
Ruby has no first-class “uncaught exception” callback, but it runs ‘at_exit` blocks during interpreter teardown — and while they run, `$!` still holds the exception that is killing the process (if any). Inspecting `$!` from inside an `at_exit` block is the idiomatic way to catch a top-level unhandled exception. We capture it, mark it unhandled (mechanism handled=false), and do a best-effort synchronous flush before the process dies.
We are deliberately conservative about WHAT counts as an unhandled termination so a clean exit (or a normal ‘exit`/`exit!`) is never reported as an error:
- `$!` must be present and be an Exception.
- SystemExit is treated as unhandled only when its status is non-zero
(i.e. `exit(1)` / `abort`), never `exit(0)` / `exit` (clean exit).
- SignalException (e.g. Ctrl-C / SIGINT) is ignored.
Constant Summary collapse
- MECHANISM =
"at_exit".freeze
Class Method Summary collapse
-
.capture_unhandled(exc) ⇒ Object
Capture an exception as a global, unhandled event and flush synchronously.
-
.install!(logger = nil) ⇒ Object
Install the process-wide at_exit hook.
- .installed? ⇒ Boolean
-
.reset! ⇒ Object
Test seam: forget that we installed (does NOT unregister the real at_exit block — Ruby has no API for that — but lets a test drive install!/idempotency logic deterministically).
-
.run_at_exit(exc) ⇒ Object
The body of the at_exit hook, factored out so it is directly unit testable without actually terminating the process.
-
.unhandled_termination?(exc) ⇒ Boolean
Decide whether ‘exc` represents a genuine unhandled termination that we should report, vs.
Class Method Details
.capture_unhandled(exc) ⇒ Object
Capture an exception as a global, unhandled event and flush synchronously. Safe to call directly as a documented integration point:
begin
run_worker
rescue => e
AllStak::GlobalHandler.capture_unhandled(e)
raise
end
Also surfaced as AllStak.capture_unhandled.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/allstak/global_handler.rb', line 80 def capture_unhandled(exc) return nil unless AllStak.initialized? client = AllStak.client begin client.capture_exception( exc, metadata: { "mechanism" => MECHANISM, "handled" => false } ) rescue => e AllStak.logger&.debug("[AllStak] at_exit capture failed: #{e.class}: #{e.}") ensure # Best-effort synchronous flush so buffered telemetry leaves the # process before it dies. Never raise out of an at_exit hook. client.flush rescue nil end end |
.install!(logger = nil) ⇒ Object
Install the process-wide at_exit hook. Idempotent: the actual at_exit block is registered exactly once per process, regardless of how many times this is called (reconfigure, multiple configure calls, etc.). The block reads the live client at exit time, so reconfiguration is honored without re-registering.
29 30 31 32 33 34 |
# File 'lib/allstak/global_handler.rb', line 29 def install!(logger = nil) return if @installed @installed = true logger&.debug("[AllStak] installing at_exit unhandled-exception handler") at_exit { run_at_exit($!) } end |
.installed? ⇒ Boolean
36 37 38 |
# File 'lib/allstak/global_handler.rb', line 36 def installed? @installed == true end |
.reset! ⇒ Object
Test seam: forget that we installed (does NOT unregister the real at_exit block — Ruby has no API for that — but lets a test drive install!/idempotency logic deterministically).
43 44 45 |
# File 'lib/allstak/global_handler.rb', line 43 def reset! @installed = false end |
.run_at_exit(exc) ⇒ Object
The body of the at_exit hook, factored out so it is directly unit testable without actually terminating the process. ‘exc` is whatever `$!` held at exit time.
50 51 52 53 54 |
# File 'lib/allstak/global_handler.rb', line 50 def run_at_exit(exc) return unless AllStak.initialized? return unless unhandled_termination?(exc) capture_unhandled(exc) end |
.unhandled_termination?(exc) ⇒ Boolean
Decide whether ‘exc` represents a genuine unhandled termination that we should report, vs. a clean/expected exit we must ignore.
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/allstak/global_handler.rb', line 58 def unhandled_termination?(exc) return false unless exc.is_a?(Exception) # Ignore Ctrl-C / signal-driven teardown. return false if exc.is_a?(SignalException) # `exit`/`exit(0)` raise SystemExit with success? == true: clean exit. if exc.is_a?(SystemExit) return exc.respond_to?(:success?) ? !exc.success? : exc.status.to_i != 0 end true end |