Search code examples
c++cross-platformiostream

Redirect debug output to null stream instead of std::cerr


A software library that I am working with writes a lot of debug output to std::cerr, but redirects that output to a null stream if I tell it to be quiet. This is a simplified main.cpp that shows how the code tries to achieve this:

#include <iostream>
#include <fstream>
#include <cassert>

// The stream that debug output is sent to. By default
// this points to std::cerr.
std::ostream* debugStream(&std::cerr);

// Throughout the library's codebase this function is called
// to get the stream that debug output should be sent to.
std::ostream& DebugStream()
{
    return *debugStream;
}

// Null stream. This file stream will never be opened and acts
// as a null stream for DebugStream().
std::ofstream nullStream;

// Redirects debug output to the null stream
void BeQuiet()
{
    debugStream = &nullStream;
}

int main(int argc, char** argv)
{
  DebugStream() << "foo" << std::endl;
  BeQuiet();
  DebugStream() << "bar" << std::endl;
  assert(debugStream->good());

  return 0;
}

When you run this program, you will notice that the string "bar" is correctly sent to the null stream. However, I noticed that the assertion fails. Is this something that I should be concerned about? Or is this just a slightly ugly detail of the approach chosen by the library developers?

If you feel like it, suggestions for better alternatives are welcome. Some constraints:

  • The library is cross-platform, so I think using opening /dev/null is not a valid solution as it would not work on Windows
  • The library uses standard C++, so any alternative solutions should not use compiler-specific stuff

Solution

  • There is no real need to be worried about the stream not being good()! Since the output operators don't really do anything wirh a stream in failure mode the different entities being logged are not formatted, i.e., the code does run faster compared to alternative approaches.

    Note that you don't really need a second stream to disable output:

    1. Assuming all output operators are well-behaved, you can just set std::ios_base::failbit:

      debugStream().setstate(std::ios_base::failbit);
      
    2. If there are misbehaved output which write to a stream even if it isn't good() you can just set its stream buffer to null:

      debugStream().rdbuf(nullptr);

    If you really want your stream to remain in good() state, you'd install a stream buffer which just consumes characters. Note, however, that you want to give this stream buffer a buffer as having overflow() called for each char is fairly expensive. Also, overriding xsputn() is a benefit:

    struct nullbuf
        : std::streambuf {
        char buf[256];
        int overflow(int c) {
            this->setp(this->buf, this->buf + 256);
            return std::char_traits<char>::not_eof(c);
        }
        std::streamsize xsputn(char const* , std::streamsize n) override {
            return n;
         }
    };
    ...
    nullbuf sbuf;
    debugStream().rdbuf(&sbuf);
    ...
    debugStream().rdbuf(0);
    

    It is necessary to reset the stream's stream buffer because the destructor of an std::ostream will flush the stresm buffer (i.e., it calls pubsync()). Doing so on a destroyed stream buffer won't work.

    Personally, I would go with setting std::ios_base::failbit.