Click here to Skip to main content
15,881,757 members
Articles / Mobile Apps / Windows Mobile

Code that debugs itself

Rate me:
Please Sign up or sign in to vote.
4.78/5 (74 votes)
17 Feb 200534 min read 311.1K   5K   184  
A set of macros for detecting and reporting critical errors, combined with a technique of writing solid code.
/////////////////////////////////////////////////////////////////////////////////
//
//  QTrace trace log facility
//
//  Copyright (c) 2002-2004
//  Andrew Schetinin
//
//  This software is provided "as is" without express or implied warranty, 
//  and with no claim as to its suitability for any purpose.
//
//  Permission to use or copy this software for any purpose is hereby granted 
//  without fee, provided the above notices are retained on all copies.
//  Permission to modify the code and to distribute modified code is granted,
//  provided the above notices are retained, and a notice that the code was
//  modified is included with the above copyright notice.
//
//  This software accompanies the article "Code that debugs itself"
//  located at http://www.codeproject.com/debug/qafdebug.asp
//
//  You are welcomed to report bugs, send comments and post code modifications 
//  to aschetinin@hotmail.com 
//
/////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
/// @file QAFTrace.h
/// @brief Set of classes for printing debug logs to files.
//////////////////////////////////////////////////////////////////////////

#ifndef _QAFTRACE_H_
#define _QAFTRACE_H_

/// By default, trace logs are compiled only in debug mode!!!
/// Using the macro DISABLE_Q_TRACE it is possible to disable 
/// trace logs even in debug builds, or in specific source files.
#ifdef _DEBUG
	#if !defined(DISABLE_Q_TRACE) && !defined(ENABLE_Q_TRACE)
		#define ENABLE_Q_TRACE
	#endif
#endif
/// By default, trace logs are compiled only in debug mode!!!

/// DISABLE_Q_TRACE define prevails on ENABLE_Q_TRACE
#if defined(DISABLE_Q_TRACE) && defined(ENABLE_Q_TRACE)
	#undef ENABLE_Q_TRACE
#endif

/// When compiling without ENABLE_Q_TRACE defined, there will no be any trace logs!!!
#ifndef ENABLE_Q_TRACE
	#define Q_TRACE( expr ) do { /* do nothing */ } while (false)
	#define Q_TRACEX( device, expr ) do { /* do nothing */ } while (false)

	/// This is a special conditional comment for lines that should be executed 
	/// only in when the trace logs are 
	/// Usage:
	///        Q_TO m_Log.Log( _T("Sending %s bytes to socket"), iDataSize ); 
	/// In release mode this line will become:
	///        // m_Log.Log( _T("Sending %s bytes to socket"), iDataSize ); 
	/// It is specifically useful for classes from this QTrace namespace whose are not defined 
	/// when the trace logs are disabled.
	#define __Q_TRACE_COMMENT /
	#define Q_TRACE_ONLY __Q_TRACE_COMMENT/
	#define Q_TO Q_TRACE_ONLY
#endif
/// When compiling without ENABLE_Q_TRACE defined, there will no be any trace logs!!!

/// This file will be compiled only with ENABLE_Q_TRACE defined!!!
#ifdef ENABLE_Q_TRACE
/// This file will be compiled only with ENABLE_Q_TRACE defined!!!

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strstream>

#include "CSyncCS.h"
#include "QAFDebug.h"
#include "RegistryUtils.h"

/// This is the main directive for trace logs
/// Usage:
///        Q_TRACE( m_Log << "New post " << this << " created" );
/// When compiling in debug mode, the trace logs are enabled by default!!!
/// If trace logs are not enabled at runtime, this macro still will execute
/// part of the code (initialize instances of Device and Record classes. 
#define Q_TRACE( expr ) if( false ) int i = 0; else QTrace::Record(__FILE__,__LINE__) << expr

/// This is an optimized directive for trace logs
/// When the trace logs are not enabled at runtime, this macro makes 
/// the performance close to the original (without any trace logs).
#define Q_TRACEX( device, expr ) if( ! device.IsEnabled() ) int i = 0; else device << QTrace::Record(__FILE__,__LINE__) << expr

/// This is a special conditional comment for lines that should be executed 
/// only in when the trace logs are 
/// Usage:
///        Q_TO m_Log.Log( _T("Sending %s bytes to socket"), iDataSize ); 
/// In release mode this line will become:
///        // m_Log.Log( _T("Sending %s bytes to socket"), iDataSize ); 
/// It is specifically useful for classes from this QTrace namespace whose are not defined 
/// when the trace logs are disabled.
/// When the trace logs are enabled, these macros transform to nothing and enable what they hide.
#define Q_TRACE_ONLY
#define Q_TO

/////////////////////////////////////////////////////////////////////////////////////////////
// Namespace for all trace classes (main clases are 
//     QTrace::LogFile, QTrace::Log and QTrace::Dump
namespace QTrace
{
/////////////////////////////////////////////////////////////////////////////////////////////

/// Trace levels
enum TraceLevel
{
	TraceNone   =  0, // no trace
	TraceError  = 20, // only trace errors (textual description of errors)
	TraceInfo   = 40, // some extra error info (save input parameters that caused the error) 
	TraceDebug  = 60, // debugging info (trace the main steps of the process)
	TraceDetail = 80, // detailed debugging info (trace the every single step of the process)
	TraceAll    = 100 // trace everything
};

///////////////////////////////////////////////////////////////////////////////////////////
// Format of the trace log
///////////////////////////////////////////////////////////////////////////////////////////
// time with ms pid  thread             location
// 15:05:49:547 [A5F:E67] Trace message <filename.cpp:120>
///////////////////////////////////////////////////////////////////////////////////////////
// Format of the memory dump
///////////////////////////////////////////////////////////////////////////////////////////
// time with ms pid  thread             location
// 15:05:49:547 [A5F:E67] Memory dump of "CType", address 0x120A, size 16b (0x10) <filename.cpp:120>
// 0000120A  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  1.2.3.4. 5.6.7.8.
///////////////////////////////////////////////////////////////////////////////////////////

/// Forward definition of the trace class
class Device;

/// Base class for all message construction classes 
class Record
{
public:

	/// Enumeration for stages of the 
	enum TraceStage 
	{
		/// this should be a short trace message between prefix and postfix
		/// for most trace records this will be the only message they output
		TRACE_HEADER = 10, 
		/// this should be a multiline trace message after the header line
		/// it does not include the prefix and postfix (memory dump or alike)
		TRACE_BODY = 20    
	};

	/// Constructor receives the source code file name and line number
	Record( const char * szFileName, const int iLine ) 
		: m_szFileName(szFileName), m_iLine(iLine), m_pDevice(NULL), 
		m_bTraced(false), m_nTraceLevel(TraceNone) 
	{
	}
	
	/// Default constructor does nothing except initializing
	Record() : m_szFileName(NULL), m_iLine(0), m_pDevice(NULL), 
		m_bTraced(false), m_nTraceLevel(TraceNone) 
	{
	}
	
	/// Destructor does whatever it is needed to clear 
	/// but before it completes the trace output if it was not performed yet
	virtual ~Record() 
	{ 
		if( ! m_bTraced && Q_ASSERT( NULL != m_pDevice ) )
			DoTrace();
		m_szFileName = NULL; 
		m_iLine = 0; 
		m_pDevice = NULL;
		m_bTraced = true;
		m_nTraceLevel = TraceNone;
	}
	
	/// Set the current trace device
	void SetDevice( Device * device ) 
	{
		if( Q_ASSERT( NULL != device ) )
			m_pDevice = device; 
	}

	/// Get the current trace device
	/// It may return NULL if the device was not yet initialized
	Device * GetDevice() const { return m_pDevice; }

	/// Output the current trace record to the trace device
	void DoTrace();

	/// Add new portion of text to the current trace record
	void AddText( const char * szText )
	{
		if( Q_ASSERT( NULL != szText ) )
			m_Buffer << szText;
	}

	/// Set location in source code
	void SetLocation( const char * szFileName, const int iLine )
	{
		if( Q_ASSERT( NULL != szFileName ) )
		{
			m_szFileName = szFileName;
			m_iLine = iLine;
		}
	}

	/// Get source code file name
	const char * GetLocationFile() const { return m_szFileName; }

	/// Get source code line
	int GetLocationLine() const { return m_iLine; }

	/// Return the trace level
	TraceLevel GetTraceLevel() const { return m_nTraceLevel; }

	/// Set the record trace level
	void SetTraceLevel( const TraceLevel nTraceLevel ) { m_nTraceLevel = nTraceLevel; }

	/// This function should indicate if this record does not contain 
	/// an error message (it serves only as a container for source code location)
	virtual bool IsEmpty() const { return 0 == m_Buffer.pcount(); }

protected:

	/// The pointer to the file name of source code
	/// This is a pointer to static string, never give 
	/// a dynamic string pointers as the file location!
	const char * m_szFileName;

	/// Line number in source code
	int m_iLine;

	/// Pointer to the current device
	Device * m_pDevice;

	/// This flag is used to check in the destructor and trace 
	/// the record if it was not yet traced. 
	/// It must be assigned to true in DoTrace()
	bool m_bTraced;

	/// This is the buffer that is used for accumulating the text
	std::strstream m_Buffer;

	/// Trace level of the record
	TraceLevel m_nTraceLevel;

	/// This class should be a friend to access the DoLog() function
	friend class Device;

	/// Write count of bytes to the trace device (wrapper for the Device method)
	bool Write( LPCVOID const pBuffer, const DWORD dwBufferSize, 
		DWORD & dwBytesWrittenRet );
	
	/// This function will be called from the device's DoTrace() function
	/// The device opened the output stream already, and this function 
	/// can write the record to it.
	virtual DWORD TraceRecord( const TraceStage stage, bool & bFinishedRet );

	/// Assignment operator is disabled
	Record & operator=( Record & obj ) { Q_ASSERT( false ); }
	
	/// Copy constructor is disabled
	Record( Record & obj ) : m_szFileName(NULL), m_iLine(0), m_pDevice(NULL), 
		m_bTraced(true) { Q_ASSERT( false ); }
	
private:
};

/// This class knows how to output a memory dump
class Dump: public Record
{
public:
	
	/// Constructor prepares or constructs the error message
	Dump( const void * const pBuf, const unsigned long ulBufSize, 
		LPCTSTR szTypeName = _T("void *") ) : m_pBuf(pBuf), 
		m_ulBufSize(ulBufSize), m_szTypeName(szTypeName) {}
	
	/// Destructor does whatever it is needed to clear 
	virtual ~Dump() 
	{ 
		if( ! m_bTraced && Q_ASSERT( NULL != m_pDevice ) )
			DoTrace();
		m_pBuf = NULL; 
		m_ulBufSize = 0; 
		m_szTypeName = NULL; 
	}
	
	/// This function should indicate if this record does not contain 
	/// an error message (it serves only as a container for source code location)
	virtual bool IsEmpty() const { return false; }
	
protected:

	/// Pointer to the memory to dump
	const void * m_pBuf;

	/// Size of the memory to dump
	unsigned long m_ulBufSize;
	
	/// Type name for the memory block
	LPCTSTR m_szTypeName;

	/// This function will be called from the device's DoTrace() function
	/// The device opened the output stream already, and this function 
	/// can write the record to it.
	virtual DWORD TraceRecord( const TraceStage stage, bool & bFinishedRet );
	
	/// Assignment operator is disabled
	Dump & operator=( Dump & obj ) {}
	
	/// Copy constructor is disabled
	Dump( Dump & obj ) {}
	
private:
};

/// This class knows how to trace the call stack
class CallStack: public Record
{
public:
	
	/// Constructor prepares or constructs the error message
	CallStack() {}
	
	/// Destructor does whatever it is needed to clear 
	virtual ~CallStack() 
	{ 
		if( ! m_bTraced && Q_ASSERT( NULL != m_pDevice ) )
			DoTrace();
	}
	
	/// This function should indicate if this record does not contain 
	/// an error message (it serves only as a container for source code location)
	virtual bool IsEmpty() const { return false; }
	
protected:
	
	/// This function will be called from the device's DoTrace() function
	/// The device opened the output stream already, and this function 
	/// can write the record to it.
	virtual DWORD TraceRecord( const TraceStage stage, bool & bFinishedRet );
	
	/// Assignment operator is disabled
	CallStack & operator=( CallStack & obj ) {}
	
	/// Copy constructor is disabled
	CallStack( CallStack & obj ) {}
	
private:
};

/// Trace class that takes care of the trace file and synchronization
class Device 
{
public:

	/// szID serves as the id in the registry to switch the log on,
	/// and as the name of the mutex for synchronizing the access 
	/// to the log device.
	/// bNeedMutex indicates if the mutex should be created for synchronization.
	/// nDefaultTraceLevel indicates which trace level will have the records
	/// by default. This trace level will be compared with the enabled trace level
	/// from the registry.
	/// The ID should not be NULL, otherwise the trace log will be switched off.
	Device( LPCTSTR szID, const bool bNeedMutex, 
		const TraceLevel nDefaultTraceLevel = TraceDebug );

	/// 
	virtual ~Device();

	/// This function outputs the trace record to the trace device 
	void DoTrace( Record & obj );
	
	/// Check if the trace log is enabled at all (in the registry there is 
	/// something different from 0 set up.
	/// Independently, each record also checks if it has a compatible trace level.
	bool IsEnabled();

	/// Return the enabled trace level in the registry
	DWORD GetEnabledTraceLevel();
	
protected:

	/// This class should have access to this class's Write() function
	friend class Record;

	/// In-process synchronization object for the device
	CAutoCS m_cs;

	/// This mutex, if requested by the descentant of this class
	/// will synchronize the access to the physical log device.
	HANDLE m_hMutex;
	
	/// The ID of the log device. This ID will be the name of 
	/// the synchronization mutex and the value in the registry
	/// to switch the log on/off.
	LPTSTR m_szID;
	
	/// The default trace level of the records in this log device.
	TraceLevel m_nDefaultTraceLevel;
	
	/// Wait timeout for the mutex object
	int m_dwWaitTimeout; 
	
	/// Registry value that must be defined in order to get the trace log.
	/// By default all trace logs are disabled.
	/// The trace log may be enabled from the registry HKLM\Software\MyCompany\Log\ + LOG_FILE_NAME
	/// By entering the value from 0 to 100.
	CRegDWORD * m_pdwEnabledTraceLevel;
	
	/// Write count of bytes to the log (this method is used by LogMessageBase descentants)
	virtual bool Write( LPCVOID const pBuffer, const DWORD dwBufferSize, DWORD & dwBytesWrittenRet ) = 0;

	/// Write prefix before the record
	virtual DWORD WritePrefix( Record & record, const DWORD dwBytesWritten );

	/// Write postfix after the record
	virtual DWORD WritePostfix( Record & record, const DWORD dwBytesWritten );

	/// Lock the device so others could not use it
	bool Lock();

	/// Unlock the device
	void Unlock();
	
	// close the current trace device
	virtual void CloseTraceDevice() {}

	// open the trace device
	virtual bool OpenTraceDevice() { return true; }

	/// Return the position of far right align that will be used 
	/// for logging the source code file name and line
	/// This makes the trace log cleaner but still informative.
	/// Returning 0 or any small value will mean that the file name
	/// will be located right after the trace log message.
	virtual unsigned short GetFarRightColumn() { return 120; }

};

/// Log device that outputs the trace log to file
class LogFile: public Device
{
public:

	/// Specify the short file name without path, default trace level 
	/// and the maximum limit for the trace log file size.
	LogFile( LPCTSTR szFileName, const TraceLevel nDefaultTraceLevel, 
		const int nMaxLogFileSize );

	/// destructor does not closes the file, file is opened and closed on each write
	virtual ~LogFile();
	
protected:

	/// Trace log file handle
	HANDLE m_hFile;
	
	/// Trace log file name
	TCHAR m_szFilename[MAX_PATH];
	
	/// Maximum trace log file size
	int m_nMaxLogFileSize;

	/// Write count of bytes to the log (this method is used by LogMessageBase descentants)
	virtual bool Write( LPCVOID const pBuffer, const DWORD dwBufferSize, DWORD & dwBytesWrittenRet );
	
	/// open a new trace file
	virtual bool OpenTraceDevice();

	/// close the current trace file
	virtual void CloseTraceDevice();

private:

};

/// Planned class for outputting the trace log in VC++ IDE.
/// Not yet implemented.
class DebugOutputDevice: public Device
{
public:
protected:
private:
};

/////////////////////////////////////////////////////////////////////////////////////////////
// CQWideCharToMultiByte class
/////////////////////////////////////////////////////////////////////////////////////////////

// convert the string to the UTF-8 format
class CQWideCharToMultiByte
{
public:
	
	// convert the string to the UTF-8 format
	CQWideCharToMultiByte( LPCWSTR szInput, UINT uiCodePage = CP_UTF8 )
		: m_szBuffer(NULL), m_ulBufferSize(0)
	{
		if( Q_INVALID( NULL == szInput ) )
			return;
		int iStrLen = wcslen(szInput); // in wide chars
		if( 0 == iStrLen )
		{
			m_szBuffer = (LPSTR) malloc( sizeof(char) );
			if( Q_ASSERT( NULL != m_szBuffer ) )
				m_szBuffer[0] = 0;
			return;
		}
		int iRet = WideCharToMultiByte( uiCodePage, 0, szInput, iStrLen, NULL, 0, NULL, NULL );
		if( Q_ASSERT( iRet > 0 ) )
		{
			m_szBuffer = (LPSTR) malloc( sizeof(char) * (iRet + 1) );
			if( Q_ASSERT( NULL != m_szBuffer ) )
			{
				m_ulBufferSize = WideCharToMultiByte( uiCodePage, 0, szInput, iStrLen, 
					m_szBuffer, iRet + 1, NULL, NULL );
				m_szBuffer[m_ulBufferSize] = 0;
				Q_ASSERT( iRet == m_ulBufferSize );
			}
		}
	}
	
	~CQWideCharToMultiByte()
	{
		if( NULL != m_szBuffer )
		{
			free( m_szBuffer );
			m_szBuffer = NULL;
		}
		m_ulBufferSize = 0;
	}
	
	/// Return the buffer (it may be UTF-8 which may be double-0-terminated!) 
	LPCSTR buffer() const
	{
		return m_szBuffer;
	}
	
	/// Return size of the string in bytes not including terminating 0 character
	unsigned long size() const
	{
		return m_ulBufferSize;
	}

	/// Return the pointer to the converted buffer and nullify it
	LPCSTR Detach() 
	{
		LPCSTR szTemp = m_szBuffer;
		m_szBuffer = NULL;
		return szTemp;
	}
	
protected:
	
	/// The buffer - it is not a real 0-terminated string
	LPSTR m_szBuffer;
	
	/// size of the string in bytes not including terminating 0 character
	unsigned long m_ulBufferSize;
	
private:
};

/////////////////////////////////////////////////////////////////////////////////////////////
// Helper formatting classes
/////////////////////////////////////////////////////////////////////////////////////////////

/// This is a printf-style formatting class. Use it with causion!
/// printf-style functions cannot detect ASCII and UNICODE strings and might fail!
/// This is also a base class for other formatting helper classes. 
class Format
{
public:

	/// Be careful with this class since it cannot detect ASCII and UNICODE strings.
	/// It will assume it works with strings of TCHAR.
	Format( LPCTSTR szFormat, ... );

	/// Destructor - frees the memory allocated.
	virtual ~Format();

	/// Return the formatted string in ASCII.
	/// Do not free the returned pointer, it will be destroyed automatically.
	virtual LPCSTR GetTextA();

	/// Return the formatted string in UNICODE
	/// Do not free the returned pointer, it will be destroyed automatically.
	virtual LPCWSTR GetTextW();
		
protected:

	/// An additional variable for the converted string
#ifdef _UNICODE
	LPSTR m_szConverted;
#else
	LPWSTR m_szConverted;
#endif

	/// The buffer for the string
	LPTSTR m_szBuffer;

	/// Constructor by default is purposed for descendant classes only
	Format() : m_szBuffer(NULL), m_szConverted(NULL) {}
	
private:
};

/// Helper class for rich formatting of integers
class Num: public Format 
{
public:

	/// Since this class is only needed for rich formatting,
	/// you must specify the uiFixedLength. By default, the value
	/// will be right-aligned and prevailed with spaces.
	Num( const signed long lValue, const unsigned int uiFixedWidth, 
		const TCHAR bPadChar = ' ', const bool bLeftAlign = false );

protected:
private:
};

/// Helper class for rich formatting of integers in hex format
class Hex: public Format 
{
public:

	/// By default, the hex value will not be padded with zeros,
	/// but you may change it. Align has no meaning here since
	Hex( const unsigned long ulValue, const unsigned int uiFixedWidth = 0, 
		const TCHAR bPadChar = '0', const bool bLeftAlign = false );
	
	/// By default, the hex value will not be padded with zeros,
	/// but you may change it. Align has no meaning here since
	Hex( const void * ptr, const unsigned int uiFixedWidth = 0, 
		const TCHAR bPadChar = '0', const bool bLeftAlign = false );
	
protected:

	/// Convert an integer to a hex string
	void Convert( const unsigned long ulValue, const unsigned int uiFixedWidth, 
		const TCHAR bPadChar, const bool bLeftAlign );
	
private:
};

/////////////////////////////////////////////////////////////////////////////////////////////
// overloaded operators <<
/////////////////////////////////////////////////////////////////////////////////////////////

/// Set new device and return the record
inline Record & operator <<( Device & device, Record & record )
{
	record.SetDevice( &device );
	return record;
}

/// Set new device and return the record
inline Record & operator <<( Record & record, Device & device )
{
	record.SetDevice( &device );
	return record;
}

/// Flush the current message and return the second message
inline Record & operator <<( Record & old_record, Record & new_record )
{
	old_record.DoTrace();
	new_record.SetDevice( old_record.GetDevice() );
	new_record.SetLocation( old_record.GetLocationFile(), old_record.GetLocationLine() );
	return new_record;
}

/// Add the formatted value to the current message and return the message
inline Record & operator <<( Record & record, Format & value )
{
	record.AddText( value.GetTextA() );
	return record;
}

/// Add a new part to the record
inline Record & operator <<( Record & record, LPCSTR szText )
{
	record.AddText( szText );
	return record;
}

/// Add a new part to the record
inline Record & operator <<( Record & record, LPCWSTR szText )
{
	CQWideCharToMultiByte conv( szText, CP_ACP );
	record.AddText( conv.buffer() );
	return record;
}

/// Add a new part to the record
inline Record & operator <<( Record & record, signed long lValue )
{
	char szBuffer[12] = { 0 };
	ltoa( lValue, szBuffer, 10 );
	record.AddText( szBuffer );
	return record;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// Namespace for all trace classes (main clases are 
//     QAFTrace::LogFile, QAFTrace::Log and QAFTrace::Dump
}; // namespace QAFTrace
/////////////////////////////////////////////////////////////////////////////////////////////

/// This file will be compiled only with ENABLE_Q_TRACE defined!!!
#endif //	ifdef ENABLE_Q_TRACE
/// This file will be compiled only with ENABLE_Q_TRACE defined!!!

#endif // XY_TRACE_H

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Team Leader OpTier
Israel Israel
Programming computers since entering the university in 1992, but dreaming of programming long time before putting hands on my first computer.

Experienced in cross-platform software development using C++ and Java, as well as rapid GUI development using Delphi/C#. Strong background in networking, relational databases, Web development, and mobile platforms.

Like playing guitar, visiting historical sites (not in the Internet, in the car Smile | :) ) and cooking meat with friends (sorry about vegetarians). Look for more information on www.schetinin.com

Comments and Discussions