newgre
newgre

Reputation: 5291

How to use my logging class like a std C++ stream?

I've a working logger class, which outputs some text into a richtextbox (Win32, C++). Problem is, i always end up using it like this:

stringstream ss;  
ss << someInt << someString;  
debugLogger.log(ss.str());

instead, it would be much more convenient to use it like a stream as in:

debugLogger << someInt << someString;

Is there a better way than forwarding everything to an internal stringstream instance? If'd do this, when would i need to flush?

Upvotes: 17

Views: 23024

Answers (7)

Jan Wilmans
Jan Wilmans

Reputation: 823

Here is an example of the using a stream class:

I must say I'm not a fan of this anymore and a more modern approach I use now is: https://cppcoach.godbolt.org/z/j6jxfY8nK

However, the goal is slightly different, so I posted both methods.

Upvotes: 0

Halfgaar
Halfgaar

Reputation: 772

Late to the party, but one can also extend the std::ostringstream to one that logs on destruct and then use it as a 'temporary' (don't give it a name, so it destructs right away):

class StreamToLog : public std::ostringstream
{
    int level = INFO;
public:
    StreamToLog(int level);
    ~StreamToLog();
};

StreamToLog::StreamToLog(int level) :
    level(level)
{

}

StreamToLog::~StreamToLog()
{
    const std::string s = str();

    // This 'cout' is simply for demo purposes. You can do anything else
    // like invoke a standard logger.
    std::cout << levelToString(level) << " " << s << std::endl;
}

And then you can implement some function somewhere to retrieve this stream:

StreamToLog Logger::log(int level)
{
    return StreamToLog(level);
}

And then log like:

    Logger::log(DEBUG) << "Hello, I'll have " << 3 " beers.";

Upvotes: 0

Marios V
Marios V

Reputation: 1174

An elegant solution that also solves the flushing issues is the following:

#include <string>
#include <memory>
#include <sstream>
#include <iostream>

class Logger
{
    using Stream = std::ostringstream;
    using Buffer_p = std::unique_ptr<Stream, std::function<void(Stream*)>>;

public:
    void log(const std::string& cmd) {
        std::cout << "INFO: " << cmd << std::endl;
    }

    Buffer_p log() {
        return Buffer_p(new Stream, [&](Stream* st) {
            log(st->str());
        });
    }
};

#define LOG(instance) *(instance.log())

int main()
{
    Logger logger;
    LOG(logger) << "e.g. Log a number: " << 3;
    return 0;
}

Upvotes: 3

PolarBear
PolarBear

Reputation: 1267

As Luc Hermitte noted, there is "Logging In C++" article which describes very neat approach to solve this problem. In a nutshell, given you have a function like the following:

void LogFunction(const std::string& str) {
    // write to socket, file, console, e.t.c
    std::cout << str << std::endl;
}

it is possible to write a wrapper to use it in std::cout like way:

#include <sstream>
#include <functional>

#define LOG(loggingFuntion) \
    Log(loggingFuntion).GetStream()

class Log {
    using LogFunctionType = std::function<void(const std::string&)>;

public:
    explicit Log(LogFunctionType logFunction) : m_logFunction(std::move(logFunction)) { }
    std::ostringstream& GetStream() { return m_stringStream; }
    ~Log() { m_logFunction(m_stringStream.str()); }

private:
    std::ostringstream m_stringStream;
    LogFunctionType m_logFunction;
};

int main() {
    LOG(LogFunction) << "some string " << 5 << " smth";
}

(online demo)

Also, there is very nice solution provided by Stewart.

Upvotes: 5

Luc Hermitte
Luc Hermitte

Reputation: 32966

Overloading the insertion operator<< is not the way to go. You will have to add overloads for all the endl or any other user defined functions.

The way to go is to define your own streambuf, and to bind it into a stream. Then, you just have to use the stream.

Here are a few simple examples:

Upvotes: 17

Vinay
Vinay

Reputation: 4793

In the Logger class, override the << operator.

Click Here to know how to implement the << operator.

You can also avoid the logging statements inside the code using Aspect Oriented programming.

Upvotes: -1

Konrad Rudolph
Konrad Rudolph

Reputation: 546043

You need to implement operator << appropriately for your class. The general pattern looks like this:

template <typename T>
logger& operator <<(logger& log, T const& value) {
    log.your_stringstream << value;
    return log;
}

Notice that this deals with (non-const) references since the operation modifies your logger. Also notice that you need to return the log parameter in order for chaining to work:

log << 1 << 2 << endl;
// is the same as:
((log << 1) << 2) << endl;

If the innermost operation didn't return the current log instance, all other operations would either fail at compile-time (wrong method signature) or would be swallowed at run-time.

Upvotes: 36

Related Questions