General Purpose Log System

What does the code do

The 'LogSystem' class can be used by developers to add powerful logging capabilites in their MFC applications. It offers the following features:
  • Use of arbitrary number of log levels
  • Use of arbitrary number of log files
  • Fully customizable mappings between log levels and log files
  • Efficient synchronization mechanism for file writing
  • VERY small overhead, easy to use, robust, flexible and well documented code
Now let me explain the system with particular referrence to the above features. Some applications ( such as the one I am currently working on) require extensive logging with various log levels. How you define a log level ofcourse may vary from application to application but the reason to have it is to be able to specify how fine grained you want the logs to be. As an example, for my application I can set log level 0 to be the highest log level and define it as the log level for entering and exitting some MAJOR methods (the methods that are encapsulate the main functionalities of my application). Similarly, I set log level 1 to be a bit more fine grained and define it as the log level for important steps in those major methods. These steps may span more than one minor method calls. Furthermore, I define log level 2 as the log level for entering and exitting minor methods. And finally, I define log level 4 as the log level for MFC & windows api call results. I define these four ! log levels for normal logs. For error log, I can have a seperate log level for each error type (warning, serious, or fatal) or have one log level for all of them or assign any of these error logs to any of the log levels I have already defined.

Once I have done this (which is very easy to do as you'll see) I can run my application at any log level and the logs will be done based on it. For example, if I set the application to run at log level 2, all the logs messages belonging to log levels 0-2 will be logged and the others wont. One thing to keep in mind is that the a log level is just a UINT to the log system and thats it. You just supply it to one of the 'logXXX()' ( logNormal(), logWarning(), logSeriousError(), or logFatalError() ) methods. The actual meaning of a log is what you want to give it. The log system just uses the log level to determine which log file the log is written to.

Each log level is mapped to one file. You can have any number of log levels and log files but the number of log files has to be less than or equal to the number of log levels. This is because there is a one to many mapping between a log file and a log level (and this, in my opinion, is the only mapping that makes sense so this was done on purpose.) For example from the above example, I want to have three log files. So the four log levels have to be mapped to either of these log files. Once the mapping is done logs of that level will be written to that file. The default mapping mechanism maps log levels to files on a one to one basis and in case there are more levels than files, the rest of the log levels will be mapped to the last file. So if I have four log levels and three log files this is how the default mapping would be done:

Log Level Log File
0 0
1 1
2 2
3 2
Note: Log levels and Log files MUST always begin at 0. This is because the log system keeps an array of these and array indexes are zero based.

Even though for most of you, the default mapping will be OK. But you can change the default mapping at ANY time during the life of your application (not just during initialization) using the 'setLogLevelToFile()' method and from that point and on, logs of that level would be written to that file.

The reason I say that file writing syncronization mechanism is efficient is because during writing, log files are locked on a per file basis. So if you have 3 log files then suppose three message handling threads are currently spawned in your application and each of them calls the logXXX() method at pretty much the same time then they can do that in parallel. This is reason I have an inner class called 'LogFile' in the log system. This inner class defines a boolean called 'bLocked' which is set to TRUE when some thread is writing to that file. This approach is better than doing the writing in a critical section because setting a boolean's value is a much much faster operation than writing to a file.

All those praises that I wrote about log system (small overhead, easy to use, robust ...) may or may not be true in your opinion but since it's my best shot, and Ive started using MFC only a month ago, in my opinion those comments are true :) Just take a look at the source and see for yourself.

How to use it in your application

Please dont complain if I make it toooo simple! Im only trying to help :)

1. Include the header file "LogSystem.h" in your application (CWinApp derived) class header and implementation file

2. Declare a 'CLogSystem' object and a accessor method in your application class header file as shown below


 private:
  CLogSystem m_appLog;

 public:
  CLogSystem* getAppLog() { return &m_appLog; }
3. In the default constructor of your application class, initialize the log system by passing a LogInfo structure

 LogInfo logInfo;
	
 logInfo.nLogFiles = 1;
 logInfo.nLogFileSize = 256 * 1024;
 logInfo.nLogLevels = 2;
 logInfo.strAppName = _T("My Application ver 1.0");
 logInfo.strAppTag = _T("WDEClientUI");

 logInfo.strLogFilesPath = _T("C:\\PROGRAM FILES\\"
  "MICROSOFT VISUAL STUDIO\\MYPROJECTS\\TEMP\\");

 if( !m_appLog.initialize( logInfo ) )
  AfxMessageBox("Couldnt intialize log system!");
The 'nLogFileSize' is currently not used by the log system. However when I have time, I will implement log file backup and recreate mechanism which will backup the log file when it's size exceeds this value and start logging in a new empty file

The 'strAppName' is used by the log system when it writes the header of a log file. The format of the header is like this:


<APPLICATION Name> Log File
Started at: <UNIX time and date>
The 'strAppTag' is prepended to the names of the log files in the log file generation algorithm 'getLogFileName()'

The 'strLogFilesPath' is where the log files will be created. This directory must already exist Now you're ready to use the log system! Below are a couple of examples for logging in your main application:

Example1:


((CLogSystem*) getAppLog())->logNormal( 0, 
 "In InitInstance() of the application");	
Example2:

((CLogSystem*)getAppLog())->logSeriousError( 1, 
  "Now going to create the document view and frame of the application" );
Here are a couple of examples to do logging in any class other than the main application:

Example1:


((CLogSystem*)((CTempApp*)AfxGetApp())->getAppLog())->logFatalError(2, 
 "In the constructor for the main frame"	); 
Example2:

((CLogSystem*)((CTempApp*)AfxGetApp())->getAppLog())->logWarning(3, 
 "In OnCreate() of the main frame" );
If you want to change the current log level, use setLogLevel( nLogLevel ). The log message belonging to log levels greater than nLogLevel wont be logged then.

Other APIs of interest

  • If you dont like the default log level to file mapping mechanism, change the doDefaultMappings() method
  • If you dont like the default file name generation mechanism, change the getLogFileName() method
  • If you dont like the default log file header change the openLogFiles() method
  • If you dont like the formatting of log messages change the log() method
  • If you dont like this log system, well, dont use it :)

Limitations and possible extensions

  • The supplied path for log files should be created if it doesnt exist
  • There should be a backup and recreate mechanism
  • The log header should be customizable Instead of using a log file name generation algorithm, the LogInfo struct could have an array of log file names
  • There should be support for exception handling and error checking (specially for open, writing to, and closing log files)

File Notes

  • It is important to extract the source code to "C:\". Winzip will extract it in "C:\Program files\Microsoft Visual Studio\MyProjects\Temp"
  • The executable can be stored in any directory but "C:\Program files\Microsoft Visual Studio\MyProjects\Temp" must exist otherwise the log files will not be created

Downloads

Download demo - 9 Kb
Download demo source - 27 Kb