I am trying to write a logger class that allows for the insertion operator to be used on it for arbitrary types.
The class needs to know when an insertion is done (e.g. std::endl detected) so that it can then write it out to console / disk / socket / cache for later / whatever at that time. It also needs to know what the logging information pertains to, e.g. "SubsystemA", "SubsystemB", or whatever. So I'd like to be able to do something like so...
[SubsystemA] some stuff 5
[SubsystemB] more stuff 3
I've tried experimenting a couple of times, but needing to be able to do some prepending of subsystem name before the insertions begin and then doing something after you receive it all by intercepting std::endl is proving complicated (mainly the latter).
I think having my class derive from std::ostringstream is a good place to start, but intercepting std::endl is difficult because the method signature is very complicated.
I once for a university assignment had to implement these operator (endl, setw) to use with operator<<. It is a little bit tricky and i actually recommend using another logging lib (search for boost::logging) it should provide you with all the functionality you need.
If you are still interested in doing it yourself i could post some code.
Thanks guys. The reason why I want to have the "SubsystemA" part handled before the rest of the insertion, is I'd like to do some ncurses stuff with that part and colour highlight it.
I know how to do this: log << foo << bar << baz;
You have to overload operator<<() within the class with a template function; let's say we called the class LogManager:
1 2 3 4 5 6 7 8 9
template <typename T>
const LogManager& operator<<(const T& data)
{
MyOfstreamObject << data;
MyOstreamObject << data;
std::cout << data;
std::cerr << data;
return (*this); /* So we can chain calls to the operator, like in log << foo << bar << baz; */
}
In which case, std::endl and std::flush would work correctly anyway.
Edit: MyLogManager("SubsystemA")
That would cause the compiler to try and find a constructor for the LogManager class that accepts a const char* as a parameter and would therefore reconstruct MyLogManager. Then it would throw a compile error because you can't insert data into or left-shift a function.
The problem with the one overload for operator<< is that how will it know when to output the subsystem prefix? As for the latter point, you can also overload the index operator() so it isn't actually a constructor call in that case.
Which would print the prefix for you. I'm not sure if this works exactly like that; I'll have to read up on it because I kind of figured out how to use it without reading a tutorial.
Hmm, that would work, but I'd prefer to have it automated. That is, it knows when it is reading the subsystem, and when the message attributed to that subsystem is being inserted. That lets it do interesting things like direct messages internally for one subsystem to disk, over network, or whatever. It also lets me colour code them with ncurses automatically.
Sorry; above should have been const LogManager& [...])
Well if you overloaded operator() like that, then you could do MyLogManager("YAY") << data;.
As LogManager::operator() returns a reference to the LogManager object that called it, then you can use "<<" with it as well (I think), like this: MyLogManager("SubsystemA") << "Some Data";
That lets it do interesting things like direct messages internally for one subsystem to disk, over network, or whatever. It also lets me colour code them with ncurses automatically
Handle that within the overload for operator(). You could use a vector of structs, like this:
1 2 3 4 5 6 7 8 9 10
struct Subsystem {
std::string SubsystemName; /* e.g. "SubsystemA" */
std::ofstream OutputStream; /* A file or stream to send output to */
};
class LogManager {
private:
std::vector <Subsystem*> SubsystemList;
[...]
};
Then, when operator() sees a subsystem, it does two things:
1. Searches the vector for the Subsystem object where SubsystemName == title
2. If the name is found in the vector, then operator() sets the output colour for ncurses and sets the output stream, and then prints the subsystem name to the output stream.
Good logic Chris and nice work. This is where I get stuck though on trying to handle chaining of multiple insertions and intercepting the std::endl. You want to be able to go...
So "Stuff.. 5" gets appended to SubsystemA's ostringstream object, and then when std::endl is detected, it outputs that stream with the subsystem name prepended and any other ncurses stuff out to wherever the log is directed. I just don't know how to intercept std::endl. It's very tricky.
@kip (& RedX), LogManager::operator<<() should return a const LogManager& to allow chaining (hence the return value of *this in one of my previous posts).
Given RedX's idea, you could do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class LogManager {
...
public:
template <typename T>
const LogManager& operator<<(const T& data)
{
MyStream << data;
return (*this);
}
const LogManager& operator<<(ostream (*endl)(basic_ostream<_CharT, _Traits>& __os))
{
/* Do what you like with std::endl */
}
...
};
Then you can overload operator<<() for whatever you want.
Compiles fine up until it hits the std::endl intercept method where I get...
error: overloaded function with no contextual type information
error: expected primary-expression before ‘&’ token
error: ‘__os’ was not declared in this scope
error: declaration of ‘operator<<’ as non-function