You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1257 lines
29 KiB
1257 lines
29 KiB
// Copyright (c) 1997-1999 Microsoft Corporation
|
|
//
|
|
// Debugging tools
|
|
//
|
|
// 8-13-97 sburns
|
|
|
|
|
|
|
|
|
|
#include "headers.hxx"
|
|
|
|
|
|
|
|
#ifdef LOGGING_BUILD
|
|
|
|
|
|
|
|
//
|
|
// Logging build only
|
|
//
|
|
|
|
|
|
|
|
static Burnslib::Log* logInstance = 0;
|
|
|
|
// # of spaces per indentation level
|
|
|
|
static const int TAB = 2;
|
|
|
|
|
|
|
|
Burnslib::Log*
|
|
Burnslib::Log::GetInstance()
|
|
{
|
|
do
|
|
{
|
|
if (!logInstance)
|
|
{
|
|
static bool initialized = false;
|
|
|
|
if (initialized)
|
|
{
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
|
|
// this might fail with a thrown exception if Log::Log can't
|
|
// init it's critical section. In that case, we can't allocate
|
|
// an instance. We don't handle that exception, but let it
|
|
// propagate to the caller
|
|
|
|
// ISSUE-2002/03/06-sburns we should handle the exception and
|
|
// leave the log uninitialized
|
|
|
|
logInstance = new Burnslib::Log(RUNTIME_NAME);
|
|
initialized = true;
|
|
}
|
|
}
|
|
while (0);
|
|
|
|
return logInstance;
|
|
}
|
|
|
|
|
|
|
|
// read the debugging options from the registry
|
|
|
|
void
|
|
Burnslib::Log::ReadLogFlags()
|
|
{
|
|
do
|
|
{
|
|
static String keyname =
|
|
String(REG_ADMIN_RUNTIME_OPTIONS) + RUNTIME_NAME;
|
|
|
|
HKEY hKey = 0;
|
|
LONG result =
|
|
::RegCreateKeyEx(
|
|
HKEY_LOCAL_MACHINE,
|
|
keyname.c_str(),
|
|
0,
|
|
0,
|
|
REG_OPTION_NON_VOLATILE,
|
|
|
|
// ISSUE-2002/03/05-sburns should be KEY_SET_VALUE | KEY_READ
|
|
|
|
KEY_ALL_ACCESS,
|
|
|
|
// ISSUE-2002/03/05-sburns should set a restrictive ACL here
|
|
// see NTRAID#NTBUG9-535841-2002/03/05-sburns
|
|
|
|
0,
|
|
&hKey,
|
|
0);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
break;
|
|
}
|
|
|
|
static const wchar_t* FLAG_VALUE_NAME = L"LogFlags";
|
|
|
|
DWORD dataSize = sizeof(flags);
|
|
|
|
// REVIEWED-2002/03/05-sburns no null-termination issue here
|
|
|
|
result =
|
|
::RegQueryValueEx(
|
|
hKey,
|
|
FLAG_VALUE_NAME,
|
|
0,
|
|
0,
|
|
reinterpret_cast<BYTE*>(&flags),
|
|
&dataSize);
|
|
if (result != ERROR_SUCCESS)
|
|
{
|
|
flags = DEFAULT_LOGGING_OPTIONS;
|
|
|
|
// create the value for convenience
|
|
|
|
result =
|
|
|
|
// REVIEWED-2002/03/05-sburns no null-termination issue here
|
|
|
|
::RegSetValueEx(
|
|
hKey,
|
|
FLAG_VALUE_NAME,
|
|
0,
|
|
REG_DWORD,
|
|
reinterpret_cast<BYTE*>(&flags),
|
|
dataSize);
|
|
}
|
|
|
|
::RegCloseKey(hKey);
|
|
}
|
|
while (0);
|
|
}
|
|
|
|
|
|
|
|
Burnslib::Log::~Log()
|
|
{
|
|
WriteLn(Burnslib::Log::OUTPUT_LOGS, L"closing log");
|
|
|
|
::DeleteCriticalSection(&critsec);
|
|
|
|
::TlsFree(logfileMarginTlsIndex);
|
|
|
|
if (IsOpen())
|
|
{
|
|
::CloseHandle(fileHandle);
|
|
fileHandle = INVALID_HANDLE_VALUE;
|
|
|
|
::CloseHandle(spewviewHandle);
|
|
spewviewHandle = INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::Log::Cleanup()
|
|
{
|
|
delete logInstance;
|
|
logInstance = 0;
|
|
}
|
|
|
|
|
|
|
|
// Returns the string name of the platform
|
|
|
|
String
|
|
OsName(OSVERSIONINFO& info)
|
|
{
|
|
switch (info.dwPlatformId)
|
|
{
|
|
case VER_PLATFORM_WIN32s:
|
|
{
|
|
return L"Win32s on Windows 3.1";
|
|
}
|
|
case VER_PLATFORM_WIN32_WINDOWS:
|
|
{
|
|
switch (info.dwMinorVersion)
|
|
{
|
|
case 0:
|
|
{
|
|
return L"Windows 95";
|
|
}
|
|
case 1:
|
|
{
|
|
return L"Windows 98";
|
|
}
|
|
default:
|
|
{
|
|
return L"Windows 9X";
|
|
}
|
|
}
|
|
}
|
|
case VER_PLATFORM_WIN32_NT:
|
|
{
|
|
return L"Windows NT";
|
|
}
|
|
// case VER_PLATFORM_WIN32_CE:
|
|
// {
|
|
// return L"Windows CE";
|
|
// }
|
|
default:
|
|
{
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return L"Some Unknown Windows Version";
|
|
}
|
|
|
|
|
|
|
|
// locate the log file with the highest-numbered extension, then add 1 and
|
|
// return the result.
|
|
|
|
int
|
|
DetermineNextLogNumber(const String& logDir, const String& logBaseName)
|
|
{
|
|
ASSERT(!logDir.empty());
|
|
ASSERT(!logBaseName.empty());
|
|
|
|
int largest = 0;
|
|
|
|
String filespec = logDir + L"\\" + logBaseName + L".*.log";
|
|
|
|
WIN32_FIND_DATA findData;
|
|
HANDLE ff = ::FindFirstFile(filespec.c_str(), &findData);
|
|
|
|
if (ff != INVALID_HANDLE_VALUE)
|
|
{
|
|
for (;;)
|
|
{
|
|
String current = findData.cFileName;
|
|
|
|
// grab the text between the dots: "nnn" in foo.nnn.ext
|
|
|
|
// first dot
|
|
|
|
size_t pos = current.find(L".");
|
|
if (pos == String::npos)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
String extension = current.substr(pos + 1);
|
|
|
|
// second dot
|
|
|
|
pos = extension.find(L".");
|
|
if (pos == String::npos)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
extension = extension.substr(0, pos);
|
|
|
|
int i = 0;
|
|
extension.convert(i);
|
|
largest = max(i, largest);
|
|
|
|
if (!::FindNextFile(ff, &findData))
|
|
{
|
|
BOOL success = ::FindClose(ff);
|
|
ASSERT(success);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// roll over after 255
|
|
|
|
return (++largest & 0xFF);
|
|
}
|
|
|
|
|
|
|
|
// Writes the Unicode BOM to the first two bytes of the
|
|
// file specified by handle
|
|
|
|
HRESULT
|
|
WriteUnicodeBOMToFile(HANDLE handle)
|
|
{
|
|
ASSERT(handle != INVALID_HANDLE_VALUE);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
static const int BYTES_TO_WRITE = 2;
|
|
BYTE buffer[BYTES_TO_WRITE];
|
|
buffer[0] = 0xFF;
|
|
buffer[1] = 0xFE;
|
|
|
|
DWORD numberOfBytesWritten = 0;
|
|
BOOL succeeded =
|
|
::WriteFile(
|
|
handle,
|
|
buffer,
|
|
BYTES_TO_WRITE,
|
|
&numberOfBytesWritten,
|
|
0);
|
|
if (!succeeded)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(::GetLastError());
|
|
}
|
|
|
|
ASSERT(SUCCEEDED(hr));
|
|
ASSERT(numberOfBytesWritten == BYTES_TO_WRITE);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
HANDLE
|
|
OpenLogFile(const String& logName)
|
|
{
|
|
HANDLE result =
|
|
::CreateFile(
|
|
logName.c_str(),
|
|
|
|
// ISSUE-2002/03/06-sburns don't need read, do we?
|
|
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
|
|
// Leave shared read so that we can look at the file while the
|
|
// program is still running.
|
|
|
|
FILE_SHARE_READ,
|
|
0,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
0);
|
|
|
|
// don't assert that the create worked: the current user may not have
|
|
// rights to open the log file.
|
|
|
|
if (result != INVALID_HANDLE_VALUE)
|
|
{
|
|
LARGE_INTEGER li;
|
|
LARGE_INTEGER newpos;
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte counts passed
|
|
|
|
::ZeroMemory(&li, sizeof li);
|
|
::ZeroMemory(&newpos, sizeof newpos);
|
|
|
|
BOOL success = ::SetFilePointerEx(result, li, &newpos, FILE_END);
|
|
ASSERT(success);
|
|
|
|
// NTRAID#NTBUG9-494875-2001/11/14-JeffJon
|
|
// If the newpos is at the beginning of the file, this means
|
|
// its a new file and we should write the Unicode BOM so
|
|
// that apps know the file is in Unicode.
|
|
|
|
if (newpos.QuadPart == 0)
|
|
{
|
|
HRESULT unused = WriteUnicodeBOMToFile(result);
|
|
ASSERT(SUCCEEDED(unused));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// Determine the name of the log file. If a log file of that name already
|
|
// exists, rename the existing file to a numbered backup. Create the new
|
|
// log file, return a handle to it.
|
|
|
|
HANDLE
|
|
OpenNewLogFile(const String& logBaseName, String& logName)
|
|
{
|
|
wchar_t buf[MAX_PATH + 2];
|
|
|
|
::ZeroMemory(buf, (MAX_PATH + 2) * sizeof wchar_t);
|
|
|
|
// +1 per MSDN docs.
|
|
// NTRAID#NTBUG9-550315-2002/03/28-sburns
|
|
|
|
UINT err = ::GetSystemWindowsDirectory(buf, MAX_PATH + 1);
|
|
ASSERT(err != 0 && err <= MAX_PATH);
|
|
|
|
String logDir = String(buf) + L"\\debug";
|
|
logName = logDir + L"\\" + logBaseName + L".log";
|
|
|
|
if (::GetFileAttributes(logName.c_str()) != 0xFFFFFFFF)
|
|
{
|
|
// file already exists. rename the existing file
|
|
|
|
int logNumber = DetermineNextLogNumber(logDir, logBaseName);
|
|
String newName =
|
|
logDir
|
|
+ L"\\"
|
|
+ logBaseName
|
|
+ String::format(L".%1!03d!.log", logNumber);
|
|
|
|
if (::GetFileAttributes(newName.c_str()) != 0xFFFFFFFF)
|
|
{
|
|
// could exist, as the log numbers roll over
|
|
|
|
BOOL success = ::DeleteFile(newName.c_str());
|
|
ASSERT(success);
|
|
}
|
|
|
|
// Don't assert that the move is successful. The user may not have
|
|
// rights to rename the file. If this is the case, then we will attempt
|
|
// to re-open the old log file and append to it.
|
|
|
|
// REVIEWED-2002/03/06-sburns keeping the same ACLs as the source
|
|
// is what we want
|
|
|
|
::MoveFile(logName.c_str(), newName.c_str());
|
|
}
|
|
|
|
HANDLE result = OpenLogFile(logName);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
// Opens the specified log file or creates one if it doesn't
|
|
// already exist.
|
|
|
|
HANDLE
|
|
AppendLogFile(const String& logBaseName, String& logName)
|
|
{
|
|
wchar_t buf[MAX_PATH + 1];
|
|
|
|
UINT err = ::GetSystemWindowsDirectory(buf, MAX_PATH);
|
|
ASSERT(err != 0 && err <= MAX_PATH);
|
|
|
|
String logDir = String(buf) + L"\\debug";
|
|
logName = logDir + L"\\" + logBaseName + L".log";
|
|
|
|
return OpenLogFile(logName);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This wrapper function required to make prefast shut up.
|
|
|
|
void
|
|
ExceptionPropagatingInitializeCriticalSection(LPCRITICAL_SECTION critsec)
|
|
{
|
|
__try
|
|
{
|
|
// REVIEWED-2002/03/06-sburns propagating the exception that might
|
|
// occur in low memory is what we want.
|
|
|
|
::InitializeCriticalSection(critsec);
|
|
}
|
|
|
|
// propagate the exception to our caller. This will cause Log::Log
|
|
// to abort prematurely, and jump to the handler in Log::GetInstance
|
|
|
|
__except (EXCEPTION_CONTINUE_SEARCH)
|
|
{
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Create a new log.
|
|
//
|
|
// logBaseName - base name of the log. If logging-to-file is active, then a
|
|
// file in the %windir%\debug folder will be created/used. The name of the
|
|
// file is of the form %windir%\debug\logBaseName.log. If a file by that name
|
|
// already exists, then the existing file will be renamed
|
|
// %windir%\debug\logBaseName.xxx.log, where xxx is an integer 1 greater than
|
|
// the last so-numbered file in that directory.
|
|
|
|
Burnslib::Log::Log(const String& logBaseName)
|
|
:
|
|
baseName(logBaseName),
|
|
fileHandle(INVALID_HANDLE_VALUE),
|
|
flags(0),
|
|
spewviewHandle(INVALID_HANDLE_VALUE),
|
|
spewviewPipeName(),
|
|
traceLineNumber(0),
|
|
logfileMarginTlsIndex(0)
|
|
{
|
|
ASSERT(!logBaseName.empty());
|
|
|
|
ReadLogFlags();
|
|
|
|
ExceptionPropagatingInitializeCriticalSection(&critsec);
|
|
|
|
// create thread-local storage for per-thread debugging state. We keep
|
|
// the output margin in the slot.
|
|
|
|
logfileMarginTlsIndex = ::TlsAlloc();
|
|
ASSERT(logfileMarginTlsIndex != 0xFFFFFFFF);
|
|
|
|
// spewview setup is done on-demand in WriteLn, so that spewview will
|
|
// be used as soon as the server sets up the connection.
|
|
|
|
if (ShouldLogToFile())
|
|
{
|
|
String logName;
|
|
fileHandle = OpenNewLogFile(logBaseName, logName);
|
|
|
|
WriteLn(
|
|
Burnslib::Log::OUTPUT_HEADER,
|
|
String::format(L"opening log file %1", logName.c_str()));
|
|
}
|
|
else if (ShouldAppendLogToFile())
|
|
{
|
|
String logName;
|
|
fileHandle = AppendLogFile(logBaseName, logName);
|
|
|
|
WriteLn(
|
|
Burnslib::Log::OUTPUT_HEADER,
|
|
String::format(L"appending to log file %1", logName.c_str()));
|
|
}
|
|
|
|
WriteHeader();
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::Log::WriteHeaderModule(HMODULE moduleHandle)
|
|
{
|
|
do
|
|
{
|
|
wchar_t filename[MAX_PATH + 1];
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte count passed.
|
|
|
|
::ZeroMemory(filename, sizeof filename);
|
|
|
|
// ISSUE-2002/03/06-sburns This call is problematic for large paths:
|
|
// it may truncate the result
|
|
|
|
if (::GetModuleFileNameW(moduleHandle, filename, MAX_PATH) == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
WriteLn(Burnslib::Log::OUTPUT_HEADER, filename);
|
|
|
|
// add the timestamp of the file
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attr;
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte count passed
|
|
|
|
::ZeroMemory(&attr, sizeof attr);
|
|
|
|
if (
|
|
::GetFileAttributesEx(
|
|
filename,
|
|
GetFileExInfoStandard,
|
|
&attr) == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
FILETIME localtime;
|
|
::FileTimeToLocalFileTime(&attr.ftLastWriteTime, &localtime);
|
|
SYSTEMTIME systime;
|
|
::FileTimeToSystemTime(&localtime, &systime);
|
|
|
|
WriteLn(
|
|
Burnslib::Log::OUTPUT_HEADER,
|
|
String::format(
|
|
L"file timestamp %1!02u!/%2!02u!/%3!04u! "
|
|
|
|
// NTRAID#NTBUG9-550381-2002/03/05-sburns
|
|
|
|
L"%4!02u!:%5!02u!:%6!02u!.%7!03u!",
|
|
systime.wMonth,
|
|
systime.wDay,
|
|
systime.wYear,
|
|
systime.wHour,
|
|
systime.wMinute,
|
|
systime.wSecond,
|
|
systime.wMilliseconds));
|
|
}
|
|
while (0);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::Log::WriteHeader()
|
|
{
|
|
// Log the name and timestamp of the file that created the process.
|
|
|
|
WriteHeaderModule(0);
|
|
|
|
// Log the name and timestamp of the file that corresponds to the
|
|
// resource module handle, if there is one.
|
|
|
|
if (hResourceModuleHandle)
|
|
{
|
|
WriteHeaderModule(hResourceModuleHandle);
|
|
}
|
|
|
|
SYSTEMTIME localtime;
|
|
::GetLocalTime(&localtime);
|
|
WriteLn(
|
|
Burnslib::Log::OUTPUT_HEADER,
|
|
String::format(
|
|
L"local time %1!02u!/%2!02u!/%3!04u! "
|
|
|
|
// NTRAID#NTBUG9-550381-2002/03/05-sburns
|
|
|
|
L"%4!02u!:%5!02u!:%6!02u!.%7!03u!",
|
|
localtime.wMonth,
|
|
localtime.wDay,
|
|
localtime.wYear,
|
|
localtime.wHour,
|
|
localtime.wMinute,
|
|
localtime.wSecond,
|
|
localtime.wMilliseconds));
|
|
|
|
OSVERSIONINFO info;
|
|
info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
|
|
|
|
BOOL success = ::GetVersionEx(&info);
|
|
ASSERT(success);
|
|
|
|
// Get the whistler build lab version
|
|
|
|
String labInfo;
|
|
do
|
|
{
|
|
HKEY key = 0;
|
|
LONG err =
|
|
::RegOpenKeyEx(
|
|
HKEY_LOCAL_MACHINE,
|
|
L"Software\\Microsoft\\Windows NT\\CurrentVersion",
|
|
0,
|
|
KEY_READ,
|
|
&key);
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
break;
|
|
}
|
|
|
|
wchar_t buf[MAX_PATH + 1];
|
|
|
|
// REVIEWED-2002/03/12-sburns correct byte count passed.
|
|
|
|
::ZeroMemory(buf, sizeof buf);
|
|
|
|
DWORD type = 0;
|
|
|
|
// -2 to guarantee terminating null
|
|
// NTRAID#NTBUG9-541959-2002/03/12-sburns
|
|
|
|
DWORD bufSize = sizeof(buf) - 2;
|
|
|
|
err =
|
|
::RegQueryValueEx(
|
|
key,
|
|
L"BuildLab",
|
|
0,
|
|
&type,
|
|
reinterpret_cast<BYTE*>(buf),
|
|
|
|
// REVIEWED-2002/03/12-sburns correct byte count passed.
|
|
|
|
&bufSize);
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
break;
|
|
}
|
|
|
|
labInfo = buf;
|
|
}
|
|
while (0);
|
|
|
|
WriteLn(
|
|
Burnslib::Log::OUTPUT_HEADER,
|
|
String::format(
|
|
L"running %1 %2!d!.%3!d! build %4!d! %5 (BuildLab:%6) "
|
|
|
|
#if defined(_M_IX86)
|
|
L"i386",
|
|
|
|
#elif defined(_M_AMD64)
|
|
L"amd64",
|
|
|
|
#elif defined(_M_IA64)
|
|
L"ia64",
|
|
|
|
#else
|
|
#error( "unknown target machine" );
|
|
#endif
|
|
|
|
OsName(info).c_str(),
|
|
info.dwMajorVersion,
|
|
info.dwMinorVersion,
|
|
info.dwBuildNumber,
|
|
info.szCSDVersion,
|
|
labInfo.c_str()));
|
|
|
|
WriteLn(
|
|
Burnslib::Log::OUTPUT_HEADER,
|
|
String::format(
|
|
L"logging flags %1!08X!",
|
|
flags));
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
Log::AdjustLogMargin(int delta)
|
|
{
|
|
// extract the current value in this thread's slot
|
|
|
|
HRESULT hr = S_OK;
|
|
do
|
|
{
|
|
PVOID margin = ::TlsGetValue(logfileMarginTlsIndex);
|
|
if (!margin)
|
|
{
|
|
DWORD err = ::GetLastError();
|
|
if (err != NO_ERROR)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(err);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// indent by adding to the margin
|
|
|
|
ULONG_PTR marginTemp = reinterpret_cast<ULONG_PTR>(margin);
|
|
|
|
// marginTemp will always be >= 0, as it is unsigned.
|
|
|
|
marginTemp += delta;
|
|
|
|
margin = reinterpret_cast<PVOID>(marginTemp);
|
|
|
|
// save the new margin in this thread's slot
|
|
|
|
BOOL succeeded = ::TlsSetValue(logfileMarginTlsIndex, margin);
|
|
if (!succeeded)
|
|
{
|
|
DWORD lastErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32(lastErr);
|
|
break;
|
|
}
|
|
}
|
|
while (0);
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
// guarded by caller
|
|
|
|
void
|
|
Burnslib::Log::Indent()
|
|
{
|
|
HRESULT hr = AdjustLogMargin(TAB);
|
|
ASSERT(SUCCEEDED(hr));
|
|
}
|
|
|
|
|
|
|
|
// guarded by caller
|
|
|
|
void
|
|
Burnslib::Log::Outdent()
|
|
{
|
|
HRESULT hr = AdjustLogMargin(-TAB);
|
|
ASSERT(SUCCEEDED(hr));
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
Burnslib::Log::GetLogMargin()
|
|
{
|
|
PVOID margin = ::TlsGetValue(logfileMarginTlsIndex);
|
|
if (!margin)
|
|
{
|
|
DWORD err = ::GetLastError();
|
|
if (err != NO_ERROR)
|
|
{
|
|
ASSERT(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return
|
|
static_cast<size_t>(
|
|
reinterpret_cast<ULONG_PTR>(margin));
|
|
}
|
|
|
|
|
|
|
|
// I've commented out the spewview stuff because it's so rarely used.
|
|
//
|
|
// // Examine the registry key for the name of the pipe, append our debugging
|
|
// // baseName to make it specific to this binary, and return the result. Return
|
|
// // an empty string on error (most likely cause: the registry keys are not yet
|
|
// // present)
|
|
// //
|
|
// // baseName - the logfile base name used to identify this binary.
|
|
//
|
|
// String
|
|
// DetermineSpewviewPipeName(const String& baseName)
|
|
// {
|
|
// String result;
|
|
//
|
|
// do
|
|
// {
|
|
// HKEY key = 0;
|
|
// LONG err =
|
|
// ::RegOpenKeyEx(
|
|
// HKEY_LOCAL_MACHINE,
|
|
// (String(REG_ADMIN_RUNTIME_OPTIONS) + L"Spewview\\" + baseName).c_str(),
|
|
// 0,
|
|
// KEY_READ,
|
|
// &key);
|
|
// if (err != ERROR_SUCCESS)
|
|
// {
|
|
// break;
|
|
// }
|
|
//
|
|
// wchar_t buf[MAX_PATH + 1];
|
|
//
|
|
// // REVIEWED-2002/03/12-sburns correct byte count passed.
|
|
//
|
|
// ::ZeroMemory(buf, sizeof buf);
|
|
//
|
|
// DWORD type = 0;
|
|
//
|
|
// // -2 to guarantee terminating null
|
|
// // NTRAID#NTBUG9-541959-2002/03/12-sburns
|
|
//
|
|
// DWORD bufSize = sizeof(buf) - 2;
|
|
//
|
|
// err =
|
|
// ::RegQueryValueEx(
|
|
// key,
|
|
// L"Server",
|
|
// 0,
|
|
// &type,
|
|
// reinterpret_cast<BYTE*>(buf),
|
|
//
|
|
// // REVIEWED-2002/03/12-sburns correct byte count passed.
|
|
//
|
|
// &bufSize);
|
|
// if (err != ERROR_SUCCESS)
|
|
// {
|
|
// break;
|
|
// }
|
|
//
|
|
// result =
|
|
// L"\\\\"
|
|
// + String(buf)
|
|
// + L"\\pipe\\spewview\\"
|
|
// + baseName;
|
|
// }
|
|
// while (0);
|
|
//
|
|
// return result;
|
|
// }
|
|
//
|
|
//
|
|
//
|
|
// // Attempts to connect to the spewview application that is running elsewhere.
|
|
// // Sets the spewviewHandle parameter to a valid handle on success, or
|
|
// // INVALID_HANDLE_VALUE on error.
|
|
// //
|
|
// // baseName - the logfile base name used to identify this binary.
|
|
// //
|
|
// // spewviewPipeName - if empty, set to the name of the pipe. If not empty,
|
|
// // used as the name of the pipe that the handle will be opened on.
|
|
// //
|
|
// // spewviewHandle - handle to the named pipe that spewage will be written to.
|
|
// // On success, this is set to a valid handle. On failure, this is set to
|
|
// // INVALID_HANDLE_VALUE.
|
|
//
|
|
// void
|
|
// AttemptConnectToSpewPipe(
|
|
// const String& baseName,
|
|
// String& spewviewPipeName,
|
|
// HANDLE& spewviewHandle)
|
|
// {
|
|
// ASSERT(!baseName.empty());
|
|
// ASSERT(spewviewHandle == INVALID_HANDLE_VALUE);
|
|
//
|
|
// spewviewHandle = INVALID_HANDLE_VALUE;
|
|
//
|
|
// // only attempt to determine the pipe name as long as we haven't been
|
|
// // successful determining it so far.
|
|
//
|
|
// if (spewviewPipeName.empty())
|
|
// {
|
|
// spewviewPipeName = DetermineSpewviewPipeName(baseName);
|
|
// }
|
|
//
|
|
// do
|
|
// {
|
|
// // wait a very short time for the pipe to become available
|
|
//
|
|
// BOOL err = ::WaitNamedPipe(spewviewPipeName.c_str(), 500);
|
|
// if (!err)
|
|
// {
|
|
// // pipe not available
|
|
//
|
|
// // DWORD lastErr = ::GetLastError();
|
|
//
|
|
// break;
|
|
// }
|
|
//
|
|
// spewviewHandle =
|
|
// ::CreateFile(
|
|
// spewviewPipeName.c_str(),
|
|
// GENERIC_WRITE,
|
|
// 0,
|
|
// 0,
|
|
// OPEN_EXISTING,
|
|
// FILE_ATTRIBUTE_NORMAL,
|
|
// 0);
|
|
//
|
|
// if (spewviewHandle == INVALID_HANDLE_VALUE)
|
|
// {
|
|
// // DWORD lastErr = ::GetLastError();
|
|
//
|
|
// break;
|
|
// }
|
|
// }
|
|
// while (0);
|
|
//
|
|
// return;
|
|
// }
|
|
|
|
|
|
|
|
String
|
|
Burnslib::Log::ComposeSpewLine(const String& text)
|
|
{
|
|
// This needs to be a wchar_t*, not a String, as this function will be
|
|
// called by Log::~Log, which is called when the process is being cleaned
|
|
// up. Part of the cleanup is to delete all static objects created since
|
|
// the program started, in reverse order of construction. This includes
|
|
// the InitializationGuard instances.
|
|
//
|
|
// The final invocation of the Initialization dtor will cause the single
|
|
// Log instance to be deleted, which will log a message, which will call
|
|
// this routine, so SPEW_FMT better still exist. If SPEW_FMT were an
|
|
// object, it would be instanciated after the InitializationGuard objects,
|
|
// and destroyed before them, and so would not exist at that point.
|
|
//
|
|
// (you may rightly suspect that I discovered this after I had declared
|
|
// SPEW_FMT a String instance.)
|
|
|
|
static const wchar_t* SPEW_FMT =
|
|
L"%1 " // base name
|
|
L"%2!03X!." // process id
|
|
L"%3!03X! " // thread id
|
|
L"%4!04X! " // spew line number
|
|
L"%5" // time of day
|
|
L"%6" // run time (time since process start)
|
|
L"%7" // margin whitespace
|
|
L"%8" // text
|
|
L"\r\n";
|
|
|
|
size_t margin = GetLogMargin();
|
|
String white(margin, L' ');
|
|
|
|
String tod;
|
|
if (ShouldLogTimeOfDay())
|
|
{
|
|
SYSTEMTIME localtime;
|
|
::GetLocalTime(&localtime);
|
|
|
|
tod =
|
|
String::format(
|
|
L"%1!02u!:%2!02u!:%3!02u!.%4!03u! ",
|
|
localtime.wHour,
|
|
localtime.wMinute,
|
|
localtime.wSecond,
|
|
localtime.wMilliseconds);
|
|
}
|
|
|
|
String rt;
|
|
if (ShouldLogRunTime())
|
|
{
|
|
static DWORD MILLIS_PER_SECOND = 1000;
|
|
static DWORD MILLIS_PER_MINUTE = 60000;
|
|
static DWORD MILLIS_PER_HOUR = 3600000;
|
|
static DWORD MILLIS_PER_DAY = 86400000;
|
|
|
|
DWORD tics = ::GetTickCount();
|
|
|
|
unsigned days = tics / MILLIS_PER_DAY;
|
|
tics -= days * MILLIS_PER_DAY;
|
|
|
|
unsigned hours = tics / MILLIS_PER_HOUR;
|
|
tics -= hours * MILLIS_PER_HOUR;
|
|
|
|
unsigned minutes = tics / MILLIS_PER_MINUTE;
|
|
tics -= minutes * MILLIS_PER_MINUTE;
|
|
|
|
unsigned seconds = tics / MILLIS_PER_SECOND;
|
|
tics -= seconds * MILLIS_PER_SECOND;
|
|
|
|
rt =
|
|
String::format(
|
|
L"%1!02u!:%2!02u!:%3!02u!:%4!02u!.%5!04u! ",
|
|
days,
|
|
hours,
|
|
minutes,
|
|
seconds,
|
|
tics);
|
|
}
|
|
|
|
String t =
|
|
String::format(
|
|
SPEW_FMT,
|
|
RUNTIME_NAME,
|
|
::GetCurrentProcessId(),
|
|
::GetCurrentThreadId(),
|
|
traceLineNumber,
|
|
tod.c_str(),
|
|
rt.c_str(),
|
|
white.c_str(),
|
|
text.c_str() );
|
|
|
|
return t;
|
|
}
|
|
|
|
|
|
|
|
// Spews output to the log according to the current logging type and
|
|
// output options in effect.
|
|
//
|
|
// type - log output type of this output spewage.
|
|
//
|
|
// text - the spewage. This is prefaced with the log name, thread id, spewage
|
|
// line number, and current indentation.
|
|
|
|
void
|
|
Burnslib::Log::UnguardedWriteLn(DWORD type, const String& text)
|
|
{
|
|
// guarded by caller
|
|
|
|
// CODEWORK: could circumvent this with a registry change notification
|
|
// (re-read only if changed)
|
|
|
|
// ReadLogFlags();
|
|
|
|
if (
|
|
!ShouldLogToFile()
|
|
&& !ShouldLogToDebugger()
|
|
&& !ShouldLogToSpewView() )
|
|
{
|
|
// nothing to do
|
|
|
|
return;
|
|
}
|
|
|
|
if (type & DebugType())
|
|
{
|
|
String t = ComposeSpewLine(text);
|
|
|
|
if (ShouldLogToDebugger())
|
|
{
|
|
::OutputDebugString(t.c_str());
|
|
}
|
|
|
|
if (ShouldLogToFile())
|
|
{
|
|
if (IsOpen())
|
|
{
|
|
// write disk output as unicode text.
|
|
|
|
DWORD bytesToWrite =
|
|
static_cast<DWORD>(t.length() * sizeof(wchar_t));
|
|
DWORD bytesWritten = 0;
|
|
|
|
BOOL success =
|
|
|
|
// REVIEWED-2002/03/06-sburns correct byte count passed
|
|
|
|
::WriteFile(
|
|
fileHandle,
|
|
reinterpret_cast<void*>(const_cast<wchar_t*>(t.data())),
|
|
bytesToWrite,
|
|
&bytesWritten,
|
|
0);
|
|
|
|
// NTRAID#NTBUG9-465946-2001/09/10-sburns
|
|
// ASSERT(success);
|
|
|
|
#ifdef DBG
|
|
if (success)
|
|
{
|
|
ASSERT(bytesToWrite == bytesWritten);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// I've commented out the spewview stuff because it's so rarely used.
|
|
// if (ShouldLogToSpewView())
|
|
// {
|
|
// if (spewviewHandle == INVALID_HANDLE_VALUE)
|
|
// {
|
|
// AttemptConnectToSpewPipe(
|
|
// baseName,
|
|
// spewviewPipeName,
|
|
// spewviewHandle);
|
|
// }
|
|
//
|
|
// if (spewviewHandle != INVALID_HANDLE_VALUE)
|
|
// {
|
|
// // the connect attempt worked, and we have a valid handle.
|
|
//
|
|
// DWORD bytesToWrite =
|
|
// static_cast<DWORD>(t.length() * sizeof(wchar_t));
|
|
// DWORD bytesWritten = 0;
|
|
//
|
|
// BOOL result =
|
|
// ::WriteFile(
|
|
// spewviewHandle,
|
|
// t.c_str(),
|
|
// bytesToWrite,
|
|
// &bytesWritten,
|
|
// 0);
|
|
// if (!result)
|
|
// {
|
|
// // write failed, so disconnect. On the next call to this
|
|
// // function, we will attempt to reconnect.
|
|
//
|
|
// ::CloseHandle(spewviewHandle);
|
|
// spewviewHandle = INVALID_HANDLE_VALUE;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
++traceLineNumber;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
Burnslib::Log::WriteLn(
|
|
WORD type,
|
|
const String& text)
|
|
{
|
|
::EnterCriticalSection(&critsec);
|
|
UnguardedWriteLn(type, text);
|
|
::LeaveCriticalSection(&critsec);
|
|
}
|
|
|
|
|
|
|
|
Burnslib::Log::ScopeTracer::ScopeTracer(
|
|
DWORD type_,
|
|
const String& message_)
|
|
:
|
|
message(message_),
|
|
type(type_)
|
|
{
|
|
// build this string once, instead of using the string literal in the
|
|
// below expression (which would implicitly build the string on each
|
|
// evaluation of that expression) as a slight performance gain.
|
|
|
|
static const String ENTER(L"Enter ");
|
|
|
|
Burnslib::Log* li = Burnslib::Log::GetInstance();
|
|
|
|
ASSERT(li);
|
|
|
|
if (!li)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (type & li->DebugType())
|
|
{
|
|
::EnterCriticalSection(&li->critsec);
|
|
|
|
li->UnguardedWriteLn(type, ENTER + message);
|
|
li->Indent();
|
|
|
|
::LeaveCriticalSection(&li->critsec);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Burnslib::Log::ScopeTracer::~ScopeTracer()
|
|
{
|
|
// build this string once, instead of using the string literal in the
|
|
// below expression (which would implicitly build the string on each
|
|
// evaluation of that expression) as a slight performance gain.
|
|
|
|
static const String EXIT(L"Exit ");
|
|
|
|
Burnslib::Log* li = Burnslib::Log::GetInstance();
|
|
|
|
ASSERT(li);
|
|
|
|
if (!li)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DWORD dt = li->DebugType();
|
|
if ((type & dt))
|
|
{
|
|
::EnterCriticalSection(&li->critsec);
|
|
|
|
li->Outdent();
|
|
|
|
if (OUTPUT_SCOPE_EXIT & dt)
|
|
{
|
|
li->UnguardedWriteLn(type, EXIT + message);
|
|
}
|
|
|
|
::LeaveCriticalSection(&li->critsec);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endif // LOGGING_BUILD
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|