Understanding the Logger

The Logger is used to transfer messages between different threads running at different priority. To illustrate how the logger mechanism works, its structure is explained and it is shown how a new LogReader is implemented.

1. The macros

The ERROR, INFO, DEBUG or MSGBOX macros are defined in CException.h. They wrap calls to methods of the Logger singleton:

#define DEBUG( MSG, ... )                                                                                                       \
        CLogger::GetLogger()                                                                                                    \
                ->Debug( MSG, __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__ )

2. The Logger and the LogQueue

In CLogger, these methods create a CLogMessage object which is added to the logger queue. Via CLogger::RegisterLogReader LogReader objects can be registered at the logger queue. Registering the first LogReader starts the logger thread. CLogQueue::Notify continuously pushes the first message in the queue to all registered LogReaders by calling their Update method:

void CLogQueue::Execute()
{
        // Loop until someone Stop()s the logger thread.
        while( ! CThread::StopRequested() )
        {
...
                // Let the LogReaders output all pending messages
                while( MessagesPending() )
                {
                        Notify();
                }
        }
}
...
void CLogQueue::Notify()
{
...
        // Send message to all registered LogReaders
        CAutoLocker oLocker( m_poReadersLock );
                if ( ! m_lstLogReaders.empty() )
                {
                        std::list< ILogReader* >::iterator itLogReader;
                        for( itLogReader = m_lstLogReaders.begin();
                                 itLogReader != m_lstLogReaders.end(); ++itLogReader )
                        {
                                (*itLogReader)->Update( oLogMessage );
                        }
                }
        oLocker.Unlock( m_poReadersLock );
...
}

3. Implementing a new LogReader

A LogReader that captures log messages to display them as messagebox dialogs to the user is implemented. First, a new macro is added to CException.h:

#define MSGBOX( MSG, ... )                                                                                                      \
        CLogger::GetLogger()                                                                                                    \
                ->MessageBox( MSG, __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__ )

And it's corresponding method to CLogger (CLogger.h):

...
          LOG_MSGBOX =  1 << 5
...
        void MessageBox( const std::string& sMessage, const std::string& sFile = "", int iLine = 0, const std::string& sFunc = "", ... );

Then a new LogReader class is created which inherits ILogReader and (for the user interface connection) QObject:

class CLogReaderGui : QObject, public ILogReader
{
public:
    CLogReaderGui();
    ~CLogReaderGui();

    /// ILogReader implementation
    void ShowLog( const CLogMessage& oLogMessage );
    
    /*
     * Process messages. Called by CGuiMain::Idle()
     */
    void ProcessMessages();

protected:
  
    void ShowMessageBox( const CLogMessage& oLogMessage );

    /*
     * Queue to collect gui logging messages into.
     */
    std::vector< CLogMessage > m_vecLogMessageQueue;
        
    /*
     * Mutex to lock the queue vector.
     */
        CMutex* m_poQueueLock;

};

In the ShowLog method, which is called in ILogReader::Update after checking the CategoryMask (is this type of log activated?), the sent message is added to the message queue. ProcessMessages is called in the CGuiMain::Idle method, which processes recurrent user interface events:

        // process events
        if( m_poLogReaderGui != NULL )
            m_poLogReaderGui->ProcessMessages();

To give CGuiMain access to the LogReader, it is created in the constructor of CGuiMain (and deleted in the destructor):

        // Create LogReaderGui
        m_poLogReaderGui = new CLogReaderGui;
        // We do want messageboxes for user warnings
        m_poLogReaderGui->SetCategory( CLogger::LOG_MSGBOX );
        // register it at the logger
        CLogger::GetLogger()->RegisterLogReader( m_poLogReaderGui );

In CLogReaderGui, it is important, to lock all access to the queue by a mutex. Activate the locker as shortly as possible:

CLogReaderGui.h:
...
    /*
     * Mutex to lock the queue vector.
     */
        CMutex* m_poQueueLock;
...
CLogReaderGui.cpp:
...
/*
 * Process messages. Called by CGuiMain::Idle()
 */
void CLogReaderGui::ProcessMessages()
{
    TRY
    
    // lock queue list while reading out and emptying it  
    CAutoLocker oLocker( m_poQueueLock );

    while( ! m_vecLogMessageQueue.empty() )
    {
            CLogMessage oLogMessage = m_vecLogMessageQueue.front();
            m_vecLogMessageQueue.erase( m_vecLogMessageQueue.begin() );
            oLocker.Unlock( m_poQueueLock );
                ShowMessageBox( oLogMessage );
            oLocker.Lock( m_poQueueLock );
    }
                    
    oLocker.Unlock( m_poQueueLock );
                
    CATCHALL
}

And finally, here is the implementation of the MessageBox call:

void CLogReaderGui::ShowMessageBox( const CLogMessage& oLogMessage )
{
    TRY
 
    QMessageBox* msgBox = new QMessageBox( );
    msgBox->setStandardButtons( QMessageBox::Ok );
    std::string sTitle = "Warning";
    msgBox->setWindowTitle( sTitle.c_str() );
    std::string sMessage = oLogMessage.GetMessage() + "\n\nWarning in\n" + oLogMessage.GetFunc();
    msgBox->setText( sMessage.c_str() );
    msgBox->setIcon( QMessageBox::Warning );
    msgBox->setModal( false );
    msgBox->setAttribute( Qt::WA_DeleteOnClose, true );
    msgBox->open( );

    CATCHALL
}

nrec: UnderstandingTheLogger (last edited 2012-02-20 08:42:00 by hih-kn-2301-01)