Reputation: 2934
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 (e.g. 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?
Upvotes: 1
Views: 603
Reputation: 782320
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
.
Upvotes: 1
Reputation: 598011
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;
}
};
Upvotes: 1
Reputation: 4476
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_); }
};
Upvotes: 1