Connecting Debugger to Rails Applications

joshdevhub

Josh Smith

Posted on December 19, 2023

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!

Note
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
  def create
    # assuming `current_user` is an instance of the User model
    @post = current_user.posts.build(post_params)

    if @post.save
      redirect_to root_path
    else
      render :new, status: :unprocessable_entity
    end
  end
end


Enter fullscreen mode Exit fullscreen mode

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 #create action:



def create
  # aliased as `binding.b` and `debugger`, you can use those too
  binding.break

  # the actual code ...
end


Enter fullscreen mode Exit fullscreen mode

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.

If you look in the terminal window that's running the server process, you'll see something like the image below:
terminal window with ruby debug prompt

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 foreman, heroku local, or some bin/dev script.

A 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 foreman and 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 foreman and 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.

Solutions

There are a few ways connect a debugger when your app is managed by a Procfile:

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 Procfile with 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

In the 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


Enter fullscreen mode Exit fullscreen mode

This 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)


Enter fullscreen mode Exit fullscreen mode

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.

Conclusion

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 🐛

💖 💪 🙅 🚩
joshdevhub
Josh Smith

Posted on December 19, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related