Oh and not to be exclusive -- there's always both.
This's been alluded to, in earlier replies I think, but yet another example is adding check code in your program, which normally falls through so has very little effect on program execution (even only a small cost to execution speed), but within which the code can set a breakpoint so not only can you "printf()" the condition but you can sniff around with the debugger at the same time. Maybe a combination of approaches might be used.
Example, say you have a nested loop that's iterating over data, and in the output you can clearly see it's made an erroneous translation or whatever, but you can't really be quite sure where/when the error occurred. So you log output from earlier and earlier steps in the computation, add asserts, range checks, etc., anything to help figure out where the error occurred. You start with a big heap of data, you might produce even more data as you're collecting additional evidence at first; gradually, you close in on the problem. Then you are able to reduce the pile of evidence to the smoking gun, one single statement, or index or pass or whatever, which was meant to perform one operation but did something else (overflow or rounding, say); or, say a function got sent erroneous data, or ran out of data, and sent back an -- entirely reasonable return value, but one you merely didn't anticipate encountering in the process, so you forgot to handle that edge case. All sorts of things.
Well, you might approach such a problem by short-circuiting part of the process, tracing it up until you find the first inconsistency. You're momentarily making the output worse, less correct, outputting intermediate steps -- but as long as you know what the data are supposed to be, you can find the discrepancy. This might be by printf() logging, might be dumping to an array, might be stepping a debugger -- maybe all the above.
Case in point, while I don't use debuggers for embedded -- mainly because I don't have any for my current platform -- I try to make good use of them elsewhere, like, I'm fond of using JS for side tools -- calculators, image processing, code generation, that sort of thing. And often I make use of all the above. Values can be logged to console; arrays can be queried with various sorting, regex, etc. functions; objects can be passed through partial steps by copy-pasting code into the interpreter (immediate) window, etc. And object views are all handy, see all the properties, arrays, etc. of variables in current scope. Best of all, you literally don't have to go anywhere to do this: JS is all right there in your browser, just hit F12, it's literally right here right now!
Tim