//======================================================================= // // Copyright (c) 1998-2001 Microsoft Corporation. All Rights Reserved. // // File: FreeLog.cpp // // Owner: KenSh // // Description: // // Runtime logging for use in both checked and free builds. // //======================================================================= #include #include #include #include #include "FreeLog.h" #include #ifndef _countof #define _countof(ar) (sizeof(ar)/sizeof((ar)[0])) #endif // Unicode files start with the 2 bytes { FF FE }. // This is the little-endian version of those 2 bytes. #define UNICODE_FILE_HEADER 0xFEFF #define MUTEX_TIMEOUT 1000 // Don't wait more than 1 second to write to logfile #define MAX_MUTEX_WAITS 4 // Don't keep trying after this many failures #define LOG_FILE_BIG_SIZE 50000 // Don't bother trimming if file is smaller than this #define LOG_LINES_TRIM_FROM 1000 // Start trimming if more than this many lines #define LOG_LINES_TRIM_TO 750 // Trim til the log file is this many lines #define LOG_LEVEL_SUCCESS 0 #define LOG_LEVEL_FAILURE 1 #define MAX_MSG_LENGTH (MAX_PATH + 20) #define MAX_ERROR_LENGTH 128 static const TCHAR c_szUnknownModuleName[] = _T("?"); // Local functions void LogMessageExV(UINT nLevel, DWORD dwError, LPCSTR pszFormatA, va_list args); //============================================================================ // // Private CFreeLogging class to keep track of log file resources // //============================================================================ class CFreeLogging { public: CFreeLogging(LPCTSTR pszModuleName, LPCTSTR pszLogFileName); ~CFreeLogging(); void WriteLine(LPCTSTR pszText, UINT nLevel, DWORD dwError); private: inline static HANDLE CreateMutex(LPCTSTR pszMutexName); inline HANDLE OpenLogFile(LPCTSTR pszFileName); inline void CloseLogFile(); void TrimLogFile(); BOOL AcquireMutex(); void ReleaseMutex(); private: HANDLE m_hFile; HANDLE m_hMutex; int m_cLinesWritten; int m_cFailedWaits; LPTSTR m_pszModuleName; }; CFreeLogging* g_pFreeLogging; //============================================================================ // // Public functions // //============================================================================ void InitFreeLogging(LPCTSTR pszModuleName, LPCTSTR pszLogFileName) { if (g_pFreeLogging == NULL) { g_pFreeLogging = new CFreeLogging(pszModuleName, pszLogFileName); } } void TermFreeLogging() { delete g_pFreeLogging; g_pFreeLogging = NULL; } void LogMessage(LPCSTR pszFormatA, ...) { va_list arglist; va_start(arglist, pszFormatA); LogMessageExV(LOG_LEVEL_SUCCESS, 0, pszFormatA, arglist); va_end(arglist); } void LogError(DWORD dwError, LPCSTR pszFormatA, ...) { va_list arglist; va_start(arglist, pszFormatA); LogMessageExV(LOG_LEVEL_FAILURE, dwError, pszFormatA, arglist); va_end(arglist); } void LogMessageExV(UINT nLevel, DWORD dwError, LPCSTR pszFormatA, va_list args) { if (g_pFreeLogging != NULL) { char szBufA[MAX_MSG_LENGTH]; size_t nRem=0; StringCchVPrintfExA(szBufA, _countof(szBufA), NULL, &nRem, MISTSAFE_STRING_FLAGS, pszFormatA, args); int cchA = _countof(szBufA) - nRem; #ifdef UNICODE WCHAR szBufW[MAX_MSG_LENGTH]; MultiByteToWideChar(CP_ACP, 0, szBufA, cchA+1, szBufW, _countof(szBufW)); g_pFreeLogging->WriteLine(szBufW, nLevel, dwError); #else g_pFreeLogging->WriteLine(szBufA, nLevel, dwError); #endif } } //============================================================================ // // CFreeLogging implementation // //============================================================================ CFreeLogging::CFreeLogging(LPCTSTR pszModuleName, LPCTSTR pszLogFileName) : m_cFailedWaits(0), m_cLinesWritten(0) { m_pszModuleName = _tcsdup(pszModuleName); if (m_pszModuleName == NULL) m_pszModuleName = (LPTSTR)c_szUnknownModuleName; m_hMutex = CreateMutex(pszLogFileName); m_hFile = OpenLogFile(pszLogFileName); } CFreeLogging::~CFreeLogging() { CloseLogFile(); if (m_hMutex != NULL) CloseHandle(m_hMutex); if (m_pszModuleName != c_szUnknownModuleName) free(m_pszModuleName); } inline HANDLE CFreeLogging::CreateMutex(LPCTSTR pszMutexName) { // Create a mutex in the global namespace (works across TS sessions) HANDLE hMutex = ::CreateMutex(NULL, FALSE, pszMutexName); return hMutex; } inline HANDLE CFreeLogging::OpenLogFile(LPCTSTR pszLogFileName) { HANDLE hFile = INVALID_HANDLE_VALUE; TCHAR szPath[MAX_PATH+1]; int cch = GetWindowsDirectory(szPath, _countof(szPath)-1); if(cch >0) { if (szPath[cch-1] != _T('\\')) szPath[cch++] = _T('\\'); HRESULT hr = StringCchCopyEx(szPath + cch, _countof(szPath)-cch, pszLogFileName, NULL, NULL, MISTSAFE_STRING_FLAGS); if(FAILED(hr)) return hFile; hFile = CreateFile(szPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); } #ifdef UNICODE if (hFile != INVALID_HANDLE_VALUE) { if (AcquireMutex()) { // // Check for the unicode header { FF FE } // WORD wHeader = 0; DWORD cbRead; (void)ReadFile(hFile, &wHeader, sizeof(wHeader), &cbRead, NULL); // // Write the header if there isn't one. This may be due to the // file being newly created, or to an ANSI-formatted file. // if (wHeader != UNICODE_FILE_HEADER) { SetFilePointer(hFile, 0, NULL, FILE_BEGIN); DWORD cbWritten; wHeader = UNICODE_FILE_HEADER; WriteFile(hFile, &wHeader, sizeof(wHeader), &cbWritten, NULL); SetEndOfFile(hFile); } ReleaseMutex(); } } #endif return hFile; } inline void CFreeLogging::CloseLogFile() { if (m_hFile != INVALID_HANDLE_VALUE) { // Trim old stuff from the log before closing the file TrimLogFile(); CloseHandle(m_hFile); m_hFile = INVALID_HANDLE_VALUE; } } BOOL CFreeLogging::AcquireMutex() { // In rare case where mutex not created, we allow file operations // with no synchronization if (m_hMutex == NULL) return TRUE; // Don't keep waiting if we've been blocked in the past if (m_cFailedWaits >= MAX_MUTEX_WAITS) return FALSE; BOOL fResult = TRUE; if (WaitForSingleObject(m_hMutex, MUTEX_TIMEOUT) != WAIT_OBJECT_0) { fResult = FALSE; m_cFailedWaits++; } return fResult; } void CFreeLogging::ReleaseMutex() { if (m_hMutex != NULL) // Note: AcquireMutex succeeds even if m_hMutex is NULL { ::ReleaseMutex(m_hMutex); } } void CFreeLogging::WriteLine(LPCTSTR pszText, UINT nLevel, DWORD dwError) { if (m_hFile != INVALID_HANDLE_VALUE) { DWORD cbText = lstrlen(pszText) * sizeof(TCHAR); if (AcquireMutex()) { DWORD cbWritten; SetFilePointer(m_hFile, 0, NULL, FILE_END); // // Write time/date/module as a prefix // // 2001-05-03 13:49:01 21:49:01 CDM Failed Loading module (Error 0x00000005: Access is denied.) // // NOTE: ISO 8601 format for date/time. Local time first, then GMT. // TCHAR szPrefix[60]; SYSTEMTIME sysTime, gmtTime; GetLocalTime(&sysTime); GetSystemTime(&gmtTime); LPCTSTR pszStatus = (nLevel == LOG_LEVEL_SUCCESS) ? _T("Success") : _T("Error "); StringCchPrintfEx(szPrefix, _countof(szPrefix), NULL, NULL, MISTSAFE_STRING_FLAGS, _T("%04d-%02d-%02d %02d:%02d:%02d %02d:%02d:%02d %s %-13s "), sysTime.wYear, sysTime.wMonth, sysTime.wDay, sysTime.wHour, sysTime.wMinute, sysTime.wSecond, gmtTime.wHour, gmtTime.wMinute, gmtTime.wSecond, pszStatus, m_pszModuleName); WriteFile(m_hFile, szPrefix, lstrlen(szPrefix) * sizeof(TCHAR), &cbWritten, NULL); // // Write the message followed by error info (if any) and a newline // WriteFile(m_hFile, pszText, cbText, &cbWritten, NULL); if (nLevel != LOG_LEVEL_SUCCESS) { TCHAR szError[MAX_ERROR_LENGTH]; HRESULT hr=S_OK; size_t nRem=0; // nRem contains the remaining characters in the buffer including the null terminator // To get the number of characters written in to the buffer we use // int cchErrorPrefix = _countof(szError) - nRem; StringCchPrintfEx(szError, _countof(szError), NULL, &nRem, MISTSAFE_STRING_FLAGS, _T(" (Error 0x%08X: "), dwError); // Get the number of characters written in to the buffer int cchErrorPrefix = _countof(szError) - nRem; int cchErrorText = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, 0, szError + cchErrorPrefix, _countof(szError) - cchErrorPrefix - 1, NULL); int cchError = cchErrorPrefix + cchErrorText; cchError -= 2; // backup past ": " or "\r\n" StringCchCopyEx(szError + cchError, _countof(szError)-cchError, _T(")"), NULL, NULL, MISTSAFE_STRING_FLAGS); WriteFile(m_hFile, szError, (cchError + 1) * sizeof(TCHAR), &cbWritten, NULL); } WriteFile(m_hFile, _T("\r\n"), 2 * sizeof(TCHAR), &cbWritten, NULL); // // If we've written a ton of stuff, trim now rather than waiting // for the module to unload. (This check is only for how much this // module has written, not how big the log file itself is.) // if (++m_cLinesWritten > LOG_LINES_TRIM_FROM) { TrimLogFile(); m_cLinesWritten = LOG_LINES_TRIM_TO; } ReleaseMutex(); } } } // Checks the size of the log file, and trims it if necessary. void CFreeLogging::TrimLogFile() { if (AcquireMutex()) { DWORD cbFile = GetFileSize(m_hFile, NULL); if (cbFile > LOG_FILE_BIG_SIZE) { DWORD cbFileNew = cbFile; // // Create a memory-mapped file so we can use memmove // HANDLE hMapping = CreateFileMapping(m_hFile, NULL, PAGE_READWRITE, 0, 0, NULL); if (hMapping != NULL) { LPTSTR pszFileStart = (LPTSTR)MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, 0); if (pszFileStart != NULL) { LPTSTR pszEnd = (LPTSTR)((LPBYTE)pszFileStart + cbFile); LPTSTR pszTextStart = pszFileStart; #ifdef UNICODE pszTextStart++; // skip the 2-byte header #endif // // Count newlines // int cLines = 0; for (LPTSTR pch = pszTextStart; pch < pszEnd; ) { if (*pch == _T('\n')) cLines++; // REVIEW: in Ansi builds should we call CharNextExA? // If so, what code page is the log file in? pch++; } if (cLines > LOG_LINES_TRIM_FROM) { int cTrimLines = cLines - LOG_LINES_TRIM_TO; for (pch = pszTextStart; pch < pszEnd; ) { if (*pch == _T('\n')) cTrimLines--; // REVIEW: in Ansi builds should we call CharNextExA? // If so, what code page is the log file in? pch++; if (cTrimLines <= 0) break; } // Move more recent data to beginning of file int cchMove = (int)(pszEnd - pch); memmove(pszTextStart, pch, cchMove * sizeof(TCHAR)); cbFileNew = (cchMove * sizeof(TCHAR)); #ifdef UNICODE cbFileNew += sizeof(WORD); #endif } UnmapViewOfFile(pszFileStart); } CloseHandle(hMapping); if (cbFileNew != cbFile) { // Truncate the file, now that we've moved data as needed SetFilePointer(m_hFile, cbFileNew, NULL, FILE_BEGIN); SetEndOfFile(m_hFile); } } } ReleaseMutex(); } }