-1

I would like to create a class for logging purposes which will behave like std::cout, but will automatically insert additional information to the stream.

a sample usage that I want would be something like (lets not care about object and context type for now, just assume they are std::string) :

Logger l;

l << "Event with object : " << obj << " while in context : " << context;

Then the output would be :

[timestamp] Event with object : [obj_desc] while in context : [this context][eol][flush]

I've been trying with :

template<typename T>
Logger& operator << (const T& msg){
    std::cout << timestamp() << msg << std::endl << std::flush;
    return *this;
}

but it seems that std::cout cannot resolve the typename T and fails to output a std::string for example while segfaulting.

A possible solution would be to overload this for all types, but this is rather annoying and time consuming.

Is there a better option to decorate std::cout output with more information?

Edit :

I do realize now that endl and flush will be appended to every message which kind of defeat the purpose, but I'm still interested in the general idea. I care more about the monadic syntax to append an arbitrary number of messages than the <<overload

Eric
  • 19,525
  • 19
  • 84
  • 147
  • 2
    `std::endl` already flushes the stream. – chris May 26 '15 at 04:56
  • 1
    see: [Overload handling of std::endl?](http://stackoverflow.com/q/2212776/14065) – Martin York May 26 '15 at 05:08
  • Please provide the whole (stripped down but complete) code to reproduce. Then, don't paraphrase the error but quote that, too. Odds are that you are using this on a temporary or constant `Logger` or trying to stream an overloaded function like `std::endl` into it, but without the actual code and error it's impossible. – Ulrich Eckhardt May 26 '15 at 05:17
  • Loki, this pretty much solves my problem, thanks! – Eric May 26 '15 at 06:10

2 Answers2

0

The reason your code does not work is because you have not implemented operator<< for everything you want to pass to it.

This statement:

Logger l;
l << "Event with object : " << obj << " while in context : " << context;

Is basically doing this (assuming operator<< is a member of Logger, which your implementation implies it is):

Logger l;
l.operator<<("Event with object : ").operator<<(obj).operaator<<(" while in context : ").operator<<(context);

So, you need separate overloads of operator<< for string, obj, context, etc. And you need a way to indicate when to flush the complete log message to std::cout.

I would suggest something more like this:

struct LoggerStream
{
    std::ostringstream strm;

    struct Timestamp
    {
    };

    ~LoggerStream()
    {
        std::string s = strm.str();
        if (!s.empty())
            std::cout << s << std::flush;
    }

    LoggerStream& operator<< (const Timestamp &t)
    {
        strm << "[timestamp] "; // format this however you need
        return *this;
    }

    LoggerStream& operator<< (const object &obj)
    {
        strm << "[obj_desc]"; // format this however you need
        return *this;
    }

    LoggerStream& operator<< (const context &ctx)
    {
        strm << "[this context]"; // format this however you need
        return *this;
    }

    LoggerStream& operator<< (std::ostream&(*f)(std::ostream&))
    {
        if (f == (std::basic_ostream<char>& (*)(std::basic_ostream<char>&)) &std::flush)
        {
            std::string s = strm.str();
            if (!s.empty())
                std::cout << s << std::flush;
            strm.str("");
            strm.clear();
        }
        else
            strm << f;

        return *this;
    }

    template<typename T>
    LoggerStream& operator<< (const T& value)
    {
        strm << value;
        return *this;
    }
};

class Logger
{
    LoggerStream getStream()
    {
        LoggerStream strm;
        strm << Timestamp;
        return strm;
    }
};

Then you can do things like this:

Logger l;
l.getStream() << "Event with object : " << obj << " while in context : " << context;
...
l.getStream() << "Event with object : " << obj << " while in context : " << context;
...

Logger l;
LoggerStream strm = l.getStream();
strm << "Event with object : " << obj << " while in context : " << context << std::flush;
...
strm << Logger::Timestamp << "Event with object : " << obj << " while in context : " << context << std::flush;
...

Alternatively:

struct Logger
{
    std::ostringstream strm;

    ~Logger()
    {
        std::string s = strm.str();
        if (!s.empty())
            std::cout << "[timestamp] " << s << std::flush;
    }

    Logger& operator<< (const object &obj)
    {
        strm << "[obj_desc]"; // format this however you need
        return *this;
    }

    Logger& operator<< (const context &ctx)
    {
        strm << "[this context]"; // format this however you need
        return *this;
    }

    Logger& operator<< (std::ostream&(*f)(std::ostream&))
    {
        if (f == (std::basic_ostream<char>& (*)(std::basic_ostream<char>&)) &std::flush)
        {
            std::string s = strm.str();
            if (!s.empty())
                std::cout << "[timestamp] " << s << std::flush;
            strm.str("");
            strm.clear();
        }
        else
            strm << f;

        return *this;
    }

    template<typename T>
    Logger& operator<< (const T& value)
    {
        strm << value;
        return *this;
    }
};

Logger() << "Event with object : " << obj << " while in context : " << context;
...
Logger() << "Event with object : " << obj << " while in context : " << context;
...

Logger l;
l << "Event with object : " << obj << " while in context : " << context << std::flush;
...
l << "Event with object : " << obj << " while in context : " << context << std::flush;
...
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
0

You can certainly overload the stream classes if you want, providing operator<< for all the data types you want to support (and that's probably the "correct" way to go) but, if all you're after is a quick way to add logging to a regular stream, there a simpler way:

#include <iostream>
#include <iomanip>
#include <sstream>
#include <ctime>
#include <unistd.h>

#define logcout std::cout << timestamp()

std::string timestamp(void) {
    time_t now = time(0);
    struct tm *tmx = localtime(&now);
    std::ostringstream oss;
    oss << '['
        << (tmx->tm_year+1900)
        << '-'
        << std::setfill('0') << std::setw(2) << (tmx->tm_mon+1)
        << '-'
        << std::setfill('0') << std::setw(2) << (tmx->tm_mday)
        << ' '
        << std::setfill('0') << std::setw(2) << (tmx->tm_hour)
        << ':'
        << std::setfill('0') << std::setw(2) << (tmx->tm_min)
        << ':'
        << std::setfill('0') << std::setw(2) << (tmx->tm_sec)
        << "] ";
    return oss.str();
}

int main (int argc, char *argv[]) {
    logcout << "A slightly\n";
    sleep (5);
    logcout << "sneaky" << " solution\n";
    return 0;
}

which outputs:

[2015-05-26 13:37:04] A slightly
[2015-05-26 13:37:09] sneaky solution

Don't be fooled by the size of the code, I just provided a complete compilable sample for testing. The crux of the matter is the single line:

#define logcout std::cout << timestamp()

where you can then use logcout instead of std::cout, and every occurrence prefixes the stream contents with an arbitrary string (the timestamp in this case, which accounts for the bulk of the code).

It's not what I would call the most pure C++ code but, if your needs are basically what you stated, it'll certainly do the trick.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953