438 lines
11 KiB
C++
438 lines
11 KiB
C++
//=======================================================================
|
|
//
|
|
// 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 <windows.h>
|
|
#include <tchar.h>
|
|
#include <stdio.h>
|
|
#include <malloc.h>
|
|
#include "FreeLog.h"
|
|
#include <MISTSAFE.h>
|
|
|
|
#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();
|
|
}
|
|
}
|