3

I would like to write a convinient interface to my very simple logging library. Take two following pieces of code. The first one is what I do now, the second one is my idea for an intuitive interface:

std::ostringstream stream;
stream<<"Some text "<<and_variables<<" formated using standard string stream"
logger.log(stream.str()); //then passed to the logger

And

logger.convinient_log()<<"Same text "<<with_variables<<" but passed directly";

My thought-design process behind that idea is to return some kind of temporary stringstream-like object from logger.convinient_log() function. That object on destruction (I hope it happens at the end of the line or in a similar, convinient place) would collect string from itself and call an actual logger.log(). The point is I want to process it whole, not term-by-term, so that log() can add eg. prefix and sufix to whole line of text.

I'm very well avare that it might be straight impossible or impossible without some heavy magic. If that's the case, what would be an almost-as-convinient way to do that and how to implement it? I bet on passing some special variable that would force collect-call-logger.log() operation.

If you don't know an exact answer, resources on the topic (eg. extending stringstream) would be also welcome.

psorek
  • 65
  • 1
  • 7
  • 1
    Possible duplicate of [How to use my logging class like a std C++ stream?](http://stackoverflow.com/questions/511768/how-to-use-my-logging-class-like-a-std-c-stream) – user4581301 Oct 26 '16 at 23:38
  • Almost a duplicate, I want something different: I want to process whole input of one `convinient_log()` at once, not piece-by-piece. If you say "good luck", do you have any better (in terms of implementation and similar convinience of interface) solutions? I would really appreciate those too :) – psorek Oct 26 '16 at 23:43

3 Answers3

6

This is how Boost.Log works, for example. The basic idea is simple:

struct log
{
    log() {
        uncaught = std::uncaught_exceptions();
    }

    ~log() {
        if (uncaught >= std::uncaught_exceptions()) {
            std::cout << "prefix: " << stream.str() << " suffix\n";
        }
    }

    std::stringstream stream;
    int uncaught;
};

template <typename T>
log& operator<<(log& record, T&& t) {
    record.stream << std::forward<T>(t);
    return record;
}

template <typename T>
log& operator<<(log&& record, T&& t) {
    return record << std::forward<T>(t);
}

// Usage:
log() << "Hello world! " << 42;

std::uncaught_exceptions() is used to avoid logging an incomplete message if an exception is thrown in the middle.

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118
  • And for the OP's case they can simply store a reference to their logger instance in the constructor, so it would look like `convenient_log(logger) <<"Same text "< – Mark B Oct 26 '16 at 23:58
  • That's exactly what I need! One more question: what is scope of `log()` eg. when will it be destroyed? – psorek Oct 27 '16 at 09:30
  • @psorek Temporaries live until the end of the expression that created them, i.e. the semicolon. See [Temporary object lifetime](http://en.cppreference.com/w/cpp/language/lifetime#Temporary_object_lifetime). – Tavian Barnes Oct 27 '16 at 15:29
5

Here's a class I've togeather a while ago. It sounds like what you're looking for is this. I was able to achieve it without any daunting inheriting of ostreams, stream_buf or anything else. You can write to files, console, sockets, or whatever you want whenever a flush is caught.

It doesn't work with ostream_iterators but handles all of the io_manip functions well.

Usage:

Logger log;

int age = 32;
log << "Hello, I am " << age << " years old" << std::endl;
log << "That's " << std::setbase(16) << age << " years in hex" << std::endl;
log(Logger::ERROR) << "Now I'm logging an error" << std::endl;
log << "However, after a flush/endl, the error will revert to INFO" << std::end;

Implementation

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

class Logger
{
public:
    typedef std::ostream&  (*ManipFn)(std::ostream&);
    typedef std::ios_base& (*FlagsFn)(std::ios_base&);

    enum LogLevel
    {
        INFO,
        WARN,
        ERROR
    };

    Logger() : m_logLevel(INFO) {}

    template<class T>  // int, double, strings, etc
    Logger& operator<<(const T& output)
    {
        m_stream << output;
        return *this;
    }

    Logger& operator<<(ManipFn manip) /// endl, flush, setw, setfill, etc.
    { 
        manip(m_stream);

        if (manip == static_cast<ManipFn>(std::flush)
         || manip == static_cast<ManipFn>(std::endl ) )
            this->flush();

        return *this;
    }

    Logger& operator<<(FlagsFn manip) /// setiosflags, resetiosflags
    {
        manip(m_stream);
        return *this;
    }

    Logger& operator()(LogLevel e)
    {
        m_logLevel = e;
        return *this;
    }

    void flush() 
    {
    /*
      m_stream.str() has your full message here.
      Good place to prepend time, log-level.
      Send to console, file, socket, or whatever you like here.
    */      

        m_logLevel = INFO;

        m_stream.str( std::string() );
        m_stream.clear();
    }

private:
    std::stringstream  m_stream;
    int                m_logLevel;
};
Stewart
  • 4,356
  • 2
  • 27
  • 59
  • FWIW, flush will be triggered when a std::endl is encountered. Simple and exactly what I needed. – Monza Apr 12 '21 at 02:23
1

Create a custom class derived from std::basic_streambuf to write to your logger, eg:

class LoggerBuf : public std::stringbuf
{
private:
    Logger logger;
public:
    LoggerBuf(params) : std::stringbuf(), logger(params) {
        ...
    }

    virtual int sync() {
        int ret = std::stringbuf::sync();
        logger.log(str());
        return ret;
    }
};

And then you can instantiate a std::basic_ostream object giving it a pointer to a LoggerBuf object, eg:

LoggerBuf buff(params);
std::ostream stream(&buf);
stream << "Some text " << and_variables << " formated using standard string stream";
stream << std::flush; // only if you need to log before the destructor is called

Alternatively, derive a custom class from std::basic_ostream to wrap your LoggerBuf class, eg:

class logger_ostream : public std::ostream
{
private:
    LoggerBuf buff;
public:
    logger_ostream(params) : std:ostream(), buff(params)
    {
        init(&buff);
    }
};

std::logger_ostream logger(params);
logger << "Some text " << and_variables << " formated using standard string stream";
logger << std::flush; // only if you need to log before the destructor is called
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770