Logger class: how to perform something once at end of line?

Started by
6 comments, last by LilBudyWizer 18 years, 3 months ago
I'm trying to write a simple but effective logger class. I got most of it working how I want so far, but I have a feeling adding the last step will require either heavily rewriting how it's done right now, or some strange way of doing it. First, here's some of the relevant code (not in final state, but the way it's written and used will be the same). This is in the logger class:

enum log_type { info, warn, error }; // this enum is outside the class (global)
	ofstream &operator <<(log_type obj) { // enumerated log-type is passed
		switch(obj) {
			case info:
				flog << "   <info> ";
				break;
			case warn:
				flog << "<warning> ";
				break;
			case error:
				quit = true;
				flog << "  <error> ";
				break;
		}
		return flog;
	}
	template<class T>
	ofstream &operator <<(T obj) { // something else is passed (not preceded by info, warn, etc)
		flog << "    <log> " << obj;
		return flog;
	}



And it's used like this:

	App << info << "an info " << 1 << 2 << endl;
	App << warn << "a warning " << 3 << 4 << endl;
	App << error << "an error " << 5 << 6 << endl;
	App << "alone" << endl;
	App << 7 << " - preceded by int" << endl;



All of this works exactly how I want it. Now, the problem is, I want a MessageBox to show up with the logged message once endl is reached for the error case. That is, the line [ App << error << "an error " << 5 << 6 << endl; ] should show a MessageBox that will say "an error 56". I can't think of a way to do it other than put everything being logged into a buffer, and then have a macro that will do the normal App << ... thing followed by calling something to MessageBox the stuff in the buffer and clear the buffer. Is there a way to do it while keeping the same usage format as I wrote above? It seems like I'm looking for a way to have the endl trigger a call to the MessageBox buffer thing I described. I guess if I knew how to implement something like the stream formatting system that iostreams uses instead of the enumerating way I'm doing, I'd know how to solve my problem. [Edited by - Ivko on January 1, 2006 4:56:48 AM]
Advertisement
My opinion is that you should create a Logger class which forwards its input to both a stringstream and an ofstream through the use of a templated operator <<. That operator should be specialized for the specific case where endl is passed, so as to display a message box with the contents of the stringstream before clearing it.
How does your logger class recieve endl, is it just a global variable set to "\n"? Or is it a function pointer? If you can detect when endl is sent, then all you have to do is stream all input into a stringstream, when endl is detected output the content of this stringstream and set it to "", so that it's ready for the next line.
The endl I'm using is the standard endl for iostreams. Yes, if I could somehow watch for endl and keep everything before it in a stringstream, that would work. There are two problems then:

1) How do I check if the parameter passed is an endl? Could I have an << operator overriden specifically for the endl case by having it accept the type of object that endl is?
2) Right now, only the item after the first << is processed by my overriden operator. This is so that it will only print stuff like the timestamp and info/warn/error once. Once this function for the override is called, the rest of the << operators go straight to an ofstream stream (because App's << operator returns an ofstream). I suppose what I'd have to do is create another class with its own << operator that would handle everything but the first item after the "App << " call, and that class will not print a timestamp but simply buffer everything and watch for the endl.

Does this seem like it'll work? This stuff is new territory for me and I'd like to know if there are any potential problems before I try something like what I just described, and if there are any better ways of doing it.
Quote:Original post by Ivko
1) How do I check if the parameter passed is an endl? Could I have an << operator overriden specifically for the endl case by having it accept the type of object that endl is?


If you are using a templated operator <<, you would create a specialized version that accepts objects of the same type as endl, and uses a simple if to determine if the argument is equal to endl.

Quote:
2) Right now, only the item after the first << is processed by my overriden operator. This is so that it will only print stuff like the timestamp and info/warn/error once. Once this function for the override is called, the rest of the << operators go straight to an ofstream stream (because App's << operator returns an ofstream). I suppose what I'd have to do is create another class with its own << operator that would handle everything but the first item after the "App << " call, and that class will not print a timestamp but simply buffer everything and watch for the endl.


You are overcomplicating things. Using a simple internal flag in the application, you can determine if a call to operator<< is the first of a given line. If it is, add the timestamp and set the flag to false, and return "App" instead of an ofstream. Set this flag to true upon creation or receiving an endl.

Yes, I thought about that after posting. I think I know how to go about doing it now and I'll see how it works out tomorrow. Thanks for the help. I'll post the source once it's done for others looking to create something like this. I was searching for a good way to do this C++ style and didn't find a whole lot of info, so the code might help others looking to do the same thing. My old logger using vsprintf worked much easier. Why is it that they ruin the good stuff in C++? :)
Quote:Original post by Ivko
Yes, I thought about that after posting. I think I know how to go about doing it now and I'll see how it works out tomorrow. Thanks for the help. I'll post the source once it's done for others looking to create something like this. I was searching for a good way to do this C++ style and didn't find a whole lot of info, so the code might help others looking to do the same thing. My old logger using vsprintf worked much easier. Why is it that they ruin the good stuff in C++? :)


OK, firstly, what you want to do is derive from std::filebuf, then override the overflow() member function. Since what you're doing is replacing the filebuf anyways (to slap up a window), that's what you want to do. You then just have to filter to the EOF character.

Second, the C++ is definitely better than any old assembly/C way of doing things. If you want to know how to program using the C++ standard library, read Stroustrup, Jossuttis, or Langer & Kreft.

I'm working on a series of articles on just this topic (implementing loggers on IOStreams. I've already solved this particular problem, but I wouldn't mind more input on other problem you need to solve in a logger.

--
smw

Stephen M. Webb
Professional Free Software Developer

Personally, I would flush the stream to display the message box and use a custom streambuf as mentioned above.
Keys to success: Ability, ambition and opportunity.

This topic is closed to new replies.

Advertisement