//////////////////////////////////////////////////////////////////////////
/// @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!!!