Some examples of some abilities that an IDE debugger will give you over trace messages in code:
- View the call stack at any point in time, giving you a context for your current stack frame.
- Step into libraries that you are not able to re-compile for the purposes of adding traces (assuming you have access to the debug symbols)
- Change variable values while the program is running
- Edit and continue - the ability to change code while it is running and immediately see the results of the change
- Be able to watch variables, seeing when they change
- Be able to skip or repeat sections of code, to see how the code will perform. This allows you to test out theoretical changes before making them.
- Examine memory contents in real-time
- Alert you when certain exceptions are thrown, even if they are handled by the application.
- Conditional breakpointing; stopping the application only in exceptional circumstances to allow you to analyse the stack and variables.
- View the thread context in multi-threaded applications, which can be difficult to achieve with tracing (as the traces from different threads will be interleaved in the output).
In summary, print statements are (generally) static and you'll need to re-compile to get additional information if your original statements weren't detailed enough. The IDE removes this static barrier, giving you a dynamic toolkit at your fingertips.
When I first started coding, I couldn't understand what the big deal with debuggers was and I thought I could achieve anything with tracing (granted, that was on unix and the debugger was GDB). But once you learn how to properly use a graphical debugger, you don't want to go back to print statements.