ForgeOpsTracker
Rails exception reporting client for a private, self-hosted ForgeOps tracker instance.
Installation
gem "forge_ops_tracker", path: "gems/forge_ops_tracker" # or git: "..." once split into its own repo
Configuration
Set a DSN (from a project's settings page in ForgeOps) via an initializer or environment variable:
# config/initializers/forge_ops_tracker.rb
ForgeOpsTracker.configure do |config|
config.dsn = ENV["FORGE_OPS_DSN"] # "https://<api_key>@forgeops.example.com/api/v1/events"
config.release = ENV["HEROKU_SLUG_COMMIT"] || `git rev-parse HEAD`.strip
config.enabled_environments = %w[production staging] # default; reporting is a no-op elsewhere
end
Delivery happens on a small background thread with a bounded queue and short HTTP timeouts. Every failure mode -- network errors, timeouts, a full queue, a malformed DSN -- is caught and dropped rather than raised, so a broken or unreachable tracker can never take down the host app.
What gets reported automatically, and what doesn't
Unhandled exceptions need no further wiring at all. The gem's Railtie subscribes to
Rails.error automatically, and Rails itself reports anything that crashes a request or job
through that same channel -- install the gem, set a DSN, and those show up in ForgeOps with zero
other code changes.
Handled exceptions -- code that catches its own error to keep running -- are a different story.
A plain rescue the gem never hears about, no matter what:
begin
charge_card(order)
rescue Stripe::CardError => e
logger.warn("card declined: #{e.}")
# ForgeOps never sees this -- nothing here goes through Rails.error at all.
end
To report it and keep swallowing it, swap the bare rescue for Rails' own built-in
Rails.error.handle -- this isn't a ForgeOps-specific API, it's Rails' own error-reporting
convention (Rails 7+), which the gem just happens to already be subscribed to:
Rails.error.handle(fallback: -> { nil }) do
charge_card(order)
end
# Reported to every Rails.error subscriber, including this gem, then swallowed --
# execution continues past the block either way.
Or Rails.error.record, if you want it reported and still raised (e.g. so a background job's
own retry logic still sees the failure):
Rails.error.record { charge_card(order) } # reports, then re-raises
Bottom line: if an exception would otherwise crash something, you're already covered. If your own
code already catches and handles it, route that specific rescue through Rails.error.handle/
.record instead of a bare one wherever you want ForgeOps to know about it.
PII scrubbing
By default, the message, backtrace, and any context/tags you attach are scanned for likely
personal data -- email addresses, formatted SSNs/credit cards, known API key/token formats, and
anything under a suspiciously-named key (password, api_key, ssn, and similar) -- and redacted
before the payload ever leaves this process. ForgeOps itself scrubs again on arrival regardless, so
this is a second, earlier layer, not the only one.
To disable it (e.g. if your app already scrubs its own error context, or you have your own reasons to want the raw payload):
ForgeOpsTracker.configure do |config|
config.scrub_pii = false
end