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


Comments

  • sample code crashes

    Posted by Legacy on 02/09/2004 12:00am

    Originally posted by: Johan Eliasson

    Yes, in your example above where you initialize the log system, you should add the following line:
    logInfo.nCurrentLogLevel = 0;
    .. or else you could get an ASSERT...

    Reply
  • Thanks for your comments regarding the logger not being thread safe.

    Posted by Legacy on 09/11/2001 12:00am

    Originally posted by: Muhammad Jawad

    Im looking at this after a long time, but whatever problems you guys mentioned are correct. I guess the best way is to use EnterCriticalSection() while writing to a file.

    Im in no mood for improving this :) since ive moved to web development in java (jsps, beans, etc.) but thanks for your comments. It was my first (and hopefully last) work project in mfc. I guess the users can fix the problems easily by reading the comments.

    Thanks.


    Reply
  • Not thread safe

    Posted by Legacy on 08/18/2001 12:00am

    Originally posted by: FS

    Unfortunately the 'writeStringToLogFile' method is NOT thread safe,
    
    allthough it attempts to be.

    1 void CLogSystem::writeStringToLogFile( UINT nLogFile, CString string )
    2 {
    3 // Wait for the write lock
    4 while( ((CLogFile*) m_arLogFiles[nLogFile])->bLocked == TRUE );
    5 // Lock is now available! Set the 'bLocked' attribute in a critical section
    6 EnterCriticalSection( &fileWriteLock );
    7 ((CLogFile*) m_arLogFiles[nLogFile])->bLocked = TRUE;
    8 LeaveCriticalSection( &fileWriteLock );
    9 ((CStdioFile*) (((CLogFile*) m_arLogFiles[nLogFile])->hLogFile))->WriteString( string );
    10 ((CLogFile*) m_arLogFiles[nLogFile])->bLocked = FALSE;
    11}

    Three things,

    1. There is a window between line 4 and 7, were multiple threads
    can enter the code below and cause all this well known trouble.
    2. Line 4 is inefficient code. It will loop and burn valuable CPU
    cycles, called busy waiting. Don't do this. Use Kernel synch
    mechanisms e.g. Semaphores.
    3. The critical section from line 6 to 8 is not needed. Setting
    integer (boolean) values is an atomic action in todays CPUs.
    There is no need to guard this. Further if the integer (boolean)
    is sitting on a word boundery, it is even atomic within multi-
    processor machines.

    I would recommend to use MFCs CSingleLock class to do the job. The
    constructor will lock the guard and the destructor will automatically
    (and always) unlock the guard. This way you are even clean during runtime
    exceptions ... Anyway, the code looks cleaner as well. See below.

    class CLogSystem
    {
    ...
    protected:
    CCriticalSection m_Guard;
    }

    void CLogSystem::writeStringToLogFile( UINT nLogFile, CString string )
    {
    CSingleLock Mutex(&m_Guard, true);
    ((CStdioFile*) (((CLogFile*) m_arLogFiles[nLogFile])->hLogFile))->WriteString( string );
    }

    Reply
  • General Purpose Log System

    Posted by Legacy on 11/15/2000 12:00am

    Originally posted by: Es

    Is this class safe to use for multithreaded/multi-task MFC applications? Can you give a simple sample application?

    Thanks.

    Reply
  • About the speed, you are using the wrong method

    Posted by Legacy on 05/29/2000 12:00am

    Originally posted by: Jin Chengde

    I have observed that your code may have some problem.

    You program will consume to much time when multi-thread doing log on the same file. If use boolean test method, the thread blocked will keep on checking that condition. while use just critical section, the thread will simply be suspended until the critical section was released.

    =================
    AS the comment
    " 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."

    I guess the following is the code.

    void CLogSystem::writeStringToLogFile( UINT nLogFile, CString string )
    {
    // Wait for the write lock
    while( ((CLogFile*) m_arLogFiles[nLogFile])->bLocked == TRUE );
    // Lock is now available! Set the 'bLocked' attribute in a critical section
    EnterCriticalSection( &fileWriteLock );
    ((CLogFile*) m_arLogFiles[nLogFile])->bLocked = TRUE;
    LeaveCriticalSection( &fileWriteLock );
    ((CStdioFile*) (((CLogFile*) m_arLogFiles[nLogFile])->hLogFile))->WriteString( string );
    ((CLogFile*) m_arLogFiles[nLogFile])->bLocked = FALSE;
    }

    Reply
  • Some bug fix for this good logging system...

    Posted by Legacy on 03/04/2000 12:00am

    Originally posted by: Kopi

    If you try to create logfile on a path that doesn't exists, the program fails...here is the fix for this:
    
    

    I modified the openLogFiles in this way:

    BOOL CLogSystem::openLogFiles()
    {
    BOOL bOpenSuccessful = TRUE;
    CString strLogFileName;

    for( UINT i = 0; i < m_logInfo.nLogFiles; i++ )
    {
    TRY
    {
    (CStdioFile*) (((CLogFile*) m_arLogFiles[i])->hLogFile) = new CStdioFile( m_logInfo.strLogFilesPath +
    getLogFileName( i, strLogFileName ) +
    LOGSYSTEM_LOGFILEEXTENSION,
    CFile::modeWrite | CFile::modeCreate | CFile::shareDenyWrite );
    }
    CATCH( CFileException, e )
    {
    closeLogFiles();
    MessageBox(NULL, "Couldn't intialize log system.\nLogging for this session will be disabled!", "Logging error", MB_OK | MB_ICONSTOP);
    return FALSE;
    }
    END_CATCH

    if( (CStdioFile*) (((CLogFile*) m_arLogFiles[i])->hLogFile) == NULL )
    {
    bOpenSuccessful = FALSE;
    break;
    }
    writeStringToLogFile( i, m_logInfo.strAppName + " Log File\n" );
    CTime currentTime = CTime::GetCurrentTime();
    writeStringToLogFile( i, "Started at: " + currentTime.Format("%a %b %d %I:%M:%S %p %Y") + "\n\n" );
    }
    // If not all log files were opened successfully then perform a roll back
    if( !bOpenSuccessful )
    {
    closeLogFiles();
    MessageBox(NULL, "Couldn't intialize log system.\nLogging for this session will be disabled!", "Logging error", MB_OK | MB_ICONSTOP);
    }

    return( bOpenSuccessful );
    }

    The second problem is in the CLogSystem::log procedure itself, the authos missed two paranthesis.

    Change the line:

    if( IsReady == FALSE || nLogLevel > m_logInfo.nCurrentLogLevel)

    to:
    if( IsReady() == FALSE || nLogLevel > m_logInfo.nCurrentLogLevel)

    I hope it helped to someone, maybe some other comments will follow...

    Anyway...this logging system is the one for me :-) You saved me some time man :-)))

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • QA teams don't have time to test everything yet they can't afford to ship buggy code. Learn how Coverity can help organizations shrink their testing cycles and reduce regression risk by focusing their manual and automated testing based on the impact of change.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds