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