I'm trying to write a class, similar to a std::ostream object which allows me to:
A) Construct with a name
B) Track log level
C) Prepend name, time, and log level to messages
D) Send messages to a remove server (instead of sending to stdout or a file)
Something like this could work:
1 2 3 4 5 6 7 8
|
template<class T>
Logger::Logger& operator<<(const T& output)
{
std::stringstream iss;
iss << m_name << m_warningLevel << currentTime() << output;
socket.send(iss.str());
return *this;
}
|
However, with C++ style logs, the following would result in a billion disjointed messages:
log << "Value is: " << val1 << std::endl;
I'd like to intercept a std::endl or std::flush and use that to identify the end of a message and to send it.
I've been looking into how to re-implement std::streambuf, but I've gotten a bit lost there. Plus std::streambuf::pubsync (which is called by std::fill) is not a virtual function that I can override so I don't think that's the solution either.
This is the code I have so far, but I think I'm on the wrong track. Line 34 is where I was trying to intercept the std::flush. Obviously it would be better if I could inherit something like a streambuf or ostream, then override flush(), but that function isn't virtual.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
#include <iostream>
#include <sstream>
#include <string>
// Usage:
// Logger log("Module's Name");
// log << "Text or double or anything really" << std::endl;
// log(Logger::WARN) << "Some warning message" << std::endl;
class Logger
{
public:
enum LogLevel
{
INFO,
WARN,
ERROR
};
Logger(std::string name)
: m_moduleName(name),
m_logLevel(INFO)
{}
template<class T>
Logger& operator<<(const T& output)
{ // (int, double, strings, etc.)
m_stream << output;
return *this;
}
Logger& operator<<(std::ostream& (*manip)(std::ostream &))
{ // endl, flush, setw, setfill, etc.
// if (manip == std::flush) this->flush();
manip(m_stream);
return *this;
}
Logger& operator<<(std::ios_base& (*manip)(std::ios_base&))
{ // setiosflags, resetiosflags
manip(m_stream);
return *this;
}
Logger& operator() (LogLevel e)
{
m_logLevel = e;
return *this;
}
void flush()
{
std::stringstream iss;
switch (m_logLevel)
{
case WARN: iss << "WARN : ["; break;
case ERROR: iss << "ERROR: ["; break;
case INFO:
default: iss << "INFO : ["; break;
}
iss << m_moduleName << "] "
<< currentTime() << ": "
<< m_stream.str();
socket.send(iss.str());
m_stream.clear();
m_stream.str(std::string());
m_logLevel = INFO;
}
private:
const std::string m_moduleName;
std::stringstream m_stream ;
int m_logLevel ;
// Placeholder
double currentTime() { return 0.0; }
struct Socket
{ // Placeholder until I get this part done.
void send(const std::string& s) { std::cout << s; }
} socket;
};
|
If I uncomment line 34, I get
$ g++ -o test test.cpp
In file included from test.cpp:1:0:
logger.h: In member function ‘Logger& Logger::operator<<(std::ostream& (*)(std::ostream&))’:
logger.h:34:23: error: assuming cast to type ‘std::basic_ostream<char>& (*)(std::basic_ostream<char>&)’ from overloaded function [-fpermissive]
if (manip == std::flush) this->flush();
^
|
If I use -fpermissive, I get:
$ g++ -o test test.cpp -fpermissive
In file included from test.cpp:1:0:
logger.h: In member function ‘Logger& Logger::operator<<(std::ostream& (*)(std::ostream&))’:
logger.h:34:23: warning: assuming cast to type ‘std::basic_ostream<char>& (*)(std::basic_ostream<char>&)’ from overloaded function [-fpermissive]
if (manip == std::flush) this->flush();
^
|
But in that case, the
if
condition is never true.