For me what approach I might take, may change depending on usage.
I will often start off with approach that many have suggested here...
I often start off simple like:
#ifndef DEBUG_BT
#undef DEBUG_BT_VERBOSE
void inline DBGPrintf(...) {};
void inline DBGFlush() {};
#else
#define DBGPrintf Serial.printf
#define DBGFlush Serial.flush
#endif
Sometimes I want to control, which stream the debug data is printed on,
so might have method to set the Print object: like my class that I am trying to
debug might have a method like: void setDebugPrint(Print *pr);
Why most of the time when I want the debug stuff, it goes to Serial, but sometimes I don't want to pollute Serial, it is used for something else, so on Teensy maybe I set up for dual serial, and output on SerialUSB1....
Sometimes I just setup to use the member varialble, like DBGSerial->print(...), or
more likely: if (DBGSerial) DBGSerial.print(....).
Sometimes I will wrap this if(...) into macros or inline functions.
Sometimes, I have found that Serial output to USB or Hardware Serial ports, during some operations, screw the timing enough that it acts differently, so instead I pass in some real simple implementation of Print class, that stores the data into memory, which I print out later.
Something like:
class MemoryPrint : public Print {
public:
size_t write (uint8_t b) { buffer[cb++] = b; return 1 }
uint8_t buffer[4096];
uint16_t cb = 0;
};
...
void setup() {
MemoryPrint mpr;
myobject.setDebugPrint(&mpr);
// Do initialization stuff...
Serial.begin(115200);
myObject.setDebugPrint(&Serial);
Serial.write(mpr.buffer, mpr.cb);
Obviously, this could be made more robust and the like. I have typically done something like this, when I am trying to debug a USB protocol such as MTP, that part of the initialization is done before the USB Serial object is initialized and as such can not be directly printed out to Serial at the time. Or that the printing screwed up the timing enough that behaviors changed.
The reason I mention all of this, is another version of this could be the bitbucket...
class BitBucketPrint : public Print {
public:
size_t write (uint8_t b) { return 1 }
};
...