Logger Class

Pages: 12
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...

1
2
MyLogManager("SubsystemA") << "some stuff " << 5 << std::endl;
MyLogManager("SubsystemB") << "more stuff " << 3 << std::endl;


Which would output...

[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 RedX. I'll take a look at boost::logging, but some code would be helpful too. =)
I did this kind of thing once before; let me find the code for you.
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'm not sure it would work exactly like that. You could do something like
1
2
3
4
5
LogManager MyLogManage("LogManager.log");
MyLogManager.SetSubsystem("SubsystemA");
MyLogManager << "Data!";
MyLogManager.SetSubsystem("SubsystemB");
MyLogManager << "More data!";
In "LogManager.log"
[SubsystemA]: Data!
[SubsystemB]: More data!


Judging by your post I haven't done what you wanted before; but I could try it now and write some conceptual code.
Last edited on
That's pretty close, but I'd like to be able to use multiple insertions on one line, and also specify the subsystem at that time like...

1
2
LogManager MyLogManager("whatever.log");
MyLogManager("SubsystemA") << "some stuff " << 5 << std::endl;


It needs to be able to "hook" std::endl so it knows internally to flush the ostringstream it is using to buffer, I reckon.
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.
Last edited on
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.
Of course you can; I didn't think of that. In which case, you could also do this:
1
2
3
4
const LogManager& operator()(std::string title)
{
        (*this) << "[" << title << "]: ";
}


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.
Last edited on
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.
Last edited on
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...

 
MyLogManager("SubsystemA") << "Stuff.. " << 5 << std::endl;


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.
Consider GNU's implementation of endl (see line 256):
http://chadmoore.us/source/gnu/libstdc++/libstdc++-2.90.8/bits/std_ostream.h

It's just a function!
Right. But I still don't know how to let the logger class know when it's being inserted.
As moore says, it's just a function.

1
2
3
ostream& operator<<( ostream (*endl)(basic_ostream<_CharT, _Traits>& __os)){
//this should be called when the endl is inserted
}
@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.
Last edited on
You should not return it const, since operator << will probably modify the stream.
Good point. Just return a LogManager reference then.
Yeah, it shouldn't be const, othewise you wouldn't be able to chain the insertions.

As I had suspected, the signature of the method to insert std::endl is very complicated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class LogManager
{
    public:

    LogManager(const std::wstring &Name)
            : m_Name(Name), m_CurrentLogLevel(Normal)
    {
    }
        
    template <typename Type>
    LogManager &operator<<(const Type &Value)
    {
        std::wcout << Value;
        return *this;
    }

    template <typename _CharT, typename _Traits>
    LogManager &operator <<(std::ostream (*std::endl)(std::basic_ostream<_CharT, _Traits> &__os))
    {
        std::wcout << "End line detected...";
        return *this;
    }
 ...
};


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
Pages: 12