/******************************************************************** Copyright (c) 1996-2000 Microsoft Corporation Module Name: weblog.cpp Abstract: Defines a generic class that can be used to log info from ISAPIs. This class allows its user to create application specific logfiles and automatically uses an intermediate file to log info and creates permanent log files at predefined intervals or after predefined number of records have been written to the intermediate file. Revision History: rsraghav created 03/25/96 DerekM modified 04/06/99 DerekM modifued 02/24/00 ********************************************************************/ #include "stdafx.h" #include "weblog.h" #include "util.h" #include "wchar.h" ///////////////////////////////////////////////////////////////////////////// // CWeblog- initialization stuff // ************************************************************************** CWeblog::CWeblog(void) { InitializeCriticalSection(&m_cs); m_fInit = FALSE; m_cMaxRecords = c_dwMaxRecordsDefault; m_dwDumpInterval = c_dwDumpIntervalDefault; m_liDumpIntervalAsFT = c_dwDumpIntervalDefault; m_liDumpIntervalAsFT *= c_dwMinToMS; m_liDumpIntervalAsFT *= c_dwFTtoMS; m_szAppName[0] = L'\0'; m_szFileName[0] = L'\0'; m_szFilePath[0] = L'\0'; ZeroMemory(&m_ftLastDump, sizeof(m_ftLastDump)); m_cRecords = 0; m_hFile = INVALID_HANDLE_VALUE; } // ************************************************************************** CWeblog::~CWeblog() { if (m_hFile != INVALID_HANDLE_VALUE) CloseHandle(m_hFile); DeleteCriticalSection(&m_cs); } ///////////////////////////////////////////////////////////////////////////// // CWeblog- internal stuff // ************************************************************************** HRESULT CWeblog::InitFromRegistry() { USE_TRACING("CWeblog::InitFromRegistry"); SAppLogInfoExtra alie; SAppLogInfo ali; HRESULT hr; // read the ALI & ALIE structures TESTHR(hr, ReadALI(m_szAppName, &ali, &alie)); if (FAILED(hr)) goto done; CopyMemory(&m_ftLastDump, &alie.ftLastDump, sizeof(m_ftLastDump)); lstrcpyW(m_szFileName, ali.wszLogPath); lstrcpyW(m_szFilePath, ali.wszLogPath); m_cMaxRecords = ali.cMaxTempRecs; m_cRecords = alie.cCurTempRecs; m_dwDumpInterval = ali.cDumpMins; m_liDumpIntervalAsFT = m_dwDumpInterval; m_liDumpIntervalAsFT *= c_dwMinToMS; m_liDumpIntervalAsFT *= c_dwFTtoMS; done: return hr; } // ************************************************************************** BOOL CWeblog::IsDumpRequired() { USE_TRACING("CWeblog::IsDumpRequired"); SYSTEMTIME stNow; FILETIME ftNow; // check if we've gone over our allotted amount of records if ((m_cMaxRecords > 0) && (m_cRecords >= m_cMaxRecords)) return TRUE; // ok, so now check if we've gone over our allotted time range. // if we fail to convert the system time, do a dump to be on the safe side... GetLocalTime(&stNow); if (SystemTimeToFileTime(&stNow, &ftNow) == FALSE) return TRUE; if (((*(unsigned __int64 *)&ftNow) - (*(unsigned __int64 *)&m_ftLastDump)) > m_liDumpIntervalAsFT) return TRUE; return FALSE; } // ************************************************************************** HRESULT CWeblog::DumpLog() { USE_TRACING("CWeblog::DumpLog"); SYSTEMTIME st; HRESULT hr = NOERROR; DWORD dwRet; WCHAR *pszStart = NULL; WCHAR szNewName[MAX_PATH + 1]; int nUsed; this->Lock(); // Close handle to the intermediate file CloseHandle(m_hFile); // intialize new internal stuff... m_hFile = INVALID_HANDLE_VALUE; m_cRecords = 0; // Rename the intermediate file to be permanent logfile with // appropriate name nUsed = wsprintfW(szNewName, L"%ls%ls", m_szFilePath, m_szAppName); if ((nUsed + c_cMaxTimeSuffixLen) > MAX_PATH) { // the file name is too long - truncate it szNewName[MAX_PATH - c_cMaxTimeSuffixLen - 1] = L'\0'; nUsed = lstrlenW(szNewName); } // update last dump time stamp GetLocalTime(&st); SystemTimeToFileTime(&st, &m_ftLastDump); // create the rest of the filename... pszStart = &szNewName[nUsed]; wsprintfW(pszStart, L"%04d%02d%02d%02d%02d%02d%ls", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, c_szPermLogfileSuffix); // move the file (duh.) MoveFileExW(m_szFileName, szNewName, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); // recreate the intermediate file and get a handle to it m_hFile = CreateFileW(m_szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (m_hFile == INVALID_HANDLE_VALUE) hr = Err2HR(GetLastError()); else SetFilePointer(m_hFile, 0, NULL, FILE_END); this->Unlock(); return hr; } ///////////////////////////////////////////////////////////////////////////// // CWeblog- exposed methods // ************************************************************************** HRESULT CWeblog::InitLogging(LPCWSTR szAppName) { USE_TRACING("CWeblog::InitLogging"); HRESULT hr = NOERROR; int nUsed; // only allow one init per lifetime of the object... VALIDATEEXPR(hr, (m_fInit), E_FAIL); if (FAILED(hr)) goto done; // validate params VALIDATEPARM(hr, (szAppName == NULL)); if (FAILED(hr)) goto done; // copy in the app name, zero terminating in case app name is longer than we allow... wcsncpy(m_szAppName, szAppName, c_cMaxAppNameLen); m_szAppName[c_cMaxAppNameLen] = L'\0'; // Read in settings from Registry TESTHR(hr, InitFromRegistry()); if (FAILED(hr)) goto done; // m_szFileName contains the file path: append appname and suffix nUsed = lstrlenW(m_szFileName); VALIDATEEXPR(hr, ((nUsed + lstrlenW(m_szAppName) + 6) > MAX_PATH), E_FAIL); if (FAILED(hr)) goto done; // Open the intermediate file and keep it ready for appending wsprintfW(m_szFileName + nUsed, L"%ls%ls", m_szAppName, c_szTempLogfileSuffix); m_hFile = CreateFileW(m_szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL); TESTBOOL(hr, (m_hFile != INVALID_HANDLE_VALUE)) if (FAILED(hr)) goto done; SetFilePointer(m_hFile, 0, NULL, FILE_END); m_fInit = TRUE; done: return hr; } // ************************************************************************** HRESULT CWeblog::TerminateLogging(void) { USE_TRACING("CWeblog::TerminateLogging"); SAppLogInfoExtra alie; HRESULT hr = NOERROR; // bug out if the initialization didn't work smoothly... if (m_fInit == FALSE) goto done; lstrcpyW(alie.wszName, m_szAppName); CopyMemory(&alie.ftLastDump, &m_ftLastDump, sizeof(alie.ftLastDump)); alie.cCurTempRecs = m_cRecords; TESTHR(hr, WriteALI(NULL, &alie)); if (FAILED(hr)) goto done; m_fInit = FALSE; m_cMaxRecords = c_dwMaxRecordsDefault; m_dwDumpInterval = c_dwDumpIntervalDefault; m_liDumpIntervalAsFT = c_dwDumpIntervalDefault; m_liDumpIntervalAsFT *= c_dwMinToMS; m_liDumpIntervalAsFT *= c_dwFTtoMS; m_szAppName[0] = L'\0'; m_szFileName[0] = L'\0'; m_szFilePath[0] = L'\0'; ZeroMemory(&m_ftLastDump, sizeof(m_ftLastDump)); m_cRecords = 0; done: return hr; } // ************************************************************************** HRESULT CWeblog::LogRecord(LPCWSTR szFormat, ... ) { USE_TRACING("CWeblog::LogRecord"); SYSTEMTIME st; va_list arglist; HRESULT hr = NOERROR; DWORD dwUsed, nWritten, dwUsedA; WCHAR szLogRecordW[c_cMaxRecLen + 1]; CHAR szLogRecordA[(2 * c_cMaxRecLen) + 1]; int nAdded; // only allow 'em in if they've called init... VALIDATEEXPR(hr, (m_fInit == FALSE), E_FAIL); if (FAILED(hr)) goto done; // validate params VALIDATEPARM(hr, (szFormat == NULL)); if (FAILED(hr)) goto done; // prepend the app name and current time GetLocalTime(&st); dwUsed = (ULONG)wsprintfW(szLogRecordW, L"%ls,%04u/%02u/%02u %02u:%02u:%02u\n", m_szAppName, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); va_start(arglist, szFormat); nAdded = _vsnwprintf(&szLogRecordW[dwUsed], c_cMaxRecLen - dwUsed, szFormat, arglist); // is the arglist too big for us? if (nAdded < 0) { // if so, just insert a dummy value lstrcpyW(&szLogRecordW[dwUsed], L"Logging Error: Record given for logging is too big!\r\n"); dwUsed = lstrlenW(szLogRecordW); } else { // otherwise, add a CRLF to the end... dwUsed += (ULONG) nAdded; szLogRecordW[dwUsed++] = L'\r'; szLogRecordW[dwUsed++] = L'\n'; szLogRecordW[dwUsed] = L'\0'; } va_end(arglist); // translate the record down into ASCII so the lab team can use grep & // other such utilities on these files (which don't work with unicode // text files) dwUsedA = WideCharToMultiByte(CP_ACP, 0, szLogRecordW, -1, szLogRecordA, 2 * c_cMaxRecLen, NULL, NULL); if (dwUsedA == 0) { dwUsedA = (ULONG)wsprintfA(szLogRecordA, "%ls,%04u/%02u/%02u %02u:%02u:%02u\n", m_szAppName, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); lstrcpyA(&szLogRecordA[dwUsedA], "Logging Error: unable to translate log record to ASCII\r\n"); dwUsedA = lstrlen(szLogRecordA); } else { // dwUsedA contains a character for the NULL terminator at the end of // the ASCII string. Since we don't want to write this to the log // file, reduce the size of the log entry by 1 byte... dwUsedA--; } this->Lock(); TESTBOOL(hr, (WriteFile(m_hFile, szLogRecordA, dwUsedA, &nWritten, NULL) == FALSE)); if (FAILED(hr)) { this->Unlock(); goto done; } m_cRecords++; if (IsDumpRequired()) TESTHR(hr, DumpLog()); this->Unlock(); done: return hr; } ////////////////////////////////////////////////////////////////////// // CWeblogConfig initialization // ************************************************************************** HRESULT ReadALI(LPCWSTR wszName, SAppLogInfo *pali, SAppLogInfoExtra *palie) { USE_TRACING("CWeblog::ReadALI"); HRESULT hr = NOERROR; WCHAR wszDef[MAX_PATH]; DWORD dw = 0, cb; HKEY hKeyLog = NULL, hKeyRoot = NULL; BOOL fWrite; VALIDATEPARM(hr, (wszName == NULL || pali == NULL)); if (FAILED(hr)) goto done; GetWindowsDirectoryW(wszDef, sizeof(wszDef) / sizeof(WCHAR)); wszDef[3] = L'\0'; TESTHR(hr, OpenRegKey(HKEY_LOCAL_MACHINE, c_szRPWeblogRootKey, FALSE, &hKeyRoot)); if (FAILED(hr)) goto done; TESTHR(hr, OpenRegKey(hKeyRoot, wszName, FALSE, &hKeyLog)); if (FAILED(hr)) goto done; // max allowed temp records cb = sizeof(pali->cMaxTempRecs); TESTHR(hr, ReadRegEntry(hKeyLog, c_szRVMaxRecords, NULL, (PBYTE)&pali->cMaxTempRecs, &cb, (PBYTE)&c_dwMaxRecordsDefault, sizeof(DWORD))); if (FAILED(hr)) goto done; // dump interval cb = sizeof(pali->cDumpMins); TESTHR(hr, ReadRegEntry(hKeyLog, c_szRVDumpInterval, NULL, (PBYTE)&pali->cDumpMins, &cb, (PBYTE)&c_dwDumpIntervalDefault, sizeof(DWORD))); if (FAILED(hr)) goto done; // weblog path cb = sizeof(pali->wszLogPath) * sizeof(WCHAR); TESTHR(hr, ReadRegEntry(hKeyLog, c_szRVLogFilePath, NULL, (PBYTE)&pali->wszLogPath, &cb, (PBYTE)&wszDef, (lstrlenW(wszDef) + 1) * sizeof(WCHAR))); if (FAILED(hr)) goto done; lstrcpyW(pali->wszName, wszName); // make sure we have a '\' at the end of the file path... dw = lstrlenW(pali->wszLogPath); if (pali->wszLogPath[dw - 1] != L'\\') { pali->wszLogPath[dw] = L'\\'; pali->wszLogPath[dw + 1] = L'\0'; } // make sure we have valid values here... if (pali->cDumpMins == 0) pali->cDumpMins = c_dwDumpIntervalDefault; if (pali->cMaxTempRecs == 0) pali->cMaxTempRecs = c_dwMaxRecordsDefault; // if this is NULL, we don't have to do anything more if (palie != NULL) { FILETIME ftDef; // last dump time ZeroMemory(&ftDef, sizeof(FILETIME)); cb = sizeof(palie->ftLastDump); TESTHR(hr, ReadRegEntry(hKeyLog, c_szRVLastDumpTime, NULL, (PBYTE)&palie->ftLastDump, &cb, (PBYTE)&ftDef, sizeof(FILETIME))); if (FAILED(hr)) goto done; // current temp records dw = 0; cb = sizeof(palie->cCurTempRecs); TESTHR(hr, ReadRegEntry(hKeyLog, c_szRVCurrentRecs, NULL, (PBYTE)&palie->cCurTempRecs, &cb, (PBYTE)&dw, sizeof(DWORD))); if (FAILED(hr)) goto done; lstrcpyW(palie->wszName, wszName); } done: if (hKeyLog != NULL) RegCloseKey(hKeyLog); if (hKeyRoot != NULL) RegCloseKey(hKeyRoot); return hr; } // ************************************************************************** HRESULT WriteALI(SAppLogInfo *pali, SAppLogInfoExtra *palie) { USE_TRACING("CWeblog::WriteALI"); HRESULT hr = NOERROR; LPWSTR pwsz = NULL; DWORD dwErr; HKEY hKeyLog = NULL, hKeyRoot = NULL; BOOL fWrite; if (pali != NULL) pwsz = pali->wszName; else if (palie != NULL) pwsz = palie->wszName; else goto done; TESTHR(hr, OpenRegKey(HKEY_LOCAL_MACHINE, c_szRPWeblogRootKey, TRUE, &hKeyRoot)); if (FAILED(hr)) goto done; TESTHR(hr, OpenRegKey(hKeyRoot, pwsz, TRUE, &hKeyLog)); if (FAILED(hr)) goto done; if (pali != NULL) { DWORD dw; // make sure we have valid values here... if (pali->cDumpMins == 0) pali->cDumpMins = c_dwDumpIntervalDefault; if (pali->cMaxTempRecs == 0) pali->cMaxTempRecs = c_dwMaxRecordsDefault; // make sure we have a '\' at the end of the file path... dw = lstrlenW(pali->wszLogPath); if (pali->wszLogPath[dw - 1] != L'\\') { pali->wszLogPath[dw] = L'\\'; pali->wszLogPath[dw + 1] = L'\0'; } // max temp records TESTERR(hr, RegSetValueExW(hKeyLog, c_szRVMaxRecords, 0, REG_DWORD, (PBYTE)&pali->cMaxTempRecs, sizeof(DWORD))); if (FAILED(hr)) goto done; // dump interval TESTERR(hr, RegSetValueExW(hKeyLog, c_szRVDumpInterval, 0, REG_DWORD, (PBYTE)&pali->cDumpMins, sizeof(DWORD))); if (FAILED(hr)) goto done; // weblog path TESTERR(hr, RegSetValueExW(hKeyLog, c_szRVLogFilePath, 0, REG_SZ, (PBYTE)&pali->wszLogPath, (lstrlenW(pali->wszLogPath) + 1) * sizeof(WCHAR))); if (FAILED(hr)) goto done; } // if this is NULL, we don't have to do anything more if (palie != NULL) { // last dump time TESTERR(hr, RegSetValueExW(hKeyLog, c_szRVLastDumpTime, 0, REG_BINARY, (PBYTE)&palie->ftLastDump, sizeof(FILETIME))); if (FAILED(hr)) goto done; // current temp records TESTERR(hr, RegSetValueExW(hKeyLog, c_szRVCurrentRecs, 0, REG_DWORD, (PBYTE)&palie->cCurTempRecs, sizeof(DWORD))); if (FAILED(hr)) goto done; } done: if (hKeyLog != NULL) RegCloseKey(hKeyLog); if (hKeyRoot != NULL) RegCloseKey(hKeyRoot); return hr; } // ************************************************************************** HRESULT DeleteALI(LPCWSTR wszName) { USE_TRACING("CWeblog::DeleteALI"); HRESULT hr = NOERROR; DWORD dwErr; HKEY hKeyRoot = NULL; BOOL fWrite; TESTHR(hr, OpenRegKey(HKEY_LOCAL_MACHINE, c_szRPWeblogRootKey, TRUE, &hKeyRoot)); if (FAILED(hr)) goto done; dwErr = RegDeleteKeyW(hKeyRoot, wszName); if (dwErr != ERROR_SUCCESS && dwErr != ERROR_PATH_NOT_FOUND && dwErr != ERROR_FILE_NOT_FOUND) { hr = Err2HR(GetLastError()); goto done; } done: if (hKeyRoot != NULL) RegCloseKey(hKeyRoot); return hr; }