Logging Again

Started by
7 comments, last by Zahlman 16 years, 7 months ago
Hiya, I've posted about how to do logging before, but I just can't seem to find an ideal solution. Every idea I come up with has some drawbacks, and I can't believe that there's not a better way. So far, my ideas have included: - Writing a log class, and passing it to each game components that might need to log something. This seemed OK at first, but it would require almost every class to pass the log down to other classes it might use/create, which seems very messy. Almost all classes would have to maintain a pointer to the log they are supposed to use (if any). - Writing a log class that is instanced without being named, and passing it the message to log (see below). This, however, means I can't store/modify any state information easily, such a logging level, filename, etc. Also, what if I wanted to log to a remote log, via TCP? I'd have to change a lot of code... Example:

// usage
void main()
{
    Log::Log() << "This is a message.";
}

- The other idea I've had is to use a singleton. I've steered clear of this so far, because most people on this board believe they are pretty evil, and I can't say I know any better. If possible, the log needs to be be able to store 'levels' of logs which must be written, be able to log to a different file for each game system, and also be able to log to different source... file, remote, stdout, etc. I think a major part of my problem is I don't like to carry on with a project until I'm completely satisfied that what I've already done... How do you guys do logging? Can anyone suggest anything? I realize this is quite a long post. I really hope someone can help, it would be very much appreciated. Thanks [smile] James
Advertisement
Log("myfile",level) << foo << bar << baz;

People shy away from this because they're afraid of the "performance hit" from opening and closing files often. The bulk of that performance hit is in the flushing of the log, which you need to do frequently anyway, unless you want your logs to be empty when it matters (a crash).

Log is class (so this is creating a temporary object) with an overloaded operator<< that chains a proxy object. This makes uses of the C++ concept that a reference to a temporary "pins" the temporary and keeps it alive until the reference goes away. The messages from chained overloaded << operators are accumulated and flushed in the destructor of the temporary.
Quote:Original post by beebs1
...the log needs to be able to store 'levels' of logs which must be written...

Given a log object L, use methods L.debug(), L.trace(), L.error() and so forth to ensure the correct level. Internally, each method can prepend an indicator to the log message and pass the resulting string to a lower-level, private logging function.

Quote:...the log needs to be able to ...log to a different file for each game system...

Have each game system create its own log, and have its owned objects write to that.

Quote:...the log needs to be able to ...log to different source...

That's just a creation parameter.

Quote:I think a major part of my problem is I don't like to carry on with a project until I'm completely satisfied that what I've already done...

I don't understand all of you with silly perfectionist hang ups. Look, in a real project, you are going to hate at least part of your code. Get over it. "Real artists produce!"
You can modify std::cout, std::cerr and std::clog (by replacing their stream_buf) to output wherever you want. I redirect output to files and to my console (when it becomes available).

See Bregma's post towards the end of that discussion for a sample.

It still leaves you with issues if you choose log levels and different files per subsystem. I think you should keep logs in a single file, and use text tools (like grep under linux) to manage reading them.
I usually use a macro like this:

#define log(text) { if (Settings.logging) { of.open("OCW.log", ios::app); of << text << "\n"; of.close(); } }

or

#define log(file,text) { if (Settings.logging) { of.open(file, ios::app); of << text << "\n"; of.close(); } }

Where Settings.logging is a bool (global) value.

We often do not care about logging. Mostly because we use it for debugging, and read/writes to the HDD per frame are not performance-friendly in games.
When we make the release version, we most likely define log as:

#define log(file,text)

so you don't need to remove all the logging code while increasing performance. If you think something is very important and should still be logged, use another macro like "vlog" which won't be redefined in the release version.

Hope this helps
Dark Sylinc
Thanks for the reply jpetrie.

That is an idea I've used before. The problem I encountered was that there was no way I could think of to store a program-wide level of messages to log (i.e. info, warning, error, etc).

Any ideas?

Thanks again.

Edit:
Wow - missed a fair few posts there... I'm reading...
I cant say as my logging code works for remote output, but it works fine for me. What i did was just simply make a namespace with the logging functions in, and then have basically one command to output to the file. It had 2 params, one was the level of the warning (Error, Warning, General Information, Developer Specific Info) and the actual string. It outputs to a html file with css formatting for the different level of message. I just include the header, and insert a little WriteLine command before important setup features like loading SDL and video modes etc. It can output lines without the carriage return (or the
command i think in html), so you can return to the same line you were outputting to if you have variable results to display

This is my header file for it.
#include <fstream>#include <string>#include <iostream>#pragma once//1=Errors only  2=Errors and warnings//3=Errors, Warnings and user debug info//4=Everything written to debugenum LogLevel{Error=1, Warning=2, Information=3, Custom=4};extern std::ofstream	log;extern LogLevel			Level;extern bool				LogCreated; namespace Log{	void		CreateLog();	void		Createstyle(std::string name,std::string font, int size, std::string colour, std::string tags);	void		WriteLine(LogLevel, std::string);	void		WriteWord(LogLevel, std::string);	void		WriteCode(std::string);	void		WriteReturn();};


Ill post the whole thing for you if you want. Im only just starting with C++ atm though, so its probably not that great for what you need
Thanks for the help guys (and gals?) - that's given me a lot to think about.

ConorH - I don't tend to use globals in C++, but the HTML output is a nice touch, thanks.
Quote:Original post by beebs1
Thanks for the help guys (and gals?) - that's given me a lot to think about.

ConorH - I don't tend to use globals in C++, but the HTML output is a nice touch, thanks.


HTML is a *horrible* idea for log files.

The whole reason you're making the log file is so you can find out what goes wrong when the program unexpectedly crashes.

If it unexpectedly crashes, how are the necessary closing tags for the current context going to get written?

This topic is closed to new replies.

Advertisement