c++loggingflusheolendl

How do loggers flush without adding an end of line to the log messages?


I have seen many loggers using operator<< to read log messages and write them on file:

template<typename T>
Logger& Logger::operator<<(Logger& l, const T& s) {
    l.logStream << s;
    return l;
}

...

Logger log;
log << "Test message " << 123;

I want the log to be flushed after "Test message" and 123 are written to the stream. However, rather than the user being in control of the flushing, I want the logger to flush the stream when it reaches the ;.

How can I do that?

Two solutions came to my mind, but I find both not acceptable:

I could put the new line at the beginning of each log message, so that the previous log is flushed, and in case of a crash all we loose is the current log.

operator<< could flush. But flushing on each invocation of << incurs a big performance penalty due to the number of write operations on disk (wearing). I want the flush to involve the whole message.


Solution

  • I would like "TestMessage" << 123 to be flushed all together, in a single operation.

    You can do that by returning a proxy object from log::operator<< that has its own proxy::operator<< which forwards to log::operator<<, and flushes in its destructor.

    Something along the line of

    #include <iostream>
    #include <iomanip>
    
    struct Logger {
        std::ostream& out = std::cout;
    };
    
    struct ProxyLogger {
          Logger& l;
          explicit ProxyLogger(Logger& l) : l(l) {}
          
          template <typename T>
          ProxyLogger& operator<<(const T& s) {
               l.out << s; 
               return *this;
          }
          ~ProxyLogger();
    };
    
    template<typename T>
    ProxyLogger operator<<(Logger& l, const T& s) {
        l.out << s;
        return ProxyLogger(l);
    }
    
    ProxyLogger::~ProxyLogger() { l.out << "hello proxy" << std::flush; }
    
    int main() {
        Logger log;
        log << "test" << 123;
    }
    

    output:

    test123hello proxy
    

    The proxy adds hello proxy to the message in its destructor for illustration.

    The code you posted is somewhat bogus. Your operator<< is a member function but has an additional Logger& parameter. Thats not how you are going to get log << "test"; to work. Make it either a friend with the explicit argument or a member without.

    Moreoever, a templated operator<< looks rather cool at first, but as soon as you want to handle iomanipulators, as eg std::flush, things get hairy, because most of them are function templates. I avoided passing the iomanipulator through Logger::operator<< by letting the proxy pipe to the stream directly. You may want to make the Loggers stream private and the proxy a friend. Making io manipulators actually work with this approach I consider as beyond the scope of this question.

    For further reading I refer you to articles about RAII (resource acquisition is initialization).