Sign in to follow this  
psykr

Error Log Design

Recommended Posts

I don't have one of those fancy UML modeling tools, but I'll try my best with an ASCII diagram:
ErrorLog : Singleton<ErrorLog>
|
|-Log::OutputStream
  |-Log::XMLFile
  |-Log::HTMLFile
  |-Log::Win32GUI
So the error log class has a collection of OutputStream objects, that are created by the engine. The files format the input, while the GUI class takes the input and spits it out to a subclassed edit control instead of a file. The next part of the design is an ErrorStream class. Errors aren't printed to a global ErrorLog class or anything; they're sent to a local ErrorStream instead.
int Graphics::Initialize() {
    ErrorStream ES( "Graphics" );
    ...
    ES << EL_FATAL << "Could not create hardware device";
}
That code would print out something like the following:
<system name="Graphics">
  <error level="fatal">Could not create hardware device</error>
</system>
When the ErrorStream goes out of scope, it ends the current section, then prints everything that happened in the code block to the actual log. This destructor is also where multithreading is taken into account; it waits until previous operations are completed, then executes. So.. comments? I'm not sure how / if I would ever do logs that started in the middle of the application's lifetime, and how to make them as safe as possible. I have a kind of framework to shut down the log in case of an uncaught exception, but I'm eager to hear any suggestions.

Share this post


Link to post
Share on other sites
Quote:
When the ErrorStream goes out of scope, it ends the current section, then prints everything that happened in the code block to the actual log. This destructor is also where multithreading is taken into account; it waits until previous operations are completed, then executes.

What happens if an exception occurs before the ErrorStream goes out of scope? From what you've described it sounds like the error information will be lost.

Share this post


Link to post
Share on other sites
Quote:
Original post by joanusdmentia
Quote:
When the ErrorStream goes out of scope, it ends the current section, then prints everything that happened in the code block to the actual log. This destructor is also where multithreading is taken into account; it waits until previous operations are completed, then executes.

What happens if an exception occurs before the ErrorStream goes out of scope? From what you've described it sounds like the error information will be lost.

Well if the exception is reasonable (not something like stack corruption), the destructor is called as part of stack unwinding, so it will get logged. But what you say is very true; I'll modify the class so it logs the error right away, multithreading overhead be damned.

Share this post


Link to post
Share on other sites
Quote:
Original post by psykr

int Graphics::Initialize() {
ErrorStream ES( "Graphics" );
...
ES << EL_FATAL << "Could not create hardware device";
}

That code would print out something like the following:

<system name="Graphics">
<error level="fatal">Could not create hardware device</error>
</system>

Hmmm...
What happens if in the code someone does:

01: ErrorStream ES("Whatever");
02: ES<< EL_FATAL << EL_FATAL;
...
53: ES<< "goes into exception" << "Message" << "Message";

- I'm not sure what one should expect from this code per your desing.
I would assume 02 would create 2 empty exceptions.
However, that's not true.
The first "goes into exception" in 53 will go with the last EL_FATAL exception in 02, and I'd expect the next 2 messages would either:
- Go into the exception
- Pass as normal messages?

Seems like ambiguity. How are you going to know an exception info block is done, without introducing another exception following it?

You'll need some sort of std::endl equivalent, e.g. ES::ende

Normally, one would expect that the code produces 2 empty exceptions, and 3 messages.

- The fact that one can write empty exceptions doesn't make me comfortable. Unless:
= Exception names contain enough error info (e.g. out_of_memory)
= You log the filename and line number with the exception

Hope this helps [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by Coder

01: ErrorStream ES("Whatever");
02: ES<< EL_FATAL << EL_FATAL;
...
53: ES<< "goes into exception" << "Message" << "Message";

- I'm not sure what one should expect from this code per your desing.
I would assume 02 would create 2 empty exceptions.
However, that's not true.
The first "goes into exception" in 53 will go with the last EL_FATAL exception in 02, and I'd expect the next 2 messages would either:
- Go into the exception
- Pass as normal messages?

I was originally hoping that I could design a sort of intermediate temp object that gets destroyed at the semicolon, so each statement results in one error being printed, but I'm also using templates to translate anything passed to the ErrorStream (internally using stringstreams), so the code is looking a bit messy, to say the least.
I think we crossed wires somewhere; my goal was to have each statement (ES << EL_* << *;) result in a single statement in the error log. So line 01 in your example would print a blank fatal error, and line 02 would print "goes into exceptionMessageMessage". Hopefully. The most that this class is supposed to interface with exceptions is to recognize that one's being thrown, log its type, then let it do as it wishes.


Quote:
Seems like ambiguity. How are you going to know an exception info block is done, without introducing another exception following it?

Well, the idea is that if an exception is thrown, the ErrorStream that's currently on the stack will be destroyed during stack unwinding, and so terminate itself correctly. The actual error created by the exception will be caught be a first-chance exception handler (Win32-specific here), and logged directly.

Quote:
You'll need some sort of std::endl equivalent, e.g. ES::endl

I was hoping to get around this by creating a temp object in the first ES << * call, then pass that object around by reference for the rest of the line. When it hits a terminator-thing (a semicolon, for example), the temp is destroyed, and the destructor will do the equivalent of std::endl. I haven't thought this out / tested it too much, so it may not work well.

Quote:
- The fact that one can write empty exceptions doesn't make me comfortable. Unless:
= Exception names contain enough error info (e.g. out_of_memory)
= You log the filename and line number with the exception
Actually, I was going to do all of that. Although I'm not so sure about logging filename / line info in retail builds..

Quote:
Hope this helps [smile]
More than you know [smile]

Share this post


Link to post
Share on other sites
Quote:
Original post by psykr

int Graphics::Initialize() {
ErrorStream ES( "Graphics" );
...
ES << EL_FATAL << "Could not create hardware device";
}


I think a better way is to either use an explicit endl for the "</error>" or to do something like this:
Quote:

ErrorStream Log( "Graphics" );
Log << EL_FATAL("Could not create hardware device");

That allows you to more easily print multiple errors using a single line of code, if for some reason you want to... it also looks better. And by making EL_FATAL take variable arguments, you can do something like:
Quote:

ErrorStream Log( "Graphics" );
Log << EL_FATAL("Screen mode %dx%dx%d not supported", ScreenWidth, ScreenHeight, ScreenBPP);

which can print "Screen mode 1280x960x32 not supported" for example.

So EL_FATAL (and the other error level stuff) can be functions that return strings, using vsprintf for the variable-argument stuff. If they're methods of ErrorStream then you can use "Log.EL_FATAL" even if they're static.

Share this post


Link to post
Share on other sites
Quote:
Original post by psykr
I was originally hoping that I could design a sort of intermediate temp object that gets destroyed at the semicolon, so each statement results in one error being printed, but I'm also using templates to translate anything passed to the ErrorStream (internally using stringstreams), so the code is looking a bit messy, to say the least.

You can't get a temp object to destroy at the semicolon, because semicolons don't signify an end of scope.

You'll need to do it like:

{ // Begin scope
ES<< EL_*<< *;
} // End scope

To get the temp object to be destroyed after the statement.

Quote:
And by making EL_FATAL take variable arguments, you can do something like
[snip]

The problem with variable-argument functions, like printf, is that it's relatively easy to make a mistake and mess up the parameters. Though one can't deny they're useful.

I'd actually prefer a safer approach - a stringstream-like approach. Say, print all what I want in a stringstream, and then do:
ES<< EL_TYPE_GOES_HERE(stringstreamContainingExceptionInfoGoesHere)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this