Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    17
  • comments
    22
  • views
    7066

Beautiful Error Logging

Sign in to follow this  
NoMonkey

246 views

Tonight I got the new error logger implemented. I utilized the idea of XML/javascript from this article:

https://www.gamedev.net/reference/programming/features/xmltech/

I must say, this is one of the sexiest loggers I have ever had. The only portions I ended up using from the article were the actual files provided for download: the .XSL, .XML, and .HTML. I figured there wasn't any point in reinventing such a beatifully layed out wheel.

As for the code, I ended up making some changes to suit my needs. The article uses macro definitions to encapsulate different levels of debug information like so:


#define Log_Write_L1( linetype, linetext )
CLogFile::GetSingletonPtr()->WriteLogEntry(
(linetype),
__NAMESPACE__,
__FILE__,
__FUNCTION__,
__LINE__,
(linetext) )

//this enables you to do the following:

#if SYSTEM_DEBUG_LEVEL == 3
//enable all macros
#define Log_Write_L1( linetype, linetext ) ...
#define Log_Write_L2( linetype, linetext ) ...
#define Log_Write_L3( linetype, linetext ) ...

#elif SYSTEM_DEBUG_LEVEL == 2
//enable levels 1..2 macros
#define Log_Write_L1( linetype, linetext ) ...
#define Log_Write_L2( linetype, linetext ) ...
#define Log_Write_L3( linetype, linetext )

#elif SYSTEM_DEBUG_LEVEL == 1
//enable level 1 macros
#define Log_Write_L1( linetype, linetext ) ...
#define Log_Write_L2( linetype, linetext )
#define Log_Write_L3( linetype, linetext )

#else
//disable macros
#define Log_Write_L1( linetype, linetext )
#define Log_Write_L2( linetype, linetext )
#define Log_Write_L3( linetype, linetext )

#endif







I had to remove \ at the end of all the macro lines for formatting purposes.

While I liked the idea, I didn't like how there was no option to supply a formatted string via a va_list. I first attempted to do the following:

#define Log_Write_L1( STRING, ... )

which I soon found out doesn't work. For a while I thought I was stuck not being able to supply parameters until I found one slick solution.

The idea is to create a function which saves the line number, file name, etc. and then returns back a callback function which does the actual argument list. Now that I read what I just wrote, it isn't very clear. Let me just show what I ended up with:


#if SYSTEM_DEBUG_LEVEL == 3
//enable all macros
#define LOG_L1( LOGTYPE ) CLogger::Log( LOGTYPE , __NAMESPACE__, __FILE__, __FUNCTION__, __LINE__ )
#define LOG_L2( LOGTYPE ) CLogger::Log( LOGTYPE , __NAMESPACE__, __FILE__, __FUNCTION__, __LINE__ )
#define LOG_L3( LOGTYPE ) CLogger::Log( LOGTYPE , __NAMESPACE__, __FILE__, __FUNCTION__, __LINE__ )
#elif SYSTEM_DEBUG_LEVEL == 2
//enable levels 1..2 macros
#define LOG_L1( LOGTYPE ) CLogger::Log( LOGTYPE , __NAMESPACE__, __FILE__, __FUNCTION__, __LINE__ )
#define LOG_L2( LOGTYPE ) CLogger::Log( LOGTYPE , __NAMESPACE__, __FILE__, __FUNCTION__, __LINE__ )
#define LOG_L3( LOGTYPE )
#elif SYSTEM_DEBUG_LEVEL == 1
//enable level 1 macros
#define LOG_L1( LOGTYPE ) CLogger::Log( LOGTYPE , __NAMESPACE__, __FILE__, __FUNCTION__, __LINE__ )
#define LOG_L2( LOGTYPE )
#define LOG_L3( LOGTYPE )
#else
//disable macros
#define LOG_L1( LOGTYPE )
#define LOG_L2( LOGTYPE )
#define LOG_L3( LOGTYPE )
#endif







The CLogger::Log prototype looks like this:


typedef void (*CallBackPtr)(const char*, ...);

static CallBackPtr Log(LOGTYPES LOGTYPE, const char *szNameSpace, const char *szFileName, const char *szFunctionName, int iLineNumber);


//This is the what the callback looks like:

static void LogCallBack(const char *msg, ...);







So now I have a macro which stores a logtype (comment, error, debug, etc.), namespace, filename, function name, and line number and then returns back a pointer to a call back function. The reason these are stored here is because all of these need to be accurate to the place where the LOG_L1 was called.

This call back function then retrieves the previously stored information and writes it to the log along with the formatted message.

here is how the log would be called now:

LOG_L1(COMMENT)("This is comment number %d", iCommentNum);

Tadda! A formatted string that is sent through a macro (sort of anyway).

You might also be wondering where __NAMESPACE__ is coming from. This is a custom define at the top of each file. The macro:

#define __NAMESPACE__ = "Default"

is in the header file and is passed if a custom namespace is not defined.

Let's see, what else... Oh yeah! I also implement GUARD and UNGAURD macros for less-mess try-catch statements:


#define CLEAN_SHUTDOWN
GarbageCollector::CleanUp();
CLogger::close();

#define GUARD
try
{

#define UNGUARD
}
catch( char * str )
{
LOG_L1(ERROR)(str);
CLEAN_SHUTDOWN
}
catch (std::exception& e)
{
LOG_L1(ERROR)("Exception: %s", e.what());
CLEAN_SHUTDOWN
}
catch (...)
{
LOG_L1(ERROR)("FATAL ERROR");
CLEAN_SHUTDOWN
}


//This makes it really easy to encapsulate a function with error handline like so:


int main(void)
{
/* Begin try/catch */
GUARD

// Check for memory leaks at the end of the application.
// Results will show in the Output window.
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

mem_ptr Logger = new CLogger("ErrorLog.xml");

LOG_L1(EVENT)("Releasing Logger");

Logger.release();

throw "We've got a problem!";

/* No errors, shutdown clean. */
CLEAN_SHUTDOWN

/* End try/catch */
UNGUARD

/* Return error free */
return 0;
}






And there you have it.

A significant amount of irrelevant code has been left out of all of this to keep from boring you guys to tears.

Unfortunatelly, I still have a lot of "low-level" engine stuff to implement such as data structures, free lists, misc macros for debugging, profiler, etc. so it will be a while before you guys get any screen shots.

That's all for now!
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

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
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!