Let’s talk about what happens when your application is live and something goes wrong. You get an alert, a user complains, or you see a strange spike in your graphs. The standard logs show an error, but they don’t tell you why. They don’t show you the slow query hiding inside a loop, or the object that’s slowly consuming all your memory. Over the years, I’ve leaned on a set of tools that let me see inside the running application. They turn a frustrating hunt into a clear, step-by-step investigation.
The first tool I reach for isn’t just for crashes; it’s for understanding. Pry is an interactive console that lets you pause your code, look around, and even change things on the fly. Imagine your code is a play. Standard logs are like reading the script afterwards. With Pry, you can stop the play, walk on stage, ask the actors what they’re thinking, and give them new lines to say, all while the show is still running.
You can drop into this console anywhere by adding a single line. I often use it with a condition, so it only stops when something specific happens, like processing a particularly large order.
def process_payment(order)
# Stop here only if the order is suspiciously large
binding.pry if order.amount > 5000
# Your normal payment logic...
charge_credit_card(order)
end
When that line runs, your terminal takes over. You’re in a live shell, right in the middle of your method. You can type order to inspect it. You can type ls to see all the variables and methods available. You can even run order.update(status: 'flagged') to change data, or reload! to refresh your code without restarting the server. It’s like having a pause and rewind button for your program’s state. I once spent an hour looking at log files for a nil value before I used Pry, stopped the code, and typed user.addresses. I saw it was empty, not nil, and fixed the bug in two minutes.
Seeing your code run is one thing; seeing how slowly it runs is another. For that, I use Rack Mini Profiler. This gem adds a tiny box to the corner of your browser window when you’re developing. It breaks down exactly how long each part of your page took: database time, Ruby time, view rendering time.
The real power comes from configuring it for safe use in a production or staging environment. You don’t want every user seeing it. You can set it to only show for admins.
# In an initializer, like config/initializers/mini_profiler.rb
if Rails.env.staging?
Rack::MiniProfiler.config.authorization_mode = :allow_authorized
Rack::MiniProfiler.config.storage = :redis
# Define who is authorized (e.g., admins)
Rack::MiniProfiler.config.pre_authorize_cb = lambda do |env|
req = Rack::Request.new(env)
req.session['user_is_admin'] == true
end
end
Now, when I’m logged in as an admin on our staging site, I can click around. I’ll see that the admin reports page takes 1200ms, and the box shows 1100ms of that is in a single, huge database query. It turns a vague “this page is slow” into a precise “this specific query on this specific page is the problem.” I’ve used it to spot N+1 query problems instantly—where you fetch a list of posts and then make a separate query for each post’s author. The profiler shows 100+ tiny queries where there should be one.
When a process is just eating CPU and I don’t know where, I need a different view. StackProf gives me that by taking snapshots of what the program is doing, hundreds of times per second. It’s like a high-speed camera for your code’s activity. It doesn’t slow things down much, so you can even run it in production for a short while to capture a real problem.
You run it around a suspect piece of code, and it generates a report.
# Profile a slow report generation task
report = StackProf.run(mode: :wall, raw: true) do
generate_monthly_financial_report
end
# Save the report to analyze
File.open('tmp/report_profile.dump', 'w') do |f|
f.write(Marshal.dump(report))
end
Later, you can read that report. The output shows you which methods were on the CPU most often. The real magic is turning it into a flamegraph—a visual chart where wide boxes represent methods that took up a lot of time. I remember profiling a background job that was taking minutes. The flamegraph showed a massive, wide box for a method called sanitize_input. It wasn’t the business logic; it was a overly complex string cleaning function running in a loop millions of times. We cached it, and the job finished in seconds.
Sometimes the problem isn’t speed, but memory. The application gets slower and slower until it crashes. MemoryProfiler helps you see what objects your code is creating and, more importantly, which ones it’s holding onto. A memory leak is when you keep adding items to an array and never remove them, or when you accidentally create global variables that reference large objects.
You can wrap a section of code to see what it allocates.
require 'memory_profiler'
report = MemoryProfiler.report do
# This might be leaking
import_large_csv_file('data.csv')
end
report.pretty_print(scale_bytes: true)
The report will list the lines of code that created the most objects and used the most memory. I once tracked a leak to a logging module. It was storing every log message in an array “in case we needed them later.” The array was never cleared. The report pointed straight to the line where the array was appended. We changed it to a bounded buffer, and the leak stopped.
For a view that feels like a proper debugging dashboard, I use the RailsPanel browser extension with the meta_request gem. Once installed, it adds a “Rails” tab to your Chrome Developer Tools. When you load a page, this tab fills with a timeline: when the controller started, each database query that ran (with its time and the actual SQL), when the view rendered.
It’s incredibly intuitive because it works right beside your network requests and console. You can see that clicking a button triggered 45 SQL queries. You can click a query to see the full, formatted SQL statement. I find this is the fastest way to explain performance issues to teammates. Instead of describing a query, I can show them a screenshot with the exact, slow SQL highlighted.
When things go truly wrong and an exception is raised, you need to know about it immediately, with all the context. ExceptionNotification is a gem that catches those errors and sends them to you via email, Slack, or other services. It’s more than just the error message; you can configure it to send the user’s ID, the request parameters, the session data, and even a snippet of the relevant code.
This context is everything. A report that says “NoMethodError: undefined method ‘name’ for nil:NilClass” is useless. A report that includes that the error happened for user_id: 12345 on the /invoice page with order_id: nil in the parameters tells the whole story. You know exactly how to reproduce it.
# Send rich error reports to Slack
ExceptionNotifier.configure do |config|
config.add_notifier :slack,
webhook_url: ENV['SLACK_WEBHOOK_URL'],
channel: '#exceptions',
username: 'ErrorBot',
additional_fields: [
{ title: 'Project', value: 'Billing API', short: true },
{ title: 'Environment', value: Rails.env, short: true }
]
end
Finally, for the deepest level of inspection, there’s TracePoint. This is a built-in Ruby feature, not a gem, but it’s a powerful tool in your debugging toolbox. It lets you run code whenever specific events happen in Ruby, like when a method is called, when an object is created, or when a line is executed.
It’s low-level and powerful. You can use it to build your own custom profilers or tracers. For example, you could trace every time a specific expensive method is called.
trace = TracePoint.new(:call) do |tp|
# tp is the TracePoint object with details
if tp.method_id == :expensive_calculation
puts "Expensive calculation called from: #{tp.path}:#{tp.lineno}"
puts " Time: #{Time.now}"
end
end
trace.enable # Start tracing
# Your application code runs here...
run_my_app
trace.disable # Stop tracing
I used this once to find where a deprecated method was still being called from. We thought we had removed all uses, but the logs showed it was still running. A simple TracePoint script listened for calls to that method and printed the full stack trace, leading us straight to an old gem we had forgotten about.
Each of these tools gives you a different lens. Pry is your interactive microscope. Rack Mini Profiler is your stopwatch. StackProf is your heat sensor. MemoryProfiler is your inventory list. RailsPanel is your flight recorder. ExceptionNotification is your alarm system. TracePoint is your raw materials to build a custom sensor.
The goal is never to guess. When a production issue appears, you should have a clear path to follow. Start with the exception notification to see what broke. Use the profiler or panel to see where it’s slow. If it’s a leak, use the memory tool. If the stack trace makes no sense, use Pry to pause and look around. With these tools, you’re not just fixing bugs; you’re understanding your system. You stop fearing production deployments because you know you have the means to diagnose and solve whatever happens next.