When your code does not behave as expected, debugging tools help you find the exact line where the actual behavior diverges from what you expected. This lesson covers breakpoints and logs in Ruby and JavaScript, the browser's network and DOM inspectors, and how to get unstuck when a bug resists you.
Important
Work on this lesson in
teachermode.
A debugger statement ("breakpoint") opens a REPL Show archive.org snapshot ("console") at that line, where you can inspect the program's state and move the control flow ahead manually. Since we use open source for everything, we can always find that line. When you ask a colleague for help, they will be able to help you better when you have already located the issue.
Learning goals
- You can explain your debugging strategy: find the exact line where the actual behavior diverges from what you expected, and narrow down toward it instead of guessing.
- You can get unstuck on your own, and ask a good question when you can't โ describing the actual problem rather than your attempted solution.
- You can set a breakpoint in Ruby code, inspect the program's state, step through the code and resume, using the debugging gem the project uses (e.g.
debug,byebugorpry). - You can debug code inside a gem by reading and temporarily changing its source.
- You can use logs to locate a problem, e.g. by following the Rails log while you reproduce it, and add log output of your own.
- You can set a breakpoint in JavaScript and inspect state and step through code in the browser's developer tools.
- You can observe requests and responses in the browser's network tab, and inspect the live DOM.
- You can debug a failing end-to-end test by pausing it and inspecting the page and the server log.
- You review your own changes before committing, so debugging statements never end up in a commit.
Resources
Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.
Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.
Getting unstuck
- ๐ Hilfe, ich hรคnge beim Programmieren fest! โ our card, German
- ๐ The XY Problem Show archive.org snapshot โ ask about the actual problem, not the workaround you're stuck on
- โถ๏ธ Getting Unstuck Show archive.org snapshot โ Joel Clermont, Laracon 2023
- ๐ Rubber ducking: not just a funny phrase
Debugging tools
- ๐ Rails Guide:
Debugging Rails Applications
Show archive.org snapshot
โ the logger, the
debuggem,web-console, and debugging memory issues; the reference for the Ruby section below - ๐
ruby/debug
Show archive.org snapshot
โ README of Ruby's built-in debugger: breakpoints, stepping,
binding.break - ๐ Debug Ruby code โ our card
- ๐ฎ Chrome DevTools: Debug JavaScript Show archive.org snapshot โ the walkthrough for the JavaScript section below
- ๐ Chrome DevTools treasure overview โ our card
- ๐ A Guide To CSS Debugging Show archive.org snapshot โ when the bug is in your stylesheet
Debugging in Ruby
Note
Running your server as part of a single combined terminal with the
bin/devcommand makes Ruby debugging harder. To work around this, you should start your web server in a separate terminal as described above. If you are using terminator, bin/term is the easiest way to to achieve this.
- Rails apps come with Ruby's
debuggem in theGemfile. This provides debugging for your app. - At any line, insert the command
debugger(orbinding.break). - When you start your app and the Ruby interpreter passes this line, this will open a debugging console. Do this.
- Note that when your app is a web app, the console running your
rails serverwill pause the server and show the debugging console. Your browser will "hang" while that debugging console is open. - Note that the debugger console is not a full
irbconsole, even though it somewhat behaves that way.
- Note that when your app is a web app, the console running your
- Use the
debug README
Show archive.org snapshot
, the debugger's
helpcommand and our Debug Ruby code card to learn to do the following in the debugging console:- Understand your current position in the program
- Read local variables
- Call methods that are reachable from the current scope
- Switch into a full
irbconsole and back to the debugger - Move the program position one statement ahead
- Jump into the next method call
- Jump out of the current method call
- Exit debugging console and resume program
- Use a simple
raisestatement e.g. in controller code and open the corresponding page with your browser- better_errors Show archive.org snapshot provides you with a nice REPL for this specific use case
- If you need to debug code that is part of a gem like
rails, just (temporarily) add aputsordebuggerstatement to its source files. Ruby is an interpreted language after all!- Using Ruby's class Method for inspecting and debugging methods can be quite handy here too, since the interpretation might even change the source code or the defined location of the method.
- You should be in a habit of reviewing your code before committing. Never commit a debugging breakpoint.
- You can achieve this by using
git add -pand following the guidelines Show archive.org snapshot from the referred card above.
- You can achieve this by using
- If you are debugging
rspectests, you can userspec -bto print the full stack trace. - If you encounter problems caused by ActiveRecord and its database mapping, you can run ActiveRecord's query methods in the rails console and it will log the exact database interaction with related sql commands and its result.
- Before you know where to debug, you will often find yourself staring at logs beforehand - they are your friend at this mission!
- Use
Rails.logger.info()to add your own custom messages tolog/<environment>.log - Use
tail -f log/development.logfor a live view of the local Rails server's log - ..or
tail -f log/test.logto see the same for RSpec tests!
- Use
Different debugging libraries
Ruby has always had multiple gems for debugging. When you're working on a customer project you need to check the Gemfile to see which one is used.
All Ruby debugging gems have the same basic functionality:
- Add a breakpoint
- Get an IRB-like console when the breakpoint is reached
A debugging gem will also offer commands to step through your program, but their names differ from gem to gem.
Each gem also uses a different expression to set a breakpoint:
| Gem | Breakpoint |
|---|---|
| pry Show archive.org snapshot | binding.pry |
| byebug Show archive.org snapshot | byebug |
| debug Show archive.org snapshot | binding.break |
Debugging in JavaScript
- At any line, insert the command
debugger - When a modern browser's JavaScript interpreter passes this line, this will open a debugging console if developer tools are already open. Do this.
- Note that since JavaScript is single-threaded, you cannot interact with your app's frontend while the debugging console is open.
- Use the
Google DevTools debugging guide
Show archive.org snapshot
to learn to do the following in the browser's debugging console:
- Understand your current position in the program
- Read local variables or
this. Observe how Chrome will automatically show the state of local variables in the Sources tab. - Call functions that are reachable from the current scope
- Move the program position one statement ahead
- Jump into the next method call
- Jump out of the current method call
- Exit debugging console and resume program
- Skim over DevTools card to get a rough overview about its many capabilities
- You should be in a habit of reviewing your code before committing (e.g. by using
git add -p). Never commit a debugging breakpoint.
Debugging network traffic
- Your browser DevTools have a Network tab. You can use it to observe outgoing network requests and their responses.
- You can also use the
curlcommand in your terminal to print the response of a request.
Debugging DOM changes
- In your browser you can right-click any element and choose Inspect.
- This will open a tree displaying the current state of the DOM. The tree is live, so it will be updated automatically when your DOM changes.
- Do this on the large hero photo at https://makandra.com/.
- Can you find the element for the photo?
- Observe how the DOM changes as the photos are rotated every few seconds.
Exercises
1. Break into MovieDB
- Put a
debuggerstatement into a controller action of your MovieDB and open the page in your browser. - In the debugging console: inspect local variables and
params, step over and into a few statements, switch into an irb console and back, then resume. - Replace the breakpoint with a plain
raise "boom"and explore the error page you get in the browser.
2. Debug inside a gem
- Pick a Rails method your controller calls (e.g.
redirect_to) and open its installed source (bundle open, or ask your agent for thesource_location). - Temporarily add a
putsordebuggerthere, trigger it, watch it fire โ then revert your change. Ruby is an interpreted language: the gem source on your disk is the code that runs.
3. Step through your movie counter
- Put a
debuggerstatement into the movie-counter component you wrote in Working with the DOM and open the page with DevTools open. - Step through the handler, read the local variables and
thisin the Sources panel, then resume.
4. Watch the wire
- Open the Network tab (check Preserve log) on an app like cards.
- Switch between a few cards. What requests and responses do you see?
- Update a card and find your form data ("params") in the request entry.
- Throttle the network in DevTools to simulate a slow connection and reload.
- Repeat one of the requests with
curlin your terminal and compare the response.
5. Debug a failing feature spec
- Break an expectation in one of your MovieDB feature specs so it fails.
- Now work the toolbox: run it with
rspec -bfor the full backtrace; put adebuggerinto the spec right before the failure and inspectpagein the console; re-run withNO_HEADLESS=1and watch the browser do its thing; followlog/test.login a second terminal while the spec runs. - Fix the expectation again.
6. Leave no trace
Before you commit anything from this card: check git diff and remove every debugger, puts and raise you planted. Debugging statements never belong in a commit.