📚 Connecting Debugger to Rails Applications
In a previous post about debugging silent create action failures, I touched upon the practice of connecting the Rails web process to a debugger when facing challenging issues. A debugger allows you to methodically step through your code and examine the runtime values for every stage of a process.
Setting up the debugger can be a bit of a hurdle though, especially if your app is being driven by a
Procfile. I wanted to make a dedicated post about the different ways you can get the debugger up and running in a Rails app. So let's get to it!
This post assumes that you're on a Rails 7 app, which comes out of the box with the ruby debug gem. With earlier versions of Rails, the ruby debug gem wasn't included by default, and it was common to use the
pry-byebug gem for debugging. So if that's the situation you're in, not everything I say in this post will work.
Without a Procfile
In the root of your Rails application, run
ls Procfile* in your terminal. If nothing shows up, your application's processes aren't managed by a
Procfile, and you likely just run
rails server to start your app.
If this is the case, then connecting a debugger to your server is fairly straightforward. Let's take a
#create action on a
Post resource as an example for illustration:
class PostsController < ApplicationController
# assuming `current_user` is an instance of the User model
@post = current_user.posts.build(post_params)
render :new, status: :unprocessable_entity
Let's say this post won't save and you want step through this code to analyze why. You can add a breakpoint to the beginning of the
# aliased as `binding.b` and `debugger`, you can use those too
# the actual code ...
Now in your browser with
localhost open, when you try to submit a form to create a new post, you'll see that the browser just hangs as if caught in an infinite loop. This is good thing! Your application is effectively paused at the breakpoint you set.
Execution is paused at the breakpoint (which has a little arrow pointing at it). You can then enter commands to the
rdbg prompt to control the debugger. For a list of the different commands you can use, visit the documentation for the debug gem.
With a Procfile
If you ran
ls Procfile* and one or more
Procfiles were listed, then your application is probably run through one. To start your app, you likely use a tool like
heroku local, or some
Procfile tends to be used when there are multiple processes that need to be running for your app to function. This can include things like building CSS/JS, background worker, search engine, etc. A
Procfile will specify the commands for each process that your app requires. Instead of opening new terminal windows for each process, tools like
heroku local can streamline your app's setup by using the
Procfile to run these processes from a single terminal instance. This is great for keeping your workspace clean and ensuring you have all the necessary processes running.
But this introduces some problems if you try to connect a debugger using the flow above. Aforementioned tools like
heroku local won't know that they should be listening to and forwarding stdin (your inputs on the terminal) to your web process. So although you can set breakpoints and pause execution in your app, you'll be unable to actually control the debugger.
There are a few ways connect a debugger when your app is managed by a
1. Split out the server process
You can isolate the Rails server process from the rest of the commands in the
Procfile. Navigate to your
Procfile (or potentially
Procfile.dev if you have a different one set to run locally), and remove the line that runs the web process. Now when you run the
Procfile, all your processes will start up except the server. Then you can open a new terminal and run the server in isolation with
bin/rails server. With the web process running separately, you'll be able to properly operate the debugger in the terminal that's running the server.
This is probably the most straightforward method, but it can be a bit inconvenient if you regularly have to do this. If it's your own project or one you control, you can remove the server process from your local
Procfile.dev and always run the server separately. If you're working with others, the team may not be okay with committing this kind of change to the
Procfile. So you'll have to manually set this up each time you want to debug and then remember to revert it back before committing any changes.
2. Use Overmind
Another solution is to use a different tool to drive the
Procfile. The one I'm most familiar with is a tool called overmind. If you run your
overmind, you'll be able to open up a new terminal window and individually connect to any of the processes that are running. So if you want to connect to the web process to debug, you can open up a new window and run
overmind connect web, and you'll have a window where you can work with the debugger's prompt.
The downside of
overmind is that it requires tmux, which is a terminal multiplexer tool. If you don't already use tmux, I'd say it's probably not worth learning it just for the purposes of using
overmind. But if you're like me and already know/use tmux, this can be a great solution to pursue.
3. Connect to debugger remotely
Procfile.dev that's currently generated by the cssbundling-rails and jsbundling-rails gems, we can see that they write the web process like:
web: env RUBY_DEBUG_OPEN=true bin/rails server
RUBY_DEBUG_OPEN=true bit can allow us to remotely connect a debugger to the server instance the
Procfile is running. When you run the
Procfile, you'll see something like this in the server logs:
web.1 | DEBUGGER: Debugger can attach via UNIX domain socket (/run/user/1000/ruby-debug-josh-1396004)
Except probably with a different username than "josh" 🤣
But like the message said, a debugger can attach remotely via UDS. Don't worry, it sounds more intimidating than it is. You just need to open a new terminal window and run
rdbg -An. The
-A option means "attach", and it automatically connects the debugger to the open UNIX domain socket that was setup. If you have multiple sockets open for a debugger to connect to (ie. because you're running multiple Rails projects), you may be forced to specify which one it should use. The
-n option means "nonstop." This prevents the program from stopping when you connect the debugger, which is usually what you're going to want when debugging a Rails app.
Now when you hit a breakpoint, you'll be able to use the debugger in the terminal window where you remotely connected with
rdbg -An. This is straightforward to use and the method I recommend for most people. If it's a project where the
Procfile.dev doesn't provide that setup and you can't add it in, you can prefix the command to start your app with
RUBY_DEBUG_OPEN=true to still enable the same effect.
Using the debugger to figure out tricky problems has saved me a ton of time when working on Rails apps. It's a great skill to learn and practice, but I also know that a lot of beginners avoid it because of how troublesome the setup can be. Hopefully this post has given you some ideas about the workflow you want to use for debugging 🐛...