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

89 Views Asked by At

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.

2

There are 2 best solutions below

7
463035818_is_not_an_ai On BEST ANSWER

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).

0
Pietro On

This is a slight modification of @463035818_is_not_an_ai answer, where I add a preprocessor flag to switch between the classic and proxy version of his code, with an abort call after the log, and show that only in the second case the log message is saved. De-comment line #define PROXY to switch:

#include <iostream>
#include <iomanip>

//#define PROXY

struct Logger {
    std::ostream& out = std::cout;

#ifndef PROXY
    template<typename T>
    Logger operator<<(const T& s) {
        out << s;
        return *this;
    }
#endif
};

#ifdef PROXY

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 << std::endl; }

#endif // PROXY

int main() {
    Logger log;
    log << "test 1 " << 123;
    log << "test 2 " << 456;
    abort();
}