简体   繁体   中英

Overloading operator<< in C++ with a prefix

I'm working on a logger class in C++ that has the following syntax:

Logger log("mylog.txt");
log << "a string" << " " << 1 << " " << 6.2 << "\n";

And it prints: a string 1 6.2

This is what my class looks like:

class Logger 
{
private:
    unique_ptr<ofstream> m_pOutStream;

public: 
    Logger(std::string sFile) : m_pOutStream(new ofstream(sFile, std::ios::app))
    {}

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

It works fine, but I would also like to add a prefix to every line (eg a timestamp). So when I write:

Logger log("mylog.txt");
log << "a string" << " " << 1 << " " << 6.2 << "\n";

I want something like this to be displayed:

11:59:12 a string 1 6.2

I have thought of a couple of solutions:

1.Keep every input stored in a list/stream and use an extra function to print and then clear the list/stream:

Logger log("mylog.txt");
log << "a string" << " " << 1 << " " << 6.2 << "\n";
log.logd(); // <- this prints and then clears the internal stream/list.

2.Keep every input stored in a list/stream and print everything after a "new line" character is detected. And clear the internal stream/list after that.

Both of these solutions are nice but I'd prefer to use them only as a last resort.

Is there any other/better way to achieve what I want?

You need to introduce an additional wrapper class for Logger that knows whether the line is starting or being appended.

class Appender
{
    Appender(Logger& logger) : os_(os) { }
    Appender& operator <<(const T& x) { os_ << x; return *this; }
};

class Logger
{
    Appender operator <<(const T& x) { os_ << timestamp() << x; return Appender(os_); }
};

The actual code will be more complicated, but try implementing the following logic.

Add a member_variable bool last_char_was_newline , and use it in the code like this:

template<typename T>
Logger& operator<< (const T& data)
{
    if (last_char_was_newline) {
        *m_pOutStream << current_time_string();
        last_char_was_newline = false;
    }
    *m_pOutStream << data;
    if (last_char(data) == '\n') {
        last_char_was_newline = true;
    }
    return *this;
}

To be more general, you should scan data for embedded newlines, and put the time after each of them as well.

The above pseudo-code is glossing over the tricky part. Since data can be any type, last_char(data) (and the more general scanning of the output for embedded newlines) is non-trivial. A general way to implement it might be to write data to a std::stringstream . Then you can scan this string for newlines, and finally output the string to *m_pOutStream .

Derive a class from std::stringbuf , say LoggerStringBuf , and give it a reference to your output std::ofstream in its constructor. Override the virtual std::stringbuf::sync() method to retrieve a std::string from the base std::stringbuf::str() method and prefix it with a timestamp when writing it to the std::ofstream . This way you generate a new timestamp every time your LoggerStringBuf object is flushed to the std::ofstream for any reason, whether explicitly by std::endl or std::flush , or implicitly by its destructor.

Then have your Logger class derive from std::ostream and initialize it with a LoggerStringBuf object. Then you can stream input values to your Logger and they will be cached in your LoggerStringBuf object until flushed to the std::ofstream . At which time you can prepend timestamps as needed.

For example:

class LoggerStringBuf : public std::stringbuf
{
private:
    std::ostream &m_OutStream;

protected:
    virtual int sync()
    {
        int ret = std::stringbuf::sync();

        std::string s = str();
        str("");

        // note sure if the string includes non-flushing
        // line breaks.  If needed, you can use std::getline()
        // to break up the string into multiple lines and 
        // write a timestamp for each line...
        //
        m_OutStream << "[timestamp] " << s << std::endl;

        return ret;
    };

public:
    LoggerStringBuf(std::ostream &OutStream)
        : std::stringbuf(std::ios_base::out), m_OutStream(OutStream)
    {
    }

    ~LoggerStringBuf()
    {
        sync();
    }
};

class Logger : public std::ostream
{
private:
    std::ofstream m_OutStream;
    LoggerStringBuf m_Buf;

public: 
    Logger(const std::string &sFile)
        : std::ostream(0), m_OutStream(sFile, std::ios::app), m_Buf(m_OutStream)
    {
        init(&m_Buf);
    }

    template<typename T>
    std::ostream& operator<< (const T& data)
    {
        return static_cast<std::ostream&>(*this) << data;
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM