I find analyzing trace statements to be far faster than setting conditional break points and stepping through a debugger.
I can quickly run a test, grep/skim through the trace output and ignore/drill down into detailed minutia that are irrelevant/relevant to the problem being investigated.
I also tend to write my trace statements in an easily parsible format so if I need to I can write another program to analyze what happened and find the problem. Writing a full on program to process log files happens less often than chaining several unix commands to find the needle in the haystack (usually the program happens when I need to thread together several widely separated trace lines).
This is situational in the extreme, especially when you're interested in specific conditions in tight loops. For example, if you're debugging a race condition in a render loop, it's probably not going to be easy to reproduce when you also start trying to write out megabytes of data at the same time.
The only way to debug real race conditions is to engineer your code from the beginning to avoid them. Debuggers and prints are equally powerless to help you.
Debuggers can help identify race conditions by giving you insight into the state of the process at the time of the break. The reason they're superior to printf's in this regard is that debuggers can signal breaks on conditions without requiring a recompile. In general, debuggers are superior because of this. Good debuggers aso let you ask questions on the fly inside a stack frame, something printf-stuff can never do.
Of course, I didn't say nor mean to say that ALL race conditions are identifiable in this way, only that it can make the results more visible for some of the more obvious cases.
Well, yeah, that's what useful stack traces are for - but stack traces aren't useful in the least when you click on a button and it's supposed to do x but it's doing y; how do you figure out why it isn't doing something it's supposed to be doing without either putting a bunch of log.debug() calls in the code or breakpoints?
With Python, when stack traces happen now, WebError will even give you an in-browser prompt to explore the state of that Python session; so log.debug() calls are only used when the software is working but not working as intended - that is a case where debugging tools can actually come in handy because I hate cleaning up log.debug() calls littered everywhere.
It really depends on the program architecture and what issue you're debugging.
For most single-threaded or simple multithreaded programs, it's easier just to throw a print statement where you need it and analyze that. Even with a large data set, a simple grep will probably get you what you need.
When you're debugging a complex, multithreaded program, on the other hand, an external debugger is much more valuable, because you can break at the exact point where things go wrong, and examine the entire program's behavior at that point. Debugging a race condition with prints can be pretty difficult, especially when the printing changes the timings of the threads.
I've debugged my fair shared of parallel programs (multithreaded and distributed), and I've used a mix of both techniques. The advantage to print statements is that it gives you an execution trace. It's easier for me to reconstruct the sequence of events - which is not trivial in a parallel program. What I give up is full knowledge of any one instant. And that's what a debugger gives me.
It's a trade-off, but I usually start with traces.
I've been debugging plenty of distributed/parallel processing systems by trace statements. It's not trivially easy, but its not too bad (its actually kind of fun).
In fact, I'm not exactly sure how you would debug a multi-process parallel system with an external debugger. Would you simultaneously use multiple debugger instances to attach to each process? That sounds like fun.
For a true, parallel system it's virtually impossible to "examine the entire program's behavior" at any one instance in time. Sure printing changes the timing of things, but unless you are single threaded, you are kidding yourself to think that a debugger also doesn't disrupt timings.
I can quickly run a test, grep/skim through the trace output and ignore/drill down into detailed minutia that are irrelevant/relevant to the problem being investigated.
I also tend to write my trace statements in an easily parsible format so if I need to I can write another program to analyze what happened and find the problem. Writing a full on program to process log files happens less often than chaining several unix commands to find the needle in the haystack (usually the program happens when I need to thread together several widely separated trace lines).