How to write iostream-like interface to logging library?

僤鯓⒐⒋嵵緔 提交于 2019-12-09 23:00:06

问题


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.


回答1:


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.




回答2:


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;
};



回答3:


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


来源:https://stackoverflow.com/questions/40273809/how-to-write-iostream-like-interface-to-logging-library

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!