/*=========================================================================== * * File: DL_Log.CPP * Author: Dave Humphrey (uesp@m0use.net) * Created On: Monday, April 02, 2001 * * Implements the CLogFile class for handling output to a simple log file * for debugging purposes. Note that this module should not use the * ErrorHandler for noting errors as the CErrorHandler class uses the * CLogFile class, causing possible infinite loops. * *=========================================================================*/ /* Include Files */ #include "dl_log.h" #include "dl_mem.h" #include <time.h> /*=========================================================================== * * Begin Global Variables * *=========================================================================*/ /* The main log file for debugging output */ CLogFile SystemLog; /*=========================================================================== * End of Global Variables *=========================================================================*/ /*=========================================================================== * * Begin Local Variables * *=========================================================================*/ DEFINE_FILE(); /*=========================================================================== * End of Local Variables *=========================================================================*/ #undef __FUNC__ #define __FUNC__ "CLogFile::CLogFile()" /*=========================================================================== * * Class CLogFile Constructor (Default) * *=========================================================================*/ CLogFile::CLogFile (void) { TabLevel = 0; pLogFileHandle = NULL; pHookProc = NULL; } /*=========================================================================== * End of Class CLogFile Constructor *=========================================================================*/ /*=========================================================================== * * Class CLogFile Constructor - CLogFile (pFilename, AppendFile); * * Attempts to open the given filename as the current logfile. Accepts * input as per the Open() class method. * *=========================================================================*/ CLogFile::CLogFile (const char* pFilename, const logmode_t AppendFile) { /* Initialize the class members */ TabLevel = 0; pLogFileHandle = NULL; pHookProc = NULL; /* Attempt to open log file */ Open(pFilename, AppendFile); } /*=========================================================================== * End of Class CLogFile Constructor *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean Close (void); * * Closes the log file, if currently open. Returns TRUE on success or FALSE * if no log file is currently open. * *=========================================================================*/ boolean CLogFile::Close (void) { //DEFINE_FUNCTION("CLogFile::Close()"); int Result; /* Is the log file currently open? */ if (!IsOpen()) return (FALSE); /* Output status messages to log file */ Printf ("Closing Log File..."); OutputMemoryStatus(); /* Close log file and reset parameters */ Result = fclose(pLogFileHandle); pLogFileHandle = NULL; TabLevel = 0; if (Result < 0) return (FALSE); return (TRUE); } /*=========================================================================== * End of Class Method CLogFile::Close() *=========================================================================*/ #if defined(_DEBUG) /*=========================================================================== * * Class CLogFile Method - void DebugPrintf (const char* pString, ...); * * Outputs a printf() formatted message to the log file but only in * debug builds. Compiles to a NULL inline function for release builds. * Otherwise is identical to the Printf() method. * *=========================================================================*/ void CLogFile::DebugPrintf (const char* pString, ...) { DEFINE_FUNCTION("CLogFile::DebugPrintf()"); va_list Args; /* Ensure valid input */ ASSERT(pString != NULL); /* Ensure the log file is currently open */ if (!IsOpen()) return; /* Print the line to the file */ va_start (Args, pString); PrintLine(pString, Args); va_end (Args); } /*=========================================================================== * End of Class Method CLogFile::DebugPrintf() *=========================================================================*/ #endif /*=========================================================================== * * Class CLogFile Method - void DecrementTabs (void) * * Decreases the current tab level by one if possible. The tab level * indicates how many tags preceed each line output to the log file, * allowing simple formatting of the output for easier viewing. * * See also: CLogFile::IncrementTabs(); * CLogFile::SetTabLevel(NewTabLevel); * *=========================================================================*/ void CLogFile::DecrementTabs (void) { if (TabLevel > 0) TabLevel--; } /*=========================================================================== * End of Class Method CLogFile::DecrementTabs() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - void IncrementTabs (void) * * Increases the current tab level by one if possible. The tab level * indicates how many tags preceed each line output to the log file, * allowing simple formatting of the output for easier viewing. * * See also: CLogFile::DecrementTags(); * CLogFile::SetTabLevel(NewTabLevel); * *=========================================================================*/ void CLogFile::IncrementTabs (void) { if (TabLevel < LOGFILE_MAX_TABS) TabLevel++; } /*=========================================================================== * End of Class Method CLogFile::IncrementTabs() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean Open (pFilename, AppendFile); * * Attempts to create a log file using the given filename. Any current * log file is closed. If the AppendFile flag is LOG_APPEND (the default * is LOG_OPEN) and the log file currently exists, log entries will be * appended to the end of the file. If AppendFile is FALSE, the log * file will be overwritten if it exists (the default). Returns TRUE * if the log file was successfully opened. * *=========================================================================*/ boolean CLogFile::Open (const char* pFilename, const logmode_t AppendFile) { DEFINE_FUNCTION("CLogFile::Open()"); boolean Result; /* Check for valid input */ ASSERT(pFilename != NULL); ASSERT(*pFilename != NULL_CHAR); /* Ensure the current log file is closed */ if (IsOpen()) { Result = Close(); if (!Result) return (FALSE); } /* Attempt to open file for output, depending on the append flag */ pLogFileHandle = fopen(pFilename, AppendFile ? "at" : "wt"); if (pLogFileHandle == NULL) return (FALSE); /* Output the filename and full date to log file */ Result = Printf ("==================== Opened Logfile %s ================", pFilename); if (!Result) return (FALSE); return (OutputDate()); } /*=========================================================================== * End of Class Method CLogFile::Open() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean OutputDate (void); * * Outputs the full date and time to the log file. Returns FALSE on any * error. * *=========================================================================*/ boolean CLogFile::OutputDate (void) { //DEFINE_FUNCTION("CLogFile::OutputDate()"); char DateString[33] = ""; struct tm* pCurrentTime; time_t Today; /* Ensure the log file is currently open */ if (!IsOpen()) return (FALSE); /* Get the current Date and convert it to local time */ time(&Today); pCurrentTime = localtime(&Today); if (pCurrentTime == NULL) return (FALSE); /* Output the date line to the file */ strftime(DateString, 32, "%A, %d of %B, %Y", pCurrentTime); return ( Printf("The current date is %s.", DateString) ); } /*=========================================================================== * End of Class Method CLogFile::OutputDate() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean OutputCurrentTime (void) * * This protected class method outputs the current time to the log file * in the format: "HH:MM:SS - " * Assumes that the log file is currently open and valid. * *=========================================================================*/ boolean CLogFile::OutputCurrentTime (void) { DEFINE_FUNCTION("CLogFile::OutputCurrentTime()()"); char TimeString[17] = ""; int Result; time_t CurrentTime; struct tm* pToday; /* Ensure a valid log file handle */ ASSERT(pLogFileHandle != NULL); /* Get the current local time and ensure its valid */ time(&CurrentTime); pToday = localtime(&CurrentTime); if (pToday == NULL) return (FALSE); /* Output the formatted time to log file */ strftime(TimeString, 16, "%H:%M:%S", pToday); Result = fprintf (pLogFileHandle, "%s (%ld) - ", TimeString, clock()); if (Result < 0) return (FALSE); return (TRUE); } /*=========================================================================== * End of Class Method CLogFile::OutputCurrentTime() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - void OutputMemoryStatus (void); * * Outputs the current memory status to the log file. * *=========================================================================*/ void CLogFile::OutputMemoryStatus (void) { //DEFINE_FUNCTION("CLogFile::OutputMemoryStatus()"); boolean MemResult; long UsedMemory = 0; long FreeMemory = 0; long TotalMemory = 0; /* Attempt to retrieve current memory usage */ MemResult = GetUsedMemory(UsedMemory); if (MemResult) MemResult = GetFreeMemory(FreeMemory); if (MemResult) MemResult = GetTotalMemory(TotalMemory); if (MemResult) Printf ("Used/Free/Total Memory: %lu/%lu/%lu bytes", UsedMemory, FreeMemory, TotalMemory); else Printf ("Error attempting to retrieve memory usage!"); /* Output heap information */ DebugPrintf("DebugHeapCheckMemory() returns %s", DebugHeapCheckMemory() ? "TRUE" : "FALSE"); Printf ("Heap Status: %s", GetHeapStatusString()); /* Output custom heap information */ #if defined(_DEBUG) OutputBlockInfo(); #endif } /*=========================================================================== * End of Class Method CLogFile::OutputMemoryStatus() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean OutputTabs (void); * * This protected class method outputs all the tabs required by the * current TabLevel to the log file. Assumes that the log file is currently * open and valid. Returns FALSE on any error. * *=========================================================================*/ boolean CLogFile::OutputTabs (void) { DEFINE_FUNCTION("CLogFile::OutputTabs()"); int LoopCounter; int Result; /* Ensure a valid log file handle */ ASSERT(pLogFileHandle != NULL); /* Output the tabs, if any */ for (LoopCounter = 0; LoopCounter < TabLevel; LoopCounter++) { Result = fputc('\t', pLogFileHandle); if (Result == EOF) return (FALSE); } return (TRUE); } /*=========================================================================== * End of Class Method CLogFile::OutputTabs() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean Printf (pString, ...); * * Outputs a log file entry in the usual printf() format. Returns TRUE * on success, or FALSE on any error. * *=========================================================================*/ boolean CLogFile::Printf (const char* pString, ...) { DEFINE_FUNCTION("CLogFile::Printf(char*)"); va_list Args; int Result; /* Ensure valid input */ ASSERT(pString != NULL); /* Ensure the log file is currently open */ if (!IsOpen()) return (FALSE); /* Print the line to the file */ va_start (Args, pString); Result = PrintLine(pString, Args); va_end (Args); return (boolean)(Result); } /*=========================================================================== * End of Class Method CLogFile::Printf() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean Printf (pFileHandle, pString, ...); * * Outputs a log file entry in the usual printf() format to the log file as well * as the given file stream. Returns TRUE on success, or FALSE on any error. * Ignores the file stream if its NULL. Outputs to the input stream * even if the log file is closed. * *=========================================================================*/ boolean CLogFile::Printf (FILE* pFileHandle, const char* pString, ...) { DEFINE_FUNCTION("CLogFile::Printf(FILE*, char*)"); int Result; int LoopCounter; va_list Args; /* Ensure valid input */ ASSERT(pString != NULL); va_start (Args, pString); /* Output line header to log file if open */ if (IsOpen()) { Result = PrintLine(pString, Args); if (!Result) { va_end (Args); return (FALSE); } } /* Output result to file stream if required */ if (pFileHandle != NULL) { /* Output the tabs, if any */ for (LoopCounter = 0; LoopCounter < TabLevel; LoopCounter++) { Result = fputc('\t', pFileHandle); if (Result == EOF) return (FALSE); } Result = vfprintf (pFileHandle, pString, Args); va_end (Args); if (Result < 0) return (FALSE); /* Terminate line with a line feed character */ Result = fprintf (pFileHandle, "\n"); if (Result < 0) return (FALSE); /* Flush output stream */ Result = fflush (pFileHandle); if (Result == EOF) return (FALSE); } return (TRUE); } /*=========================================================================== * End of Class Method CLogFile::Printf() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - boolean PrintLine (pString, Args); * * Outputs a line to the log file using the format string and variable * arguments. Returns FALSE on any error. Assumes that the log file * is currently open. Protected class method. * *=========================================================================*/ boolean CLogFile::PrintLine (const char* pString, va_list Args) { DEFINE_FUNCTION("CLogFile::PrintLine()"); int Result; ASSERT(pString != NULL); ASSERT(pLogFileHandle != NULL); /* Get the current time and write to file as the line header */ Result = OutputCurrentTime(); if (!Result) return (FALSE); /* Output the tabs, if any */ Result = OutputTabs(); if (!Result) return (FALSE); /* Print the variable argument list to the file */ Result = vfprintf (pLogFileHandle, pString, Args); if (Result < 0) return (FALSE); /* Output to the optional hook procedure */ if (pHookProc != NULL) pHookProc(pString, Args); /* A linefeed to terminate the current line */ Result = fprintf (pLogFileHandle, "\n"); if (Result < 0) return (FALSE); /* Flush the file stream to make sure the written characters are written * to the file. This is in case the program crashes and isn't closed. If * this did happen without the following line, any buffered log file * data might not be output. */ Result = fflush (pLogFileHandle); if (Result == EOF) return (FALSE); return (TRUE); } /*=========================================================================== * End of Class Method CLogFile::PrintLine() *=========================================================================*/ /*=========================================================================== * * Class CLogFile Method - void SetTabLevel (NewTabLevel); * * Sets the current tab level to the given value, if valid. The tab level * indicates how many tags preceed each line output to the log file, * allowing simple formatting of the output for easier viewing. Valid * input is from 0 (no tabs) to LOGFILE_MAX_TABS. * * See also: CLogFile::IncrementTags(); * CLogFile::DecrementTags(); * *=========================================================================*/ void CLogFile::SetTabLevel (const int NewTabLevel) { /* Ensure a valid input */ if (NewTabLevel < 0 || NewTabLevel > LOGFILE_MAX_TABS) return; TabLevel = NewTabLevel; } /*=========================================================================== * End of Class Method CLogFile::SetTabLevel() *=========================================================================*/ /*=========================================================================== * * Begin Module Test Routines * *=========================================================================*/ #if defined(_DEBUG) /* Turn off compiler warning options */ #if defined(__BCPLUSPLUS__) #pragma warn -rch #pragma warn -ccc #endif /*=========================================================================== * * Function - void Test_LogHook (pString, Args); * * Tests the callback functionality of the CLogFile class. * *=========================================================================*/ void Test_LogHook (const char* pString, va_list Args) { //DEFINE_FUNCTION("Test_LogHook()"); printf ("Test_LogHook called...\n"); vprintf (pString, Args); printf ("\n"); } /*=========================================================================== * End of Function void Test_LogHook() *=========================================================================*/ /*=========================================================================== * * Function - void Test_LogFile (void); * * Tests the CLogFile class. Only defined in debug builds. * - Checks constructors * - Standard open method * - Appending log files * - Open an invalid file (empty string and NULL) * - Open a log file without closing the current one. * - Tab level increments and decrements * - SetTabLevel() method with random values (-100 to 100) * - Checks the hook procedure * - Test split output to file stream * - Tests DebugPrintf() method. * *=========================================================================*/ void Test_LogFile (void) { DEFINE_FUNCTION("Test_LogFile()"); CLogFile TestLog1("test1.log"); /* Check constructors */ CLogFile TestLog2; CLogFile TestLog3; int LoopCounter; int TabLevel; /* Check standard open method */ TestLog2.Open("test2.log"); /* Test appending logs */ TestLog3.Open("test3.log", LOG_OPEN); TestLog3.Printf("Test1"); TestLog3.Close(); TestLog3.Open("test3.log", LOG_APPEND); TestLog3.Printf("Append Test1"); /* Attempt to open an invalid file (both should ASSERT) */ //TestLog2.Open(""); //TestLog2.Open(NULL); /* Open file without closing the current log */ TestLog1.Open("test1a.log"); /* Check tab level incrementation */ for (LoopCounter = 0; LoopCounter < 100; LoopCounter++) { TestLog1.IncrementTabs(); TestLog1.Printf ("TabLevel = %d", LoopCounter); } /* Check tab level decrementation */ for (LoopCounter = 0; LoopCounter < 110; LoopCounter++) { TestLog1.DecrementTabs(); TestLog1.Printf ("TabLevel = %d", LoopCounter); } /* Check random tab levels with SetTabLevel() method */ for (LoopCounter = 0; LoopCounter < 100; LoopCounter++) { TabLevel = (int) ((float)rand() * 201 / RAND_MAX) - 100; TestLog1.SetTabLevel(TabLevel); TestLog1.Printf ("SetTabLevel = %d", TabLevel); } /* Reset the tab level for the log */ TestLog1.SetTabLevel(); /* Check the hook procedures */ TestLog1.SetHookProc(Test_LogHook); TestLog1.Printf("Testing hook proc..."); TestLog1.SetHookProc(); TestLog1.Printf("Removed hook proc..."); /* Test splitting output to file stream */ TestLog1.Printf(TestLog2.GetFileHandle(), "Testing split output to file stream..."); TestLog1.Printf(stdout, "Testing split output to stdout..."); /* Test debug output */ TestLog1.DebugPrintf("Testing debug print...%d, %s", 1001, "adedr"); ASSERT(DebugHeapCheckMemory()); } /*=========================================================================== * End of Function Test_LogFile() *=========================================================================*/ /* Restore compiler warning options */ #if defined(__BCPLUSPLUS__) #pragma warn .rch #pragma warn .ccc #endif #endif /*=========================================================================== * End of Module Test Routines *=========================================================================*/