2025-04-27 07:49:33 -04:00

614 lines
23 KiB
C++

/////////////////////////////////////////////////////////////////////////////
//File : TSLog.cpp Thread safe logger class
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "TSLog.h"
#include <comdef.h>
_TCHAR g_szLogFileName[MAX_PATH];
BOOL g_bVerboseModeOn = FALSE;
/////////////////////////////////////////////////////////////////////////////
// Function name : AddStringToString
// Description : add a "," then concat szIn to the end of szStr
// if either string pointer is null, just return
// Return type : void
// Argument : char *szIn
// Argument : char * szStr
// Argument : long lMaxLen
/////////////////////////////////////////////////////////////////////////////
void AddStringToString(char *szIn, char * szStr, long lMaxLen)
{
if (!g_bVerboseModeOn)
return;
if ( (NULL == szIn) || (NULL ==szStr) )
return;
// default is blank
char szTemp[1024] = ", ";
if ( NULL != szIn )
{
// 1 is already used for the ',' and 1 for the terminating \0
strncpy(szTemp + 1, szIn, sizeof(szTemp) - 2);
}
//make sure not to overflow the string
long lStrLen = strlen(szStr);
if ( lStrLen < lMaxLen )
strncat(szStr, szTemp, (lMaxLen - lStrLen) );
}
/////////////////////////////////////////////////////////////////////////////
// Function name : AddBSTRAsString
// Description : Append a "," and then the value of the BSTR
// (as a string) to the end of the given string
// Return type : void
// Argument : BSTR bsIn
// Argument : char * szStr
// Argument : long lMaxLen
/////////////////////////////////////////////////////////////////////////////
void AddBSTRAsString(BSTR bsIn, char * szStr, long lMaxLen)
{
if (!g_bVerboseModeOn)
return;
// init witl blank for empty str
char szTemp[1024] = ", ";
if ( NULL == szStr )
return;
if ( NULL != bsIn )
{
// replace blank with bsIn
strncpy(szTemp + 1, (char *) _bstr_t(bsIn), sizeof(szTemp) - 2);
}
//make sure not to overflow the string
long lStrLen = strlen(szStr);
if ( lStrLen < lMaxLen )
strncat(szStr, szTemp, (lMaxLen - lStrLen) );
}
/////////////////////////////////////////////////////////////////////////////
// Function name : AddDoubleAsString
// Description : Append a "," and then the value of the double
// (as a string) to the end of the given string
// Return type : void
// Argument : double dIn
// Argument : char * szStr
// Argument : long lMaxLen
/////////////////////////////////////////////////////////////////////////////
void AddDoubleAsString(double dIn, char * szStr, long lMaxLen)
{
if (!g_bVerboseModeOn)
return;
if ( NULL == szStr )
return;
char szTemp[1024] = "";
sprintf(szTemp,",%.4f", dIn);
//make sure not to overflow the string
long lStrLen = strlen(szStr);
if ( lStrLen < lMaxLen )
strncat(szStr, szTemp, (lMaxLen - lStrLen) );
}
/////////////////////////////////////////////////////////////////////////////
// Function name : AddULAsString
// Description : Append a "," and then the value of the unsigned long
// (as a string) to the end of the given string
// Return type : void
// Argument : unsigned long lIn
// Argument : char * szStr
// Argument : long lMaxLen
/////////////////////////////////////////////////////////////////////////////
void AddULAsString(unsigned long lIn, char * szStr, long lMaxLen)
{
if (!g_bVerboseModeOn)
return;
if ( NULL == szStr )
return;
char szTemp[1024] = "";
sprintf(szTemp,",%lu", lIn);
//make sure not to overflow the string
long lStrLen = strlen(szStr);
if ( lStrLen < lMaxLen )
strncat(szStr, szTemp, (lMaxLen - lStrLen) );
}
/////////////////////////////////////////////////////////////////////////////
// Function name : AddLongAsString
// Description : Append a "," and then the value of the long
// (as a string) to the end of the given string
// Return type : void
// Argument : long lIn
// Argument : char * szStr
// Argument : long lMaxLen
/////////////////////////////////////////////////////////////////////////////
void AddLongAsString(long lIn, char * szStr, long lMaxLen)
{
if (!g_bVerboseModeOn)
return;
if ( NULL == szStr )
return;
char szTemp[1024] = "";
sprintf(szTemp,",%ld", lIn);
//make sure not to overflow the string
long lStrLen = strlen(szStr);
if ( lStrLen < lMaxLen )
strncat(szStr, szTemp, (lMaxLen - lStrLen) );
}
/////////////////////////////////////////////////////////////////////////////
// Function name : AddVariantBoolAsString
// Description : Append a "," and then the value of the VARIANT_BOOL
// (as a string) to the end of the given string
// Return type : void
// Argument : VARIANT_BOOL vIn
// Argument : char * szStr
// Argument : long lMaxLen
/////////////////////////////////////////////////////////////////////////////
void AddVariantBoolAsString(VARIANT_BOOL vIn, char * szStr, long lMaxLen)
{
if (!g_bVerboseModeOn)
return;
if ( NULL == szStr )
return;
char szTemp[1024];
if ( 0 == vIn )
strcpy(szTemp,",FALSE");
else
strcpy(szTemp,",TRUE");
//make sure not to overflow the string
long lStrLen = strlen(szStr);
if ( lStrLen < lMaxLen )
strncat(szStr, szTemp, (lMaxLen - lStrLen) );
}
/////////////////////////////////////////////////////////////////////////////
// Function name : AddVariantAsString
// Description : Append a "," and then the value of the variant
// (as a string) to the end of the given string
// Return type : void
// Argument : VARIANT vIn
// Argument : char * szStr
// Argument : long lMaxLen
/////////////////////////////////////////////////////////////////////////////
void AddVariantAsString(VARIANT vIn, char * szStr, long lMaxLen)
{
if (!g_bVerboseModeOn)
return;
if ( NULL == szStr )
return;
char szTemp[1024] = ", ", *pszVal = szTemp + 1;
switch ( vIn.vt )
{
case VT_UI1:
sprintf(pszVal,"%u", vIn.bVal);
break;
case VT_UI1 | VT_BYREF:
sprintf(pszVal,"%u", *vIn.pbVal);
break;
case VT_I2:
sprintf(pszVal,"%d", vIn.iVal);
break;
case VT_I4:
sprintf(pszVal,"%ld", vIn.lVal);
break;
case VT_I2 | VT_BYREF:
sprintf(pszVal,"%d", *vIn.piVal);
break;
case VT_I4 | VT_BYREF:
sprintf(pszVal,"%ld", *vIn.plVal);
break;
case VT_BOOL:
if ( 0 == vIn.boolVal )
sprintf(pszVal,"FALSE");
else
sprintf(pszVal,"TRUE");
break;
case VT_BOOL | VT_BYREF:
if ( 0 == *vIn.pboolVal )
sprintf(pszVal,"FALSE");
else
sprintf(pszVal,"TRUE");
break;
case VT_BSTR:
strncpy(pszVal, _bstr_t(vIn.bstrVal), sizeof(szTemp) - 2);
break;
case VT_BSTR | VT_BYREF:
strncpy(pszVal, _bstr_t(*vIn.pbstrVal), sizeof(szTemp) - 2);
break;
case VT_R4:
sprintf(pszVal,"%.4f", vIn.fltVal);
break;
case VT_R8:
sprintf(pszVal,"%.4f", vIn.dblVal);
break;
case VT_R4 | VT_BYREF:
sprintf(pszVal,"%.4f", *vIn.pfltVal);
break;
case VT_R8 | VT_BYREF:
sprintf(pszVal,"%.4f", *vIn.pdblVal);
break;
default:
break;
}//switch ( vIn.vt )
//make sure not to overflow the string
long lStrLen = strlen(szStr);
if ( lStrLen < lMaxLen )
strncat(szStr, szTemp, (lMaxLen - lStrLen) );
}
/////////////////////////////////////////////////////////////////////////////
// CTSLog
/////////////////////////////////////////////////////////////////////////////
// Function name : CTSLog::CTSLog
// Description : Constructor
// Return type : NA
// Argument : void
/////////////////////////////////////////////////////////////////////////////
CTSLog::CTSLog( void )
{
m_bInit = FALSE;
m_bQuit = FALSE;
m_bVerboseModeOn = FALSE;
m_ulChildThread = 0;
m_uThreadAddr = 0;
m_ulNumPoolStrings = 0;
m_hPoolSemaphore = NULL;
m_hQSemaphore = NULL;
m_hFileSemaphore = NULL;
//JVP 2/23/2000 - let's start off with a valid name
_tcscpy(g_szLogFileName, kszLogFileName);
}
/////////////////////////////////////////////////////////////////////////////
// Function name : CTSLog::~CTSLog
// Description : Destructor
// Return type : NA
// Argument : void
/////////////////////////////////////////////////////////////////////////////
CTSLog::~CTSLog( void )
{
//make sure that we shutdown everything properly
Close();
}
/////////////////////////////////////////////////////////////////////////////
// Function name : CTSLog::Init
// Description : Initialize the logger class, creates semaphores, starts
// the logger thread, allocates strings for the
// buffer pool
// Return type : BOOL
// Argument : _TCHAR * pFileName
// Argument : int nPriority
/////////////////////////////////////////////////////////////////////////////
BOOL CTSLog::Init(_TCHAR * pFileName, int nPriority )
{
ULONG j;
//if verbose mode is not on, then check the registry
if ( FALSE == m_bVerboseModeOn )
{
m_bVerboseModeOn = IsRegistryVerboseSet();
g_bVerboseModeOn = m_bVerboseModeOn;
}
if ( TRUE == m_bInit )//let's only do this once
{
return TRUE;
}
SECURITY_ATTRIBUTES sa;
//security attributes for our shared file semaphore handle
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
_tzset();
m_bInit = FALSE;
//initial value is zero because we really want to wait till somebody puts
//something in the queue
m_hQSemaphore = CreateSemaphore( NULL, 0, LONG_MAX, NULL );
m_hPoolSemaphore = CreateSemaphore( NULL, 0, LONG_MAX, NULL );
//initial value is one because nobody is using the file when we start
m_hFileSemaphore = CreateSemaphore( &sa, 1, LONG_MAX, kszFileSemaphoreName );
InitializeCriticalSection( &m_outMutex );
InitializeCriticalSection( &m_poolMutex );
m_bQuit = FALSE;
m_ulChildThread = 0;
if ( (NULL == m_hQSemaphore) || (NULL == m_hFileSemaphore) || (NULL == m_hPoolSemaphore) )
goto INIT_CLEANUP_FAIL;
char * pStr;
m_ulNumPoolStrings = 0;
for ( j = 0; j < NUM_POOL_STRINGS ; j ++ )
{
pStr = (char*)malloc(LOG_STRING_LEN);
if ( NULL != pStr )
{
pushPool( pStr );
m_ulNumPoolStrings++;
}
}
//JVP - 2/29/2000
//need to do this here, because we need the file semaphore in GetLogfileName
_tcscpy(g_szLogFileName, kszLogFileName);
GetLogfileName();
//
// Lock the DLL until the thread exits.
//
_Module.Lock();
//
// Start thread.
//
m_bQuit = FALSE;
m_ulChildThread = (HANDLE) _beginthreadex( NULL, 0, LoggerThread , (void *) this , 0, &m_uThreadAddr);
if ( 0 == m_ulChildThread )//thread failed to start
goto INIT_CLEANUP_FAIL;
long lRetVal;
switch ( nPriority )
{
case THREAD_PRIORITY_ABOVE_NORMAL:
case THREAD_PRIORITY_BELOW_NORMAL:
case THREAD_PRIORITY_HIGHEST:
case THREAD_PRIORITY_IDLE:
case THREAD_PRIORITY_LOWEST:
case THREAD_PRIORITY_NORMAL:
case THREAD_PRIORITY_TIME_CRITICAL:
lRetVal = SetThreadPriority( (void *) m_ulChildThread, nPriority);
break;
default://if we were not given a valid priority use normal
lRetVal = SetThreadPriority( (void *) m_ulChildThread, THREAD_PRIORITY_NORMAL);
}//switch ( nPriority )
m_bInit = TRUE;
return TRUE;
INIT_CLEANUP_FAIL:
for ( j = 0; j < m_ulNumPoolStrings ; j ++ )
{
pStr = popPool(100);
if ( NULL != pStr )
free(pStr);
}
if ( m_hQSemaphore )
CloseHandle( m_hQSemaphore );
if ( m_hFileSemaphore )
CloseHandle( m_hFileSemaphore );
if ( m_hPoolSemaphore )
CloseHandle( m_hPoolSemaphore );
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// Function name : CTSLog::Close
// Description : close the logger class, make sure the thread dies off
// empty the output queue, delete the strings for the
// pool, close the semaphores, and mutexes
// Return type : void
/////////////////////////////////////////////////////////////////////////////
void CTSLog::Close()
{
ULONG j;
char * pStr = NULL;
if ( FALSE == m_bInit )
return;
if ( 0 == m_ulChildThread )//no thread
return;
//we'll signal the child thread that it's time to quit
m_bQuit = TRUE;
//we'll push an empty string onto the child thread's queue so that it will unblock
//and then it will check m_bQuit and then exit gracefully
pStr = popPool(100);
if ( NULL != pStr )
{
sprintf(pStr,"");
pushLogQueue( pStr );
}
//Let's set the child thread's priority up high and then give it a few cycles
//to finish whatever it has to do (Then if it doesn't complete we'll KILL it)
SetThreadPriority( (void *) m_ulChildThread, THREAD_PRIORITY_TIME_CRITICAL);
Sleep(100);
DWORD ExitCode ;
DWORD Error;
ULONG lCount = 0;
do
{
//if the child does not exit itself after a few tries, KILL IT
if ( lCount++ > 25 )
{
TerminateThread( (void *)m_ulChildThread , 1);
break;
}
if ( 0 == GetExitCodeThread( (void *)m_ulChildThread, &ExitCode ) )
Error = GetLastError();
if ( STILL_ACTIVE == ExitCode )
Sleep(100); //we do not want a tight loop so sleep for a while
}while ( STILL_ACTIVE == ExitCode );
m_ulChildThread = 0;
//get any strings that are left in the output queue
//and push them back into the pool
EnterCriticalSection( &m_outMutex );
lCount = m_outQueue.size();
for ( j=0 ; j < lCount ; j++)
{
pStr = m_outQueue.front();//get the string from the queue
m_outQueue.pop();//remove the item
pushPool ( pStr );
}
LeaveCriticalSection( &m_outMutex );
//empty out the pool of strings and delete them
//the popPool function uses the semaphore to
//synchronize the access to the pool
for ( j = 0; j < m_ulNumPoolStrings ; j ++ )
{
//we can't wait forever here, so set the timeout
pStr = popPool(1000);
if ( NULL != pStr )
free(pStr);
}
//cleanup the mutexes and semaphores
EnterCriticalSection( &m_outMutex );
CloseHandle( m_hQSemaphore );
LeaveCriticalSection( &m_outMutex );
DeleteCriticalSection( &m_outMutex );
EnterCriticalSection( &m_poolMutex );
CloseHandle( m_hPoolSemaphore );
LeaveCriticalSection( &m_poolMutex );
DeleteCriticalSection( &m_poolMutex );
//finally, set our init flag to false
m_bInit = FALSE;
//
// We're doing everything we need to do.
// Let's release the DLL lock we took in
// Init().
//
_Module.Unlock();
}
/////////////////////////////////////////////////////////////////////////////
// Function name : __stdcall CTSLog::LoggerThread
// Description : This is where the actual logging get's done.
// Return type : unsigned int
// Argument : void * pArg
/////////////////////////////////////////////////////////////////////////////
unsigned int __stdcall CTSLog::LoggerThread(void * pArg)
{
CTSLog *pParent = (CTSLog *) pArg;
char * pStr;
char szEOL[] = ",|\r\n";
DWORD dwRetVal;
unsigned long wWritten;
//wait until our parent sets the flag to tell us to quit
while ( FALSE == pParent->m_bQuit )
{
//we wait for something to be put into the output queue
dwRetVal = WaitForSingleObject( pParent->m_hQSemaphore, INFINITE );
if (WAIT_OBJECT_0 == dwRetVal )//OK we got something in the queue
{
//make sure nobody can access the queue while we get the
//first item
EnterCriticalSection( &pParent->m_outMutex );
pStr = pParent->m_outQueue.front();//get the string from the queue
pParent->m_outQueue.pop();//remove the item
LeaveCriticalSection( &pParent->m_outMutex );
if ( NULL != pStr )//OK now we can log to the file
{
HANDLE hFile = INVALID_HANDLE_VALUE;
//don't log the string if it is empty
//the parent puts an empty string in the queue to allow us to exit gracefully
if ( strlen(pStr) > 0 )
{
//we need to get access to the file semaphore before we attempt to open the file
//many copies of this thread may be running and this is how we synchronize the
//file access
dwRetVal = WaitForSingleObject( pParent->m_hFileSemaphore, INFINITE );
//NOTES:
//kszLogFileName - name of the file
//GENERIC_WRITE - Data can be written and the file pointer moved
//OPEN_ALWAYS - Opens the file if it exists, creates it if it does not
//kdwNoFileShare - the file cannot be shared. Subsequent open operations on the object will fail, until the handle is closed.
//File Attributes - FILE_ATTRIBUTE_NORMAL can binary or with FILE_FLAG_WRITE_THROUGH to get more robust writing at the expense of more disk thrashing
//Template file - NULL don't use it
hFile = CreateFile(g_szLogFileName, GENERIC_WRITE, kdwNoFileShare, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL /*| FILE_FLAG_WRITE_THROUGH*/ , NULL);
if ( INVALID_HANDLE_VALUE != hFile )
{
//go to the end of the file
SetFilePointer(hFile, 0 , NULL , FILE_END);
//write out the string
WriteFile(hFile, pStr, strlen(pStr), &wWritten, NULL);
//add a cariage return and line feed
WriteFile(hFile, szEOL, strlen(szEOL), &wWritten, NULL);
CloseHandle(hFile);
}
//ok we are done with the file, so let the next thread have access to it
ReleaseSemaphore( pParent->m_hFileSemaphore, 1, NULL );
}//if ( "" != pStr )
//always push the string back into the pool of strings
pParent->pushPool ( pStr );
}//if ( NULL != pStr )//OK now we can log to the file
}//if (WAIT_OBJECT_0 == dwRetVal )
}//while ( FALSE == pParent->m_bQuit )
_endthreadex(0);//cleanup
return 0;
}