Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Debugging Technique: Logger for making logs in many ways at once

0.00/5 (No votes)
29 Dec 2004 1  
This logger can be configured to produce log output using various output types.

Introduction

This article is useful for those who want to see good design implementations. This article uses design patterns to solve the problem of logging in an effective way. Logging is an essential part of our development effort. Often, we don't know the kind of inputs that can be passed as parameters to the API/Calls written. This can produce unexpected behavior by the application. So to check/verify, we must log the details in various ways, like we can store it in XML, Text file, Email, Print on console, etc. Here, I have only provided text based implementation as it is easy to do. You can derive your classes to implement other loggers.

Background

If you really want to learn what is behind the scenes, then pick any simple book where design patterns have been given with examples. You will really learn something with this example as well.

Using the Code

A little description of the code is given below. Don't forget to copy the example configuration file in a given directory in the source code of demo application.

We have one main class which is the parent of all loggers, CLogger.

class  CLogger
{
public:
    virtual void MakeLog( enum EnumLogType enumType ,string sData ) = 0;
    virtual void AddDate() = 0;
    virtual ~CLogger(){ }
protected:
    friend class CLoggerManager;
    virtual BOOL UpLoadSettings( ifstream &in_confile ){ return 0; }
};

Two main functions MakeLog, AddDate are provided which can serve to be either basic or the minimum functions for any logger. Now, any class derived from it must implement these two. The third function UpLoadSettings is explained later. See the section below:

class  CLoggerManager : public CLogger
{
    string m_sConfigFile;
    CLogResolver*    m_pResolver;
    void set_members( string &sConfigFile ,CLogResolver* pResolver );
    CLoggerManager(){}
    CLoggerManager( const CLoggerManager& );
    CLoggerManager& operator = ( const CLoggerManager& );
    vector<CLogger*> m_vOutput;
public:
    static CLoggerManager& instantiate( string sConfigFile ,
                    CLogResolver* pResolver );
    ~CLoggerManager();
private:
    BOOL LoadConfig();
public:
    void MakeLog( enum EnumLogType enumType ,string sData );
};

CLoggerManager class is an important part of logging. It acquires the interface from CLogger but also acts as manager by letting various loggers initialize with the data required by each one of them.

First, understand the format of the configuration File. Config file contains the following:

  • Class name
  • Specific data
  • again class name and specific data....

Resolver returns the address of the new object whose class is specified in the class name. Then, UpLoadSettings loads the specific data. Here, Resolver is added as we need not complicate reading of the configuration file. The most important thing to remember about resolver is that you can have derived classes of Resolver to handle more Loggers. Here, in the code, LoadConfig function loads the loggers one by one in the specified vector.

BOOL LoadConfig()
{
    try
    {
        if ( !m_pResolver )
            return FALSE;

        ifstream in_confile; 
        in_confile.open( m_sConfigFile.c_str() );
        if ( !in_confile.is_open() )
            return FALSE;


        // Now Read Settings
        while ( 1 )
        {
            string sClassName;
            in_confile >> sClassName;

            if ( !sClassName.length() )
                break;

            CLogger* pLogger = m_pResolver->Resolve( sClassName );

            if ( pLogger )
            {
                if ( !pLogger->UpLoadSettings( in_confile ) )
                    break;
            }
            else
                break;

            m_vOutput.push_back( pLogger );
        }
    }
    catch(...)
    {
        // Catch any exception
    }
    return TRUE;
}

Now, if we make a call to CLoggerManager to log, then it is routed to all the loggers inside, like this:

void MakeLog( enum EnumLogType enumType ,string sData )
{
   for (vector<CLogger *>::const_iterator iter_output = m_vOutput.begin();
                                            iter_output != m_vOutput.end() ; 
                                            ++iter_output )
   {
        CLogger* p_output = *iter_output;

        if ( p_output )
        {
            p_output->MakeLog( enumType ,sData );
        }
   }
}

Below is the code to make a single instance of Logger as we don't want to replicate our CLoggerManager object (we don't need it).

static CLoggerManager& instantiate(string sConfigFile ,CLogResolver* pResolver)
{
    static CLoggerManager m_Manager;
    m_Manager.set_members( sConfigFile ,pResolver );
    m_Manager.LoadConfig();
    return m_Manager;
}

Have a look at the CFileLogger code in source code files to see how it works. It's very simple to understand.

History

This is the first article I have posted. I have many more new ideas that can enhance knowledge. I am also a learner. I'll wait for your responses. If you have any suggestions, you are welcome.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here