To monitor a Ruby on Rails app in production, you need visibility into six things: request latency, slow SQL, N+1 queries, background jobs, memory, and errors — plus an external uptime check. Rails already emits most of this data through ActiveSupport::Notifications; you just have to capture it.
This guide shows how to monitor each one, with copy-paste code you can drop into your app today, and when it makes sense to reach for an APM tool instead.
Quick answer: how to monitor a Rails app
- Switch to structured (JSON) request logs with Lograge.
- Subscribe to
process_action.action_controllerto flag slow requests (track p95/p99, not averages). - Subscribe to
sql.active_recordand enablepg_stat_statementsto find slow and frequent queries. - Catch N+1 queries in development and CI with Bullet or
strict_loading. - Monitor Sidekiq / Active Job queue latency, retries and failures.
- Track process memory (RSS) over time to spot leaks and bloat.
- Report exceptions with
Rails.errorand add an external uptime check on/up.
1. Structured request logs
Default Rails logs spread one request over many lines, which makes them hard to aggregate. Lograge collapses each request into a single line with controller, action, status and timings:
# Gemfile gem "lograge" # config/environments/production.rb config.lograge.enabled = true config.lograge.formatter = Lograge::Formatters::Json.new
JSON logs can be shipped to any log tool and queried by endpoint, status or duration.
2. Request performance: find slow endpoints
Every controller action emits a process_action.action_controller event with total, database and view time. A small initializer is enough to start flagging slow requests:
# config/initializers/slow_request_monitor.rb
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |event|
next if event.duration < 500 # ms
p = event.payload
Rails.logger.warn(
"[slow_request] #{p[:controller]}##{p[:action]} " \
"total=#{event.duration.round}ms db=#{p[:db_runtime]&.round}ms view=#{p[:view_runtime]&.round}ms"
)
end
What to track per endpoint:
- p95 and p99 latency — a fast average can hide a very slow tail
- Throughput — slow endpoints that are rarely hit matter less than moderately slow ones hit constantly
- Error rate — 5xx responses per endpoint
- DB time vs. view time — tells you whether to fix queries or rendering
3. Database: slow and frequent SQL queries
For most Rails apps the database is the bottleneck. Log slow queries straight from Active Record:
# config/initializers/slow_sql_monitor.rb
ActiveSupport::Notifications.subscribe("sql.active_record") do |event|
next if event.payload[:name] == "SCHEMA" || event.duration < 100 # ms
Rails.logger.warn("[slow_sql] #{event.duration.round}ms #{event.payload[:sql]}")
end
Slow queries are only half the story — a 5 ms query called 10,000 times an hour costs more than one 2-second report. On PostgreSQL, enable the pg_stat_statements extension and look at total time:
SELECT calls,
round(total_exec_time) AS total_ms,
round(mean_exec_time, 1) AS mean_ms,
left(query, 120) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
The biggest wins usually come from adding a missing index or simplifying a query, not from changing Ruby code.
4. Catch N+1 queries before production
N+1 queries are the most common Rails performance regression, and often slip in during harmless-looking refactors. Detect them in development and fail the build in CI with Bullet:
# config/environments/development.rb config.after_initialize do Bullet.enable = true Bullet.rails_logger = true end # config/environments/test.rb config.after_initialize do Bullet.enable = true Bullet.raise = true end
Alternatively, use Rails' built-in strict_loading on associations that must always be preloaded. In production, watch the number of queries per request — a sudden jump after a deploy is almost always an N+1.
5. Background jobs (Sidekiq / Active Job)
Job problems stay hidden because users don't see them immediately. The key metric is queue latency: how long the oldest job has been waiting.
require "sidekiq/api"
Sidekiq::Queue.all.each do |queue|
puts "#{queue.name}: size=#{queue.size} latency=#{queue.latency.round(1)}s"
end
stats = Sidekiq::Stats.new
puts "retries=#{stats.retry_size} dead=#{stats.dead_size} failed=#{stats.failed}"
Also subscribe to perform.active_job to record execution time per job class. Alert when latency exceeds what your users would tolerate (e.g. 30 seconds for emails) and when the retry set keeps growing — a job silently retrying thousands of times is a classic hidden cost.
6. Memory usage
Rails processes grow over time because of object retention, unbounded caches or leaky gems. Track RSS per process and look at the trend, not the snapshot:
# Linux only: current process RSS in MB
rss_mb = File.read("/proc/self/status")[/VmRSS:\s+(\d+)/, 1].to_i / 1024
Rails.logger.info("[memory] pid=#{Process.pid} rss=#{rss_mb}MB")
A steady climb that only resets on deploy or restart points to a leak; sudden jumps usually point to a specific request or job loading too many records into memory.
7. Errors and uptime
Since Rails 7, you can subscribe to all handled and unhandled errors through the error reporter:
# config/initializers/error_monitor.rb
class ErrorLogger
def report(error, handled:, severity:, context:, source: nil)
Rails.logger.error("[error] #{error.class}: #{error.message} severity=#{severity} source=#{source}")
end
end
Rails.error.subscribe(ErrorLogger.new)
Finally, monitor the app from the outside. Rails 7.1+ ships a /up health check route; point an external uptime checker at it so you know when the app is down even if your logging is down too.
DIY vs. APM: when to use a Rails monitoring tool
The snippets above get you surprisingly far, but logs only tell you what happened. Once you need to know why, you want a tool that correlates each request with its SQL queries, shows p95 trends over time, and highlights regressions after deploys.
Common options for Rails include the free, self-hosted rails_performance gem, hosted APMs like New Relic, Datadog, AppSignal, Scout and Skylight, and DeadBro, which we build specifically for Rails — request traces, SQL and N+1 detection, job and memory monitoring, plus uptime and server checks in one place.
What to check daily vs. weekly
Daily: error rate, slowest endpoints, failed and retrying jobs, uptime alerts.
Weekly: p95 trends per endpoint, top queries by total time, memory growth, queue latency trends.
FAQ
What is the best way to monitor a Rails application?
Start with structured logs and ActiveSupport::Notifications subscribers for requests, SQL and jobs, then add an APM once you need per-request traces and historical trends. Always add an external uptime check.
How do I find slow queries in Rails?
Subscribe to sql.active_record events to log queries above a threshold, and use pg_stat_statements (PostgreSQL) to find queries with the highest total execution time.
How do I detect N+1 queries in Rails?
Use the Bullet gem in development and test, or strict_loading on associations. In production, track queries per request and watch for spikes after deploys.
Which metrics matter most for Rails performance?
p95/p99 request latency, database time per request, queries per request, background job queue latency, error rate and memory (RSS) per process.