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.message}")
  # 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