Beautiful Error Logging

Published August 18, 2005
Advertisement
Tonight I got the new error logger implemented. I utilized the idea of XML/&#106avascript 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!
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement