First-class monitoring for Rails. One gem instruments requests, jobs, scheduled tasks, commands, queries, exceptions, cache, mail, broadcasts, outgoing HTTP, storage, views, and logs, and links them into one trace per execution, for about half a millisecond per request plus tens of microseconds per query, with zero writes to your database.
By default all of it stays inside the app:
embedded mode serves the full dashboard at
/railwatch out of two SQLite files your app owns, with no token, no
Node, no Redis and no job worker. Railwatch Cloud is optional: it
delivers alerts to Slack, email, webhooks or Linear (including when the
app is down), serves MCP to your AI assistant, and puts many apps and
servers in one place.
bundle add railwatch # 1. add the public gem
bin/rails generate railwatch:install # 2. embedded: databases, Puma writer, dashboard at /railwatchRestart and open /railwatch. It is open in development; before you
deploy, give it a password with
RAILS_ENV=production bin/rails railwatch:authentication:configure
(it answers 401 in production until you do). On a PostgreSQL or MySQL
app without the sqlite3 gem, the generator adds it to the Gemfile;
run bundle install and bin/rails db:prepare to finish.
To send everything to Railwatch Cloud instead, pass --cloud or a token
option:
bundle add railwatch
bin/rails generate railwatch:install --prompt-token # hidden token input plus app wiring
bin/rails railwatch:doctor # check every piece is wired up after restartThe generator's flags, getting a token, and deploying with Kamal, Docker, Heroku, or Render are covered in Getting started. For an unreleased revision, use the Git source instead:
gem "railwatch", github: "Rebulk/railwatch"- Requests, jobs, scheduled tasks, commands, queries, exceptions, and logs, linked into one trace per execution (Record types).
- Sampling decided once per execution, plus tail sampling that keeps a sampled-out request that turns out slow or raises (Configuration).
- An optional stack profiler through
vernierorstackprof, off by default (Configuration). - LLM calls made through RubyLLM: tokens, cost, cut-off answers, tool calls and workflows, each tied to the request or job that made it (Record types).
- A browser client for Inertia apps: page-visit timing, Core Web Vitals, and browser errors (Getting started).
- RSpec and Minitest matchers that turn a query budget into a CI gate (Testing).
- With Railwatch Cloud, an MCP server so Claude Code, Cursor, VS Code, or Zed can read your production data (AI assistants and MCP).
Configuration lives in config/initializers/railwatch.rb; most options
also have a RAILWATCH_* environment variable.
Railwatch.configure do |c|
c.sample = { requests: 0.1, jobs: 1.0 }
c.user { |u| { id: u.id, name: u.name, email: u.email } }
endThe same instrumentation runs in your test suite, so a spec can hold a hot path to a query budget:
expect { get "/widgets" }.to have_railwatch_queries(at_most: 6)
expect { get "/widgets" }.not_to have_railwatch_n_plus_oneTelemetry goes to two SQLite files the app owns -- railwatch for
issues, comments and saved views, railwatch_telemetry for what the app
reports -- and the dashboard is served at /railwatch from a bundle
shipped inside the gem. Nothing leaves the machine unless you turn on
export to Railwatch Cloud, and there is nothing else to run.
Puma forks a single writer process (plugin :railwatch, which the
installer adds) that owns both files. The web workers hand it batches
over a Unix socket instead of writing SQLite on their own threads, and it
runs the maintenance clock too, so exception grouping, rollups, retention
and threshold scans happen without a queue.
That dashboard reads every query, log line and exception the app
produced, so it is gated the way Mission Control Jobs is: HTTP Basic is
on, and with no credentials every request is 401 until you set them
with bin/rails railwatch:authentication:configure. Development is the
exception: with no credentials set there, it is open. Apps that would
rather use their own authentication turn Basic off and gate the pages
with base_controller_class or a routes constraint around the mount; a
dashboard_user resolver then names the operator and authorizes live
updates, but does not gate pages. Embedded mode
covers all of it, including upgrades and what it costs to store.
- Getting started — the embedded install, the Railwatch Cloud install and its token, the three optional lines, and deploying with Kamal, Docker, Heroku, Render, or none of them.
- Configuration — every option and
RAILWATCH_*variable, field by field. - Record types — every record Railwatch ships and every attribute on it, sourced from the code that builds it.
- Testing — the RSpec and Minitest matchers, and a CI performance gate.
- AI assistants and MCP — connecting Claude Code, Cursor, VS Code, or Zed to your production data through Railwatch Cloud.
- Replacing Sentry — a step-by-step migration, option by option and call site by call site.
- Coming from Laravel Nightwatch — the record-type mapping and the sampling model, for Laravel people.
- Embedded mode — the default: the whole dashboard inside your app, telemetry in your own SQLite files, and optional export to Railwatch Cloud.
- Self-hosting — pointing the gem at your own Railwatch Cloud.
- Troubleshooting — every failure mode, paired
with the
railwatch:doctorline it shows up as. - FAQ — overhead numbers, retention, PII posture, SQLite.
- Security — transport, capture defaults, the browser beacon, and application responsibilities.
AI coding agents working on an app that uses Railwatch: llms.txt
and AGENTS.md.
Railwatch subscribes to Rails.error on install, so existing
Rails.error.report and Rails.error.handle calls are captured with no
code changes. Each exception is linked to the request, job, or command
it happened inside. The option-by-option mapping and the
supported-workload matrix live in
Replacing Sentry.
bundle install
bundle exec rspec