Click here to Skip to main content
15,886,664 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.6K   5K   184  
A set of macros for detecting and reporting critical errors, combined with a technique of writing solid code.
//////////////////////////////////////////////////////////////////////////
/// @file QAFTrace.cpp
/// @brief Set of classes for printing debug logs to files.
//////////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include "qaftrace.h"

/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!
#ifndef DISABLE_Q_TRACE
/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!

#ifdef WIN32
	#include <crtdbg.h>
#endif

#include <malloc.h>
#include <stdarg.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>

// Windows-specific code
#ifdef WIN32

	#include <io.h>
	#include <direct.h>
	#include <shlobj.h>

	// In order to link to version.lib automatically
	#pragma message("Automatic link to version.lib")
	#pragma comment(lib, "version.lib")

	const int R_OK = 0;
	const int W_OK = 0;

	const Q_TCHAR PATH_DELIMITER = _T('\\');
	const Q_TCHAR PATH_DELIMITER_STR[] = _T("\\");

	#define EOL "\r\n"
	#define TEOL _T(EOL)

// Linux-specific code
#else

	#include <sys/time.h>
	#include <errno.h>
	#include <unistd.h>

	const Q_TCHAR PATH_DELIMITER = _T('/');

	#define EOL "\n"
	#define TEOL _T(EOL)

	#define _tfopen fopen
	#define _stat stat
	#define _tstat stat
	#define _tremove remove
	#define _trename rename

#endif

/// Beginning of the format string >>>
/// 12:31 15:05:49:547 [A5F:E67] Trace message <filename.cpp:120>
const char TRACE_FORMAT_PRE_STRING[] = "%02d:%02d %02d:%02d:%02d:%03d [%X:%X] ";
const int TRACE_FORMAT_PRE_STRING_LEN = 42; // in characters + some reserve space

/// Beginning of the format string >>>
// 15:05:49:547 [A5F:E67] Memory dump of "CType", address 0x120A, size 16b (0x10) <filename.cpp:120>
const char TRACE_FORMAT_DUMP_STRING[] = "Memory dump of \"%s\", address 0x%X, size %db (0x%X)";
const int TRACE_FORMAT_DUMP_STRING_LEN = Q_MAX_PATH; // in characters + some reserve space

/// End of the format string with file name and line
const Q_TCHAR TRACE_FORMAT_POST_STRING[] = _T(" <%s:%d>");

/// Timeout to wait for the trace device to be unlocked - default value in msec.
const unsigned long DEFAULT_TIMEOUT = 500;

/// Key under HKLM where all error logs are configured
const Q_TCHAR LOG_REGISTRY_KEY[] = _T("Software\\") _T(QAFDEBUG_COMPANY_NAME) _T("\\Log\\");


/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Device class
/////////////////////////////////////////////////////////////////////////////////////////////

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

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Record class
/////////////////////////////////////////////////////////////////////////////////////////////

/// Output the current trace record to the trace device
void QTrace::Record::DoTrace()
{
	if( Q_ASSERT( NULL != m_pDevice ) )
	{
		m_pDevice->DoTrace( *this );
		m_bTraced = true;
	}
}

/// Write count of bytes to the trace device (wrapper for the Device method)
bool QTrace::Record::Write( const void * const pBuffer,
	const unsigned long dwBufferSize, unsigned long & dwBytesWrittenRet )
{
	if( Q_ASSERT( NULL != m_pDevice ) )
		return m_pDevice->Write( pBuffer, dwBufferSize, dwBytesWrittenRet );
	else
		return false;
}

/// 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.
unsigned long QTrace::Record::TraceRecord( const TraceStage stage, bool & bFinishedRet )
{
	unsigned long dwWritten = 0;
	if( TRACE_HEADER == stage )
	{
		// str() calls freeze(true), we must unfreeze it back to prevent a memory leak
		Q_ASSERT( Write( m_Buffer.str().c_str(), m_Buffer.str().length(), dwWritten ) );
		//#ifndef WIN32
		//	m_Buffer.freeze( false );
		//#endif
		Q_ASSERT( dwWritten == m_Buffer.str().length() );
		bFinishedRet = true;
	}
	return dwWritten;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// QAFTrace::Log class
/////////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Dump class
/////////////////////////////////////////////////////////////////////////////////////////////

/// This function does the actual work of writting to the given log
unsigned long QTrace::Dump::TraceRecord( const TraceStage stage, bool & bFinishedRet )
{
	unsigned long dwWritten = 0;
	if( TRACE_HEADER == stage )
	{
		// prepare the dump message string
		char szDumpMessage[TRACE_FORMAT_DUMP_STRING_LEN] = { 0 };
		#ifdef _UNICODE
			CQWideCharToMultiByte conv( m_szTypeName, CP_ACP );
			#if defined(WIN32) && (_MSC_VER >= 1400)
				sprintf_s( szDumpMessage, TRACE_FORMAT_DUMP_STRING_LEN, TRACE_FORMAT_DUMP_STRING, 
					conv.buffer(), m_pBuf, m_ulBufSize, m_ulBufSize  );
			#else
				sprintf( szDumpMessage, TRACE_FORMAT_DUMP_STRING, conv.buffer(),
					m_pBuf, m_ulBufSize, m_ulBufSize  );
			#endif
		#else
			#if defined(WIN32) && (_MSC_VER >= 1400)
				_stprintf_s( szDumpMessage, TRACE_FORMAT_DUMP_STRING_LEN, TRACE_FORMAT_DUMP_STRING, 
					m_szTypeName, m_pBuf, m_ulBufSize, m_ulBufSize );
			#else
				_stprintf( szDumpMessage, TRACE_FORMAT_DUMP_STRING, m_szTypeName, 
					m_pBuf, m_ulBufSize, m_ulBufSize );
			#endif
		#endif
		// write the complete trace message
		Write( szDumpMessage, strlen(szDumpMessage), dwWritten );
		bFinishedRet = false;
	}
	if( TRACE_BODY == stage )
	{
		// write the buffer to the file
		const unsigned short BUF_LEN = 20;
		char szDumpMessage[BUF_LEN] = { 0 };
		const unsigned char * pPos = (const unsigned char *) m_pBuf;
		const unsigned char * pEnd = pPos + m_ulBufSize;
		while( pPos < pEnd )
		{
			szDumpMessage[0] = 0;
			#if defined(WIN32) && (_MSC_VER >= 1400)
				sprintf_s( szDumpMessage, BUF_LEN, "%08X  ", pPos );
			#else
				sprintf( szDumpMessage, "%08X  ", pPos );
			#endif
			unsigned long dwBytes = 0;
			Write( szDumpMessage, strlen(szDumpMessage), dwBytes );
			dwWritten += dwBytes;
			for( int i = 0; i < 16; i++ )
			{
				if( (pPos + i) < pEnd )
				{
					szDumpMessage[0] = 0;
					unsigned char uc = ((unsigned char *)pPos)[i];
					#if defined(WIN32) && (_MSC_VER >= 1400)
						sprintf_s( szDumpMessage, 4, "%02X ", uc );
					#else
						sprintf( szDumpMessage, "%02X ", uc );
					#endif
					Write( szDumpMessage, 3, dwBytes );
					dwWritten += dwBytes;
				}
				else
				{
					Write( "   ", 3, dwBytes );
					dwWritten += dwBytes;
				}
				if( 7 == i )
				{
					Write( " ", 1, dwBytes );
					dwWritten += dwBytes;
				}
			}
			Write( " ", 1, dwBytes );
			dwWritten += dwBytes;
			for( int j = 0; j < 16; j++ )
			{
				if( (pPos + j) >= pEnd )
					break;
				if( (pPos[j] > 0x20) && (pPos[j] < 0x80) )
					Write( pPos + j, 1, dwBytes );
				else
					Write( ".", 1, dwBytes );
				dwWritten += dwBytes;
			}
			Write( EOL, 2, dwBytes );
			dwWritten += dwBytes;
			pPos += 16;
		}
		bFinishedRet = true;
	}
	return dwWritten;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// 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.
/// Be careful with this class since it cannot detect ASCII and UNICODE strings.
/// It will assume it works with strings of TCHAR.
QTrace::Format::Format( Q_LPCTSTR szFormat, ... ) : m_szBuffer(NULL), m_szConverted(NULL)
{
	try
	{
		int nBufSize = Q_MAX_PATH;
		va_list args;
		while( NULL == m_szBuffer )
		{
			m_szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * nBufSize );
			// try to print the string to the buffer
			va_start( args, szFormat );
			#ifndef _vsntprintf
				#define _vsntprintf vsnprintf
			#endif
			#if defined(WIN32) && (_MSC_VER >= 1400)
				int iRet = _vsntprintf_s( m_szBuffer, nBufSize, nBufSize - 1, szFormat, args );
			#else
				int iRet = _vsntprintf( m_szBuffer, nBufSize - 1, szFormat, args );
			#endif
			va_end( args );
			// if the buffer is too small, make its size twice larger and repeat
			if( iRet < 0 )
			{
				free( m_szBuffer );
				m_szBuffer = NULL;
				nBufSize *= 2;
			}
		}
	}
	catch(...)
	{
		if( NULL != m_szBuffer )
		{
			free( m_szBuffer );
			m_szBuffer = NULL;
		}
	}
}

QTrace::Format::~Format()
{
	if( NULL != m_szConverted )
	{
		free( m_szConverted );
		m_szConverted = NULL;
	}
	if( NULL != m_szBuffer )
	{
		free( m_szBuffer );
		m_szBuffer = NULL;
	}
}

Q_LPCSTR QTrace::Format::GetTextA()
{
	if( Q_INVALID( NULL == m_szBuffer ) )
		return NULL;
#ifdef _UNICODE
	QTrace::CQWideCharToMultiByte conv( m_szBuffer, CP_ACP );
	m_szConverted = const_cast<Q_LPSTR>(conv.Detach());
	return m_szConverted;
#else
	return m_szBuffer;
#endif
}

#ifdef WIN32
Q_LPCWSTR QTrace::Format::GetTextW()
{
	if( Q_INVALID( NULL == m_szBuffer ) )
		return NULL;
#ifdef _UNICODE
	return m_szBuffer;
#else
	return NULL;
#endif
}
#endif


/// Linux and Windows class for getting the current time up to msec
class CSysTime
{
public:

	/// Stores msec value of the last call to Now() or the class instance creation.
	unsigned int msec;

	/// Stores sec value of the last call to Now() or the class instance creation.
	unsigned int sec;

	/// Stores min value of the last call to Now() or the class instance creation.
	unsigned int min;

	/// Stores hour value of the last call to Now() or the class instance creation.
	unsigned int hour;

	/// Stores day value of the last call to Now() or the class instance creation.
	unsigned int day;

	/// Stores month value of the last call to Now() or the class instance creation.
	unsigned int month;

	/// Stores year value of the last call to Now() or the class instance creation.
	unsigned int year;

	/// Default constructor. It calculates and stores the time of the class instance creation.
	CSysTime()
	{
		Now();
	}

	/// This function calculates and stores the current time.
	void Now()
	{
		#ifdef WIN32 /// Windows-specific implementation
			SYSTEMTIME st;
			GetLocalTime( &st );
			msec = st.wMilliseconds;
			sec = st.wSecond;
			min = st.wMinute;
			hour = st.wHour;
			day = st.wDay;
			month = st.wMonth;
			year = st.wYear;
		#else /// Linux-specific implementation
			struct timeval tp; // time with msec
			time_t tmt;        // time for broking
			struct tm tb;      // broken time
			gettimeofday( &tp, NULL );
			tmt = time( NULL );
			localtime_r( &tmt, &tb );
			msec = tp.tv_usec / 1000; // from microseconds to msec
			sec = tb.tm_sec;
			min = tb.tm_min;
			hour = tb.tm_hour;
			day = tb.tm_mday;
			month = tb.tm_mon;
			year = tb.tm_year + 1900; // They should count from 1970 but use 1900 instead
		#endif
	}

protected:
private:

	/// Copy constructor - disabled
	CSysTime( const CSysTime & obj )
	{
		assert( false );
	}

	/// Assignment operator - disabled
	CSysTime & operator=( const CSysTime & obj )
	{
		assert( false );
		return *this;
	}
};

/// Portable function that returns process id
inline unsigned long get_process_id()
{
	#ifdef WIN32
		return GetCurrentProcessId();
	#else
		return getpid();
	#endif
}

/// Portable function that returns process id
inline unsigned long get_thread_id()
{
	#ifdef WIN32
		return GetCurrentThreadId();
	#else
		return 0;
	#endif
}

/// Helper class for rich formatting of integers
/// 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.
QTrace::Num::Num( const signed long lValue, const unsigned int uiFixedWidth,
	const Q_TCHAR bPadChar /*= ' '*/, const bool bLeftAlign /*= false*/ )
{
	const unsigned short BUF_LEN = 12;
	m_szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (uiFixedWidth + BUF_LEN) );
	if( Q_INVALID( NULL == m_szBuffer ) )
		return;
	m_szBuffer[0] = 0;
	Q_TCHAR szTemp[BUF_LEN] = { 0 };
	#if defined(WIN32) && (_MSC_VER >= 1400)
		int iLen = _stprintf_s( szTemp, BUF_LEN, _T("%d"), lValue );
	#else
		int iLen = _stprintf( szTemp, _T("%d"), lValue );
	#endif
	if( iLen < (signed int)uiFixedWidth )
	{
		if( bLeftAlign )
		{
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[iLen + i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
		}
		else
		{
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
		}
	}
	else
		_tcscpy_s( m_szBuffer,uiFixedWidth + BUF_LEN, szTemp );
}

/// Helper class for rich formatting of integers in hex format
/// By default, the hex value will not be padded with zeros,
/// but you may change it. Align has no meaning here since
QTrace::Hex::Hex( const unsigned long ulValue, const unsigned int uiFixedWidth /*= 0*/,
	const Q_TCHAR bPadChar /*= '0'*/, const bool bLeftAlign /*= false*/ )
{
	Convert( ulValue, uiFixedWidth, bPadChar, bLeftAlign );
}

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

/// By default, the hex value will not be padded with zeros,
/// but you may change it. Align has no meaning here since
void QTrace::Hex::Convert( const unsigned long ulValue, const unsigned int uiFixedWidth,
	const Q_TCHAR bPadChar, const bool bLeftAlign )
{
	const unsigned short BUF_LEN = 12;
	m_szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (uiFixedWidth + BUF_LEN) );
	if( Q_INVALID( NULL == m_szBuffer ) )
		return;
	m_szBuffer[0] = 0;
	Q_TCHAR szTemp[BUF_LEN] = { 0 };
	#if defined(WIN32) && (_MSC_VER >= 1400)
		int iLen = _stprintf_s( szTemp, BUF_LEN, _T("%X"), ulValue );
	#else
		int iLen = _stprintf( szTemp, _T("%X"), ulValue );
	#endif
	if( iLen < (signed int)uiFixedWidth )
	{
		if( bLeftAlign )
		{
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[iLen + i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
		}
		else
		{
			for( int i = 0; i < (signed int)uiFixedWidth - iLen; i++ )
				m_szBuffer[i] = bPadChar;
			m_szBuffer[uiFixedWidth] = 0;
			_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
		}
	}
	else
		_tcscpy_s( m_szBuffer, uiFixedWidth + BUF_LEN, szTemp );
}

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::Device class
/////////////////////////////////////////////////////////////////////////////////////////////

QTrace::Device::Device( Q_LPCTSTR szID, const bool bNeedMutex,
	const QTrace::TraceLevel nDefaultTraceLevel )
	: m_hMutex(NULL), m_szID(NULL), m_nDefaultTraceLevel(nDefaultTraceLevel),
	m_dwWaitTimeout(DEFAULT_TIMEOUT), m_dwEnabledTraceLevel( QTrace::TraceNone )
{
	/// cannot continue without the correct ID
	if( Q_INVALID( NULL == szID ) && Q_INVALID( 0 == *szID ) )
		return;
	int iLen = _tcslen(szID);
	m_szID = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (iLen + 1) );
	if( Q_INVALID( NULL == m_szID ) )
		return;
	_tcscpy_s( m_szID, iLen + 1, szID );
	/// Create the mutex if needed
	if( bNeedMutex )
	{
		#ifdef WIN32
		Q_LPTSTR szBuffer = (Q_LPTSTR) malloc( sizeof(Q_TCHAR) * (iLen + 10) ); // for additional suffix
		if( Q_ASSERT( NULL != szBuffer ) )
		{
			szBuffer[0] = 0;
			_tcscpy_s( szBuffer, iLen + 10, szID );
			_tcscat_s( szBuffer, iLen + 10, _T("_Mtx123") );
			m_hMutex = CreateMutex( NULL, FALSE, szBuffer );
			Q_ASSERT( NULL != m_hMutex );
			free( szBuffer );
		}
		#endif
	}
	// initialize the trace log control registry key
	#ifdef WIN32
		CRegDWORD dwRead( HKLM, LOG_REGISTRY_KEY, szID, TraceNone, REG::RF_READONLY );
		m_dwEnabledTraceLevel = dwRead;
	#else
		// Replace dots with underscores in order to get the environment variable
		Q_TCHAR * szBuf = (Q_TCHAR *)malloc( sizeof(Q_TCHAR) * (iLen + 1) );
		if( NULL != szBuf )
		{
			_tcscpy( szBuf, szID );
			for( char * pch = szBuf; *pch != 0; pch++ )
			{
				if( !isalnum( *pch ) )
					*pch = '_';
			}
			m_dwEnabledTraceLevel = 0;
			char * szVal = getenv( szBuf );
			if( NULL != szVal )
				m_dwEnabledTraceLevel = atoi( szVal );
			free( szBuf ); 
		}
	#endif
}

QTrace::Device::~Device()
{
	if( NULL != m_szID )
	{
		free( m_szID );
		m_szID = NULL;
	}
	if( NULL != m_hMutex )
	{
		#ifdef WIN32
		ReleaseMutex( m_hMutex );
		CloseHandle( m_hMutex );
		#endif
		m_hMutex = NULL;
	}
}

//
void QTrace::Device::DoTrace( QTrace::Record & obj )
{
	/// Protect the device object from cross-thread usage
	/// It is not connected to the mutex since the mutex is not always requested
	CAutoLockCS sync( m_cs );
	// check that the record is supposed to be written
	if( obj.IsEmpty() )
		return;
	// lock the device
	if( ! Lock() )
		return;
	// open the device
	if( ! OpenTraceDevice() )
	{
		Unlock();
		return;
	}
	// write the trace record with the prefix and postfix
	unsigned long dwBytesWritten = WritePrefix( obj, 0 );
	bool bFinished = false;
	dwBytesWritten += obj.TraceRecord( QTrace::Record::TRACE_HEADER, bFinished );
	WritePostfix( obj, dwBytesWritten );
	if( ! bFinished )
		obj.TraceRecord( QTrace::Record::TRACE_BODY, bFinished );
	// close everything
	CloseTraceDevice();
	Unlock();
}

///
bool QTrace::Device::Lock()
{
	#ifdef WIN32
	if( NULL == m_hMutex )
		return false;
	unsigned long dwRet = WaitForSingleObject( m_hMutex, m_dwWaitTimeout );
	return Q_CHECK( WAIT_OBJECT_0, dwRet );
	#else
	return true;
	#endif
}

///
void QTrace::Device::Unlock()
{
	#ifdef WIN32
	if( NULL != m_hMutex )
		Q_ASSERT( ReleaseMutex( m_hMutex ) );
	#endif
}

///
unsigned long QTrace::Device::WritePrefix( Record & record, const unsigned long dwBytesWritten )
{
	// Prepare parameters for the complete error message
	CSysTime st;
	unsigned long dwProcess = get_process_id(), dwThread = get_thread_id();
	// format the string
	char szPreString[TRACE_FORMAT_PRE_STRING_LEN] = { 0 };
	#if defined(WIN32) && (_MSC_VER >= 1400)
		sprintf_s( szPreString, TRACE_FORMAT_PRE_STRING_LEN, TRACE_FORMAT_PRE_STRING, 
			st.month, st.day, st.hour, st.min, st.sec, st.msec, dwProcess, dwThread );
	#else
		sprintf( szPreString, TRACE_FORMAT_PRE_STRING, st.month, st.day, st.hour,
			st.min, st.sec, st.msec, dwProcess, dwThread );
	#endif
	// write the beginning of the trace message
	unsigned long dwWritten = 0;
	Write( szPreString, strlen(szPreString), dwWritten );
	return dwWritten;
}

///
unsigned long QTrace::Device::WritePostfix( Record & record, const unsigned long dwBytesWritten )
{
	unsigned long dwRet = 0;
	unsigned long dwBytes = 0;
	if( NULL != record.GetLocationFile() )
	{
		const char * szFileName = record.GetLocationFile();
		const unsigned short BUF_LEN = 16;
		char szTemp[BUF_LEN] = { 0 }; // for all games with cars
		// write iSpaces space characters (if iSpaces > 0)
		int nSpaces = (unsigned long)GetFarRightColumn() - dwBytesWritten;
		for( int i = 0; i < nSpaces; )
		{
			int iNum = nSpaces - i;
			iNum = iNum < 15 ? iNum : 15;
			memset( szTemp, ' ', iNum );
			szTemp[iNum] = 0;
			Write( szTemp, iNum, dwBytes );
			dwRet += dwBytes;
			i += iNum;
		}
		// write the delimiter, filename, line number and the end of the line
		Write( " <== ", 5, dwBytes );
		dwRet += dwBytes;
		Write( szFileName, strlen(szFileName), dwBytes );
		dwRet += dwBytes;
		#if defined(WIN32) && (_MSC_VER >= 1400)
			sprintf_s( szTemp, BUF_LEN, "(%d)" EOL, record.GetLocationLine() );
		#else
			sprintf( szTemp, "(%d)" EOL, record.GetLocationLine() );
		#endif
		Write( szTemp, strlen(szTemp), dwBytes );
		dwRet += dwBytes;
	}
	else
	{
		Write( EOL, 2, dwBytes );
		dwRet += dwBytes;
	}
	return dwRet;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// QTrace::LogFile class
/////////////////////////////////////////////////////////////////////////////////////////////

QTrace::LogFile::LogFile( Q_LPCTSTR szFileName, const QTrace::TraceLevel nDefaultTraceLevel,
	const int nMaxLogFileSize )
	: QTrace::Device( szFileName, true, nDefaultTraceLevel ), m_hFile(NULL),
	m_nMaxLogFileSize( nMaxLogFileSize )
{
	// the file name must be specified
	if( Q_INVALID( NULL == szFileName ) || Q_INVALID( 0 == *szFileName ) )
		return;
	m_szFilename[0] = 0;
	if( QAFDebug::GetLogDir( m_szFilename, Q_MAX_PATH - 1 ) > 0 )
	{
		Q_LPCTSTR lpszPos = _tcsrchr( szFileName, PATH_DELIMITER );
		if( NULL != lpszPos )
		{
			// I need to create subdirs here
		}
		else
			_tcscat_s( m_szFilename, Q_MAX_PATH, szFileName );
	}
	// Get the log file size from the registry key.
	// Use "<filename>.size" as the value name.
	const unsigned long MIN_LOG_SIZE = 1024;               // 1 Kb
	const unsigned long DEFAULT_LOG_SIZE = 1024 * 1024;    // 1 Mb
	const unsigned long MAX_LOG_SIZE = 1024 * 1024 * 1024; // 1 Gb
	#define MAKE_LOG_SIZE( x ) ((x) < MIN_LOG_SIZE ? MIN_LOG_SIZE : ((x) > MAX_LOG_SIZE ? MAX_LOG_SIZE : (x)))
	if( _tcslen( szFileName ) < (Q_MAX_PATH - 6) )
	{
		Q_TCHAR szTemp[Q_MAX_PATH] = { 0 };
		_tcscpy_s( szTemp, Q_MAX_PATH, szFileName );
		_tcscat_s( szTemp, Q_MAX_PATH, _T(".size") );
		#ifdef WIN32
			CRegDWORD dwRead( HKLM, LOG_REGISTRY_KEY, szTemp, nMaxLogFileSize, REG::RF_READONLY );
			m_nMaxLogFileSize = dwRead;
		#else
			char * szVal = getenv( szTemp );
			if( NULL != szVal )
				m_nMaxLogFileSize = atoi( szVal );
		#endif
		m_nMaxLogFileSize = MAKE_LOG_SIZE( m_nMaxLogFileSize );
	}
	else
		m_nMaxLogFileSize = MAKE_LOG_SIZE( nMaxLogFileSize );
	#undef MAKE_LOG_SIZE
}

///
QTrace::LogFile::~LogFile()
{
	m_nMaxLogFileSize = 0;
}

//
bool QTrace::LogFile::Write( const void * const pBuffer, const unsigned long dwBufferSize, unsigned long & dwBytesWrittenRet )
{
	dwBytesWrittenRet = 0;
	if( Q_INVALID( NULL == pBuffer ) || Q_INVALID( 0 == dwBufferSize ) )
		return false;
	if( Q_INVALID( NULL == m_hFile ) )
		return false;
#ifndef WIN32
	struct flock fl;
	memset( &fl, 0, sizeof(fl) );
	fl.l_type = F_WRLCK;
	fl.l_whence = SEEK_END;
	int fid = fileno( m_hFile );
	if( Q_ASSERT( fid >= 0 ) )
	{
		if( Q_ASSERT( -1 != fcntl( fid, F_SETLKW, &fl ) ) )
		{
#endif
			Q_CHECK( 0, fseek( m_hFile, 0, SEEK_END ) );
			dwBytesWrittenRet = fwrite( pBuffer, 1, dwBufferSize, m_hFile ); // and I need it in bytes
			Q_CHECK( dwBufferSize, dwBytesWrittenRet );
			Q_CHECK( 0, fflush( m_hFile ) );
#ifndef WIN32
			memset( &fl, 0, sizeof(fl) );
			fl.l_type = F_UNLCK;
			fl.l_whence = SEEK_END;
			Q_ASSERT( -1 != fcntl( fid, F_SETLK, &fl ) );
		}
	}
#endif
	return (dwBufferSize == dwBytesWrittenRet);
}

/// open a new trace file
bool QTrace::LogFile::OpenTraceDevice()
{
	bool bAlreadyTried = false;
	// Close the trace file if necessary
	if( Q_INVALID( (NULL != m_hFile) ) )
	{
		Q_ASSERT( 0 == fclose( m_hFile ) );
		m_hFile = NULL;
	}
	while( true )
	{
		// Open the trace file
		int nRet = _tfopen_s( &m_hFile, m_szFilename, _T("a+b") );
		if( !Q_CHECK( 0, nRet ) || Q_INVALID( NULL == m_hFile ) )
			return false;
		struct _stat buf;
		nRet = _tstat( m_szFilename, &buf );
		if( bAlreadyTried || ((0 == nRet) && ((m_nMaxLogFileSize / 2) > buf.st_size)) )
			break;
		// if the file is large than the max size, copy it to save and create new
		if( Q_INVALID( 0 != fclose( m_hFile ) ) )
			return false;
		Q_TCHAR szCopyFilename[Q_MAX_PATH] = { 0 };
		_tcscpy_s( szCopyFilename, Q_MAX_PATH, m_szFilename );
		Q_LPTSTR szPos = _tcsrchr( szCopyFilename, _T('.') );
		if( Q_INVALID( NULL == szPos ) )
			return false;
		szPos[0] = 0;
		_tcscat_s( szPos, Q_MAX_PATH, _T(".old.log") );
		_tremove( szCopyFilename ); // ignore the error if the file does not exist
		if( Q_INVALID( 0 != _trename( m_szFilename, szCopyFilename ) ) )
			return false;
		bAlreadyTried = true;
	}
	return true;
}

/// close the current trace file
void QTrace::LogFile::CloseTraceDevice()
{
	if( NULL != m_hFile )
	{
		Q_CHECK( 0, fclose( m_hFile ) );
		m_hFile = NULL;
	}
}

/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!
#endif // #if !defined(DISABLE_Q_TRACE)
/// This file will be compiled only if DISABLE_Q_TRACE is not defined!!!

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