The key thing about print debugging for me, that I've never seen a typical debugger handle well, is what I like to think of as "temporal debugging":
Taking a program trace from the print debug statements, grepping through it repeatedly to filter down to certain events of interest, looking at the interleaving of those events, and figuring out the order that things happened in to cause it to go off the rails. That sort of thing. (To be fair, time-travel debuggers can start to get at this, but those are pretty uncommon. Traces, as you say, work almost everywhere.)
Or better yet, compare the traces between working and non-working runs to see how they differ. I've looked at diffs of traces this way before. (Sometimes I'll first use a small script to renumber pointers in traces by order of appearance.)
I've also used this sort of strategy before for debugging rare threading or other non-deterministic issues. Have the shell run the program in a loop, saving each run's trace and results to a different file, go off and get lunch, come back and see if anything failed. Then look to see if any of the runs failed and look for the structural differences between the working and non-working traces.
I can't imagine sitting and stepping through in a debugger 100+ times in the hopes that maybe this time, it will be the run that's just different enough to trigger the bug and that the debugger itself won't prevent the issue from manifesting. Not to mention, trying to remember the steps from all the good runs and spotting where the bad run goes bad before you've stepped to far. No thank you.
I think people really underestimate print debugging. Debuggers are fast and easy for simple bugs, sure, but there's powerful stuff that you can only really do with printed traces.