sommergyll.software(c++);
"programming the source code file to contain simple logging"
 

Introduction

Sometimes it is useful and necessary to find out what is going on during the execution of a program but it is not always feasible to use the debugger for tracking down defects. Instead we can put log comments inside the source code, to retrieve useful information about the inner workings of the code. The classes we have created, are designed to be easy to use and making the log comments easy to switch on and off at will.

A Log Comment Example

We will implement a test harness that has a few log comments which will be written to a predefined log file. Then we will show how to accomplish this by creating the logging class. Note that this is only tested on MS Windows XP.

Main method

The listing below, testharness.cpp, consists of a few header file includes and two log comments. Notice how the Log.h is placed after the constants.h file, which contains the define macro that switches on or off the logging. By putting the define macro in a separate file, it will be propagated to all involved header files. If a source code file needs logging even if the macro is not defined, it is possible to customise that one by adding a define macro in that particular file.

 

The log file will contain two entries in this example. Each entry consists of the file name it was placed in, the line number and of course the log text itself, see listing (testharness.cpp).

// TestHarness.cpp
// Copyright (c) Sommergyll Software 2007-2010

#include <tchar.h>

// put the constants.h before any log file header
// to control if it should include logging or not
#include "constants.h"
#include <Log.h>

int _tmain(int argc, _TCHAR* argv[])
{      
    _Log("FirstLogTest");
    _Log("SecondLogTest");    

    return 0;
}


/*
Will produce the following entries in the log file:

c:\sommergyll_software\testharness\testharness.cpp  : 15
FirstLogTest
------------------------------- 
c:\sommergyll_software\testharness\testharness.cpp  : 16
SecondLogTest
-------------------------------
*/

The Constants Header File

The only line of code in constants.h (see listing below) is the macro:
#define LOGLOG. As mentioned before, this macro can be commented out to prevent any logging.

// constants.h
// Copyright (c) Sommergyll Software 2007-2010
// 
#define LOGLOG

The Log Class Header File

Below is the listing with the header file (Log.h). The idea is to include this header in every source file that should have log comments, as mentioned in the testharness.cpp above. Notice that if the LOGLOG macro is not defined, the preprocessor will not insert any code in the source files. The benefit is that we do not need to do error prone cut and pasting every time we would like to include logging.

// Log.h
// Copyright (c) Sommergyll Software 2007-2010
//
#pragma once
#include <string>

namespace Logging
{

/**
 * Simple log class that will write the following to the log file:
 * file name - line number - actual log text
 */
class Log
{
public:    
    Log(std::string aLogDirectory, std::string aLogFileName) throw ();
    ~Log(void);

    void Output(std::string aFileNameStr, 
                int aLineNumber, 
                std::string aLogText);           

private:       
    FILE* iFile;
    bool iOutputAvailable;
};

}// namespace Logging 

/**
 * Log directory is:  C:\Temp\Log\
 * Log file name is:  LogFile.txt
 *
 *
 * The macro mechanism that writes to the log file if LOGLOG is defined. 
 * The defining macro, i.e. #define LOGLOG, should optimally
 * be placed in a separate header file, to be able to switch all logging
 * on and off on demand.
 */
#ifdef LOGLOG
  extern Logging::Log logObj;
  #define _Log(x) logObj.Output(__FILE__, __LINE__, x);
#else
  #define _Log(x) // do nothing...
#endif

The Log Source Code

There is a global object in the source code. This is initialised when the program starts running. The benefit from this is that we can start putting log comments in the source code direct, without having to think about objects and initialisations. This is one of the criterias to make this kind of log commenting easy and useful to have. There is basically no need for understanding what the code does, it is just to include the header file and write the log comments. Thats all!

The constant strings that points to the directory and file name are put in the source code because this class was intended to be a statically linked library. Obviously, the directory and/or file name can be customised to point at any desired directory and file.

The method that writes the text to the log file is placed in Log::Output(). This can also be customised for personalised output.

// Log.cpp
// Copyright (c) Sommergyll Software 2007-2010
// 
#include "Log.h"
#include "Utils.h"
#include <iostream>
#include <exception>

// Log directory
const std::string KLogDirectory = "C:\\Temp\\Log\\";
// Log file name
const std::string KLogFileName = "LogFile.txt";
// Global object
Logging::Log logObj(KLogDirectory, KLogFileName);

namespace Logging
{

/**    
 * Constructor that creates and opens 
 * the log file for appending. 
 * @param aLogDirectory The name of the directory.
 * @param aLogFileName The name of the file to open.
 */     
Log::Log(std::string aLogDirectory, 
         std::string aLogFileName) throw ()
: iFile(NULL), iOutputAvailable(true)
{
    try{
        std::string fullPath = aLogDirectory + aLogFileName;
        // See the Utils class for more information on
        // CreateDirectory and OpenFileForAppending
        Utils::CreateDirectory(aLogDirectory);     
        Utils::OpenFileForAppending(fullPath, iFile);    
    }
    catch (std::string err)
    {
        std::cout << err.c_str() << std::endl;  
        iOutputAvailable = false;
    }
}

Log::~Log(void)
{
    // close the log file
    _fcloseall();
}

/**    
 * The method that does the actual writing 
 * of text to the log file. 
 * @param aFileStr The file name containing the log comment.
 * @param aLineNo The line number in the file where 
 * the comment is located.
 * @param aLogText Comment to write to the log file.
 */ 
void Log::Output(std::string aFileStr, 
                 int aLineNo, 
                 std::string aLogText)
{   
    // Only write to log file if it was created
    if (iOutputAvailable) 
    {
        std::string strOut = aFileStr + "  : " + Utils::IntToStr(aLineNo);
        strOut += "\n";
        strOut += aLogText;
        strOut += "\n";
        strOut += "------------------------------- \n";
        
        // Write the comment to the log file and flush it
        fputs(strOut.c_str(), iFile);   
        fflush(iFile);
    }
}

}// namespace Logging

The Helper Code - Header File

We have put the code that is not directly involved in the logging as a separate class. This makes the code easier to understand and maintain. We have used the c runtime library to perform all the utility handling.

// Utils.h
// Copyright (c) Sommergyll Software 2007-2010
// 
#pragma once
// warning for throw(std::string)
#pragma warning( disable : 4290 ) 

#include <string>
#include <stdio.h>

/**
 * Simple class that contains static helper functions
 * for handling directory, file and string.
 */
class Utils
{
public:   
    static void CreateDirectory(std::string aDirPath) throw(std::string);
    static void OpenFileForAppending(std::string aFileName, 
                        FILE*& aOutFile) throw (std::string);
    static std::string IntToStr(int aInt);           
};

The Helper Code - Source File

The implementation takes advantage of std::strings rich and safe api. We have also added some simple exception handling as the file system may not always behave as expected.

// Utils.cpp
// Copyright (c) Sommergyll Software 2007-2010
// 
#include "Utils.h"
#include <direct.h>
#include <errno.h>

/**
 * Will create a directory
 * @param aDirPath The directory to create.
 * @throw std::string if the directory could not be created.
 */
void Utils::CreateDirectory(std::string aDirPath) throw(std::string)
{            
    if (_mkdir(aDirPath.c_str()) != 0)
    {
        errno_t err;
        _get_errno(&err);
        if (err != EEXIST)
        {
            std::string errStr = 
            "Error: cannot create directory: " + aDirPath;
            throw errStr;
        }
    }     
}

/**
 * Tries to create and open a file for appending text.
 * @param aFileName The file to create/open.
 * @param aOutFile Will contain the file handle on return.
 * @throw std::string if an error occurred.
 */
void Utils::OpenFileForAppending(std::string aFileName, FILE*& aOutFile)
                                 throw (std::string)
{    
    aOutFile = _fsopen(aFileName.c_str(), "a+", _SH_DENYNO);
    if (aOutFile == NULL)
    {
        std::string err = "Error: cannot open: " + aFileName;
        throw err;     
    }        
}

/**
 * Convert an integer to a std::string
 * @param aInt The integer to convert.
 * @return The std::string version of the integer
 */
std::string Utils::IntToStr(int aInt)
{
    char c[20];
    _itoa_s(aInt, c, sizeof(c), 10);
    std::string s(c);
    return s;
}

 

 

Front Page | Disclaimer

© Copyright 2003-2010 Sommergyll Software. All Rights Reserved.

Programming the source code file - adding logging