+1 for logging.
Flash memory for the log strings is probably the only cost.
You can, if you wish use a MACRO instead of a function, say:
DEBUG_LOG("Trying to Init UART2");
Which has a build time conditional flag to turn it on, or off. When it's off the code for the logging isn't even compiled.
Log is W5. Who, What, Where, When, Why?
Always put yourself into the position of some poor bugger tasked with fixing your code late at night, hunched over a laptop on a data centre floor. What would they like to see in the logs?
Using a debugger often helps you too much and makes you leave out useful logging in tricky error prone sections. If you had to have debugged it without one, you probably would have added debug diagnostics in them which would have helped others later if it had more bugs.
(I got angry a few weeks back because I had a framework failing with an un caught error like, "File is a directory" without telling me which one. I looked at the code and got very angry. The code was a mess, over complicated non-sense, it was also littered with about 60% commented out debug logging code. Commented out. It was very clear that, that section of code was error prone and fragile, so why the hell did they comment out all the logging and not add any error handling at all? Argh!
Of course the downside on MCUs can be that the UART doing the debug logging is using up too much demand for "real time" in it's event handlers. If you are trying to process timing critical streams/buffers in your main code, the debug UART code needs to be written very carefully. No Serial.println("...."); and blocking calls.
In UART coms I have found the debugger is only helpful on the first fire. As soon as you finish with that ITRS debug, the state will be corrupted the buffers overrun etc. etc.