/*++ Copyright (c) 1997 Microsoft Corporation Module Name: stilog.cpp Abstract: Class to handle file based logging Author: Vlad Sadovsky (VladS) 01-Sep-1997 History: --*/ // // Include Headers // #include "cplusinc.h" #include "sticomm.h" // // Static definitions and variables // static const TCHAR szDefaultName[] = TEXT("Sti_Trace.log"); static const TCHAR szDefaultTracerName[] = TEXT("STI"); static const TCHAR szColumns[] = TEXT("Severity TracerName [Process::ThreadId] Time MessageText\r\n\r\n"); static const TCHAR szOpenedLog[] = TEXT("\n****** Opened file log at %s %s .Tracer (%s) , called from [%#s::%#lx]\n"); static const TCHAR szClosedLog[] = TEXT("\n******Closed trace log on %s %s Tracer (%s) , called from [%#s::%#lx]\n"); #define STI_LOG_NUM_BYTES_TO_LOCK 4096 // // Functions // // inline BOOL FormatStdTime( IN const SYSTEMTIME * pstNow, IN OUT TCHAR * pchBuffer, IN int cbBuffer ) { return ( GetTimeFormat( LOCALE_SYSTEM_DEFAULT, ( LOCALE_NOUSEROVERRIDE | TIME_FORCE24HOURFORMAT| TIME_NOTIMEMARKER), pstNow, NULL, pchBuffer, cbBuffer) != 0); } // FormatStdTime() inline BOOL FormatStdDate( IN const SYSTEMTIME * pstNow, IN OUT TCHAR * pchBuffer, IN int cbBuffer) { return ( GetDateFormat( LOCALE_SYSTEM_DEFAULT, LOCALE_NOUSEROVERRIDE, pstNow, NULL, pchBuffer, cbBuffer) != 0); } // FormatStdDate() inline TCHAR TraceFlagToLetter( IN DWORD dwType ) { if (dwType & STI_TRACE_ERROR) { return TEXT('e'); } else if (dwType & STI_TRACE_WARNING) { return TEXT('w'); } else if (dwType & STI_TRACE_INFORMATION) { return TEXT('i'); } else { return TEXT('t'); } } STI_FILE_LOG::STI_FILE_LOG( IN LPCTSTR lpszTracerName, IN LPCTSTR lpszLogName, IN DWORD dwFlags, // = 0 IN HMODULE hMessageModule // =NULL ) /*++ Description Constructor function for given log object. Arguments: lpszSource: file name for log. Relative to Windows directory Note: --*/ { LPCTSTR lpActualLogName; TCHAR szTempName[MAX_PATH]; LPTSTR pszFilePart; DWORD dwError; DWORD cbName; DWORD dwLevel; DWORD dwMode; BOOL fTruncate = FALSE; m_dwSignature = SIGNATURE_FILE_LOG; lpActualLogName = szDefaultName; m_lWrittenHeader = FALSE; m_hDefaultMessageModule = hMessageModule ? hMessageModule : GetModuleHandle(NULL); dwLevel = STI_TRACE_ERROR; dwMode = STI_TRACE_ADD_TIME | STI_TRACE_ADD_THREAD; m_hLogFile = INVALID_HANDLE_VALUE; *m_szLogFilePath = TEXT('\0'); m_dwMaxSize = STI_MAX_LOG_SIZE; ReportError(NO_ERROR); // // Read global settings from the registry // RegEntry re(REGSTR_PATH_STICONTROL REGSTR_PATH_LOGGING,HKEY_LOCAL_MACHINE); if (re.IsValid()) { m_dwMaxSize = re.GetNumber(REGSTR_VAL_LOG_MAXSIZE,STI_MAX_LOG_SIZE); // // If we need to check append flag, read it from the registry // if (dwFlags & STIFILELOG_CHECK_TRUNCATE_ON_BOOT) { fTruncate = re.GetNumber(REGSTR_VAL_LOG_TRUNCATE_ON_BOOT,FALSE); } } // // Open file for logging // if (lpszLogName && *lpszLogName ) { lpActualLogName = lpszLogName; } // // lpszTracerName is ANSI. // if (lpszTracerName && *lpszTracerName ) { lstrcpyn(m_szTracerName,lpszTracerName,sizeof(m_szTracerName) / sizeof(m_szTracerName[0])); m_szTracerName[sizeof(m_szTracerName) / sizeof(m_szTracerName[0]) -1] = TEXT('\0'); } else { lstrcpy(m_szTracerName,szDefaultTracerName); } // // Approximate process name with name of the executable binary used to create process // *szTempName = TEXT('\0'); ::GetModuleFileName(NULL,szTempName,sizeof(szTempName) / sizeof(szTempName[0])); szTempName[(sizeof(szTempName) / sizeof(szTempName[0])) - 1] = TEXT('\0'); pszFilePart = _tcsrchr(szTempName,TEXT('\\')); pszFilePart = (pszFilePart && *pszFilePart) ? ::CharNext(pszFilePart) : szTempName; lstrcpyn(m_szProcessName,pszFilePart,sizeof(m_szProcessName)/sizeof(TCHAR)); m_szProcessName[sizeof(m_szProcessName)/sizeof(TCHAR) - 1] = TEXT('\0'); // // Read flags for this logger // re.MoveToSubKey(lpszTracerName); if (re.IsValid()) { dwLevel = re.GetNumber(REGSTR_VAL_LOG_LEVEL,STI_TRACE_ERROR) & STI_TRACE_MESSAGE_TYPE_MASK; dwMode = re.GetNumber(REGSTR_VAL_LOG_MODE,STI_TRACE_ADD_THREAD) & STI_TRACE_MESSAGE_FLAGS_MASK; } m_dwReportMode = dwLevel | dwMode; // // Open log file // DWORD dwCharsAvailable = sizeof(m_szLogFilePath) / sizeof(m_szLogFilePath[0]) - lstrlen(TEXT("\\")) - lstrlen(lpActualLogName); cbName = ::ExpandEnvironmentStrings(TEXT("%USERPROFILE%"), m_szLogFilePath, dwCharsAvailable); if (( cbName == 0) || !*m_szLogFilePath ) { ReportError(::GetLastError()); return; } m_szLogFilePath[sizeof(m_szLogFilePath) / sizeof(m_szLogFilePath[0]) - 1] = TEXT('\0'); lstrcat(lstrcat(m_szLogFilePath,TEXT("\\")),lpActualLogName); m_hLogFile = ::CreateFile(m_szLogFilePath, GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, // security attributes OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // template file handle // if ( m_hLogFile!= INVALID_HANDLE_VALUE) { if (IS_VALID_HANDLE(m_hLogFile)) { if(fTruncate) { ::SetFilePointer( m_hLogFile, 0, NULL, FILE_BEGIN ); ::SetEndOfFile( m_hLogFile ); } ::SetFilePointer( m_hLogFile, 0, NULL, FILE_END); } if (!IS_VALID_HANDLE(m_hLogFile)) { ReportError(::GetLastError()); } else { // // Success // } } /* STI_FILE_LOG::STI_FILE_LOG() */ STI_FILE_LOG::~STI_FILE_LOG( VOID) /*++ Description: Destructor function for given STI_FILE_LOG object. --*/ { SYSTEMTIME stCurrentTime; TCHAR szFmtDate[20]; TCHAR szFmtTime[32]; TCHAR szTextBuffer[128]; if(m_lWrittenHeader) { GetLocalTime(&stCurrentTime); FormatStdDate( &stCurrentTime, szFmtDate, 15); FormatStdTime( &stCurrentTime, szFmtTime, sizeof(szFmtTime) / sizeof(szFmtTime[0])); ::_sntprintf(szTextBuffer, sizeof(szTextBuffer)/sizeof(szTextBuffer[0]), szClosedLog, szFmtDate, szFmtTime, m_szTracerName, m_szProcessName, ::GetCurrentThreadId() ); szTextBuffer[sizeof(szTextBuffer)/sizeof(szTextBuffer[0]) - 1] = TEXT('\0'); WriteStringToLog(szTextBuffer); } if (IS_VALID_HANDLE(m_hLogFile)) { ::FlushFileBuffers( m_hLogFile); ::CloseHandle(m_hLogFile); m_hLogFile = INVALID_HANDLE_VALUE; } m_dwSignature = SIGNATURE_FILE_LOG_FREE; } /* STI_FILE_LOG::~STI_FILE_LOG() */ // // IUnknown methods. Used only for reference counting // STDMETHODIMP STI_FILE_LOG::QueryInterface( REFIID riid, LPVOID * ppvObj) { return E_FAIL; } STDMETHODIMP_(ULONG) STI_FILE_LOG::AddRef( void) { ::InterlockedIncrement(&m_cRef); return m_cRef; } STDMETHODIMP_(ULONG) STI_FILE_LOG::Release( void) { LONG cNew; if(!(cNew = ::InterlockedDecrement(&m_cRef))) { delete this; } return cNew; } void STI_FILE_LOG:: WriteLogSessionHeader( VOID ) /*++ Description Arguments: Note: --*/ { SYSTEMTIME stCurrentTime; TCHAR szFmtDate[32]; TCHAR szFmtTime[32]; TCHAR szTextBuffer[128]; GetLocalTime(&stCurrentTime); FormatStdDate( &stCurrentTime, szFmtDate, sizeof(szFmtDate) / sizeof(szFmtDate[0])); FormatStdTime( &stCurrentTime, szFmtTime, sizeof(szFmtTime) / sizeof(szFmtTime[0])); ::_sntprintf(szTextBuffer, sizeof(szTextBuffer)/sizeof(szTextBuffer[0]), szOpenedLog, szFmtDate, szFmtTime, m_szTracerName, m_szProcessName, ::GetCurrentThreadId()); szTextBuffer[sizeof(szTextBuffer)/sizeof(szTextBuffer[0]) -1] = TEXT('\0'); WriteStringToLog(szTextBuffer); WriteStringToLog(szColumns); } void STI_FILE_LOG:: ReportMessage( DWORD dwType, LPCTSTR pszMsg, ... ) /*++ Description Arguments: Note: --*/ { va_list list; TCHAR *pchBuff = NULL; DWORD cch; va_start (list, pszMsg); pchBuff = NULL; cch = ::FormatMessage(FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, pszMsg, 0, 0, (LPTSTR) &pchBuff, 1024, &list ); if (pchBuff && cch) { vReportMessage(dwType,pchBuff,list); LocalFree(pchBuff); } va_end(list); } void STI_FILE_LOG:: ReportMessage( DWORD dwType, DWORD idMessage, ... ) /*++ Description Arguments: Note: --*/ { va_list list; TCHAR *pchBuff = NULL; DWORD cch; va_start (list, idMessage); // // Read message and add inserts // pchBuff = NULL; cch = ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_HMODULE, m_hDefaultMessageModule , idMessage, 0, (LPTSTR) &pchBuff, 1024, &list ); if (pchBuff && cch) { vReportMessage(dwType,pchBuff,list); LocalFree(pchBuff); } va_end(list); } void STI_FILE_LOG:: vReportMessage( DWORD dwType, LPCTSTR pszMsg, va_list arglist ) /*++ Description Arguments: Note: --*/ { TCHAR achTextBuffer[1024]; CSimpleString csTextBuffer; DWORD dwReportMode; if ((QueryError() != NOERROR) || (!m_hLogFile || m_hLogFile == INVALID_HANDLE_VALUE)) { return; } // Do we need to display this message ? if (!((dwType & m_dwReportMode) & STI_TRACE_MESSAGE_TYPE_MASK)) { return; } // Start message with type csTextBuffer = TraceFlagToLetter(dwType); csTextBuffer += TEXT(" "); // Add name of tracer source csTextBuffer += m_szTracerName; csTextBuffer += TEXT(" "); dwReportMode = m_dwReportMode | (dwType & STI_TRACE_MESSAGE_FLAGS_MASK); // // Prepare header if needed // if (dwReportMode & STI_TRACE_MESSAGE_FLAGS_MASK ) { if (dwReportMode & STI_TRACE_ADD_THREAD ) { // // Add process::thread ID // CSimpleString csThreadId; csThreadId.Format(TEXT("[%#s::%#lx] "), m_szProcessName, ::GetCurrentThreadId()); csTextBuffer += csThreadId; csTextBuffer += TEXT(" "); } if (dwReportMode & STI_TRACE_ADD_TIME ) { // Add current time SYSTEMTIME stCurrentTime; TCHAR szFmtTime[32]; *szFmtTime = TEXT('\0'); ::GetLocalTime(&stCurrentTime); FormatStdTime( &stCurrentTime, szFmtTime, 15); csTextBuffer += szFmtTime; csTextBuffer += TEXT(" "); } } csTextBuffer += TEXT("\t"); // // Now add message text itself // _vsntprintf(achTextBuffer, sizeof(achTextBuffer)/sizeof(achTextBuffer[0]), pszMsg, arglist); achTextBuffer[sizeof(achTextBuffer)/sizeof(achTextBuffer[0]) - 1] = TEXT('\0'); csTextBuffer += achTextBuffer; csTextBuffer += TEXT("\r\n"); // // Write to the file, flushing buffers if message type is ERROR // WriteStringToLog(csTextBuffer.String(),(dwType & STI_TRACE_MESSAGE_TYPE_MASK) & STI_TRACE_ERROR); } VOID STI_FILE_LOG:: WriteStringToLog( LPCTSTR pszTextBuffer, BOOL fFlush // = FALSE ) { DWORD dwcbWritten; LONG lHeaderWrittenFlag; // // Integrity check. Make sure the file handle is valid - if not, // then bail. // if (!(IS_VALID_HANDLE(m_hLogFile))) { #ifdef DEBUG //OutputDebugString(TEXT("STILOG File or Mutex handle is invalid. ")); #endif return ; } // // If had not done yet, write session header here. Note this is recursive call // and flag marking header should be set first. // lHeaderWrittenFlag = InterlockedExchange(&m_lWrittenHeader,1L); if (!lHeaderWrittenFlag) { WriteLogSessionHeader(); } // // Check that log file size is not exceeding the limit. Assuming log file size is set to fit // into 32bit field, so we don't bother looking at high dword. // // Nb: We are not saving backup copy of the log, expecting that it never grows that large // BY_HANDLE_FILE_INFORMATION fi; if (!GetFileInformationByHandle(m_hLogFile,&fi)) { #ifdef DEBUG OutputDebugString(TEXT("STILOG could not get file size for log file. ")); #endif return ; } if ( fi.nFileSizeHigh !=0 || (fi.nFileSizeLow > m_dwMaxSize) ){ ::SetFilePointer( m_hLogFile, 0, NULL, FILE_BEGIN ); ::SetEndOfFile( m_hLogFile ); ::GetFileInformationByHandle(m_hLogFile,&fi); } OVERLAPPED overlappedStruct = {0}; // Initialize all members to 0 overlappedStruct.Offset = fi.nFileSizeLow; overlappedStruct.OffsetHigh = fi.nFileSizeHigh; // // Wait till lock is granted. We can call LockfileEx to do this since the file was not opened for async i/o // BOOL fLockedFile = ::LockFileEx(m_hLogFile, // log file handle LOCKFILE_EXCLUSIVE_LOCK, // we want to be the only writer 0, // reserved - must be 0 STI_LOG_NUM_BYTES_TO_LOCK, // low-order word of length 0, // high-order word of length &overlappedStruct); // contains starting offset if (fLockedFile) { ::SetFilePointer( m_hLogFile, 0, NULL, FILE_END); #ifdef _UNICODE UINT len = lstrlen(pszTextBuffer); CHAR *ansiBuffer = (CHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 2)); if(ansiBuffer) { ::WideCharToMultiByte(CP_ACP, 0, pszTextBuffer, -1, ansiBuffer, len + 1, NULL, NULL); ::WriteFile(m_hLogFile, ansiBuffer, len, &dwcbWritten, NULL); HeapFree(GetProcessHeap(), 0, ansiBuffer); ansiBuffer = NULL; } #else ::WriteFile(m_hLogFile, pszTextBuffer, lstrlen(pszTextBuffer), &dwcbWritten, NULL); #endif BOOL fUnlockedFile = ::UnlockFileEx(m_hLogFile, // handle to file 0, // reserved STI_LOG_NUM_BYTES_TO_LOCK, // low-order part of length 0, // high-order part of length &overlappedStruct); // unlock region start if (!fUnlockedFile) { #ifdef DEBUG ::OutputDebugString(TEXT("Failed to unlock STI log file!")); #endif } if (fFlush) { // Flush buffers to disk FlushFileBuffers(m_hLogFile); } } else { #ifdef DEBUG ::OutputDebugString(TEXT("Failed to lock STI log file!")); #endif } #ifdef DEBUG ::OutputDebugString(pszTextBuffer); #endif } // // C-APIs // HANDLE WINAPI CreateStiFileLog( IN LPCTSTR lpszTracerName, IN LPCTSTR lpszLogName, IN DWORD dwReportMode ) /*++ Description Arguments: Note: --*/ { HANDLE hRet = INVALID_HANDLE_VALUE; STI_FILE_LOG* pStiFileLog = NULL; pStiFileLog = new STI_FILE_LOG(lpszTracerName,lpszLogName); if(pStiFileLog){ if (pStiFileLog->IsValid()) { hRet = static_cast(pStiFileLog); pStiFileLog->SetReportMode(pStiFileLog->QueryReportMode() | dwReportMode); } else { // // Notice that we delete this object rather than calling // CloseStiFileLog. We do this because it the filelog is // not valid (maybe it had an internal creation error), // CloseStiFileLog won't try to delete it, hence we delete // it here. // delete pStiFileLog; pStiFileLog = NULL; hRet = INVALID_HANDLE_VALUE; } } else { ::SetLastError(ERROR_NOT_ENOUGH_MEMORY); } return hRet; } DWORD WINAPI CloseStiFileLog( IN HANDLE hFileLog ) { STI_FILE_LOG* pStiFileLog = NULL; pStiFileLog = static_cast(hFileLog); if (IsBadWritePtr(pStiFileLog,sizeof(STI_FILE_LOG)) || !pStiFileLog->IsValid()) { ::SetLastError(ERROR_INVALID_HANDLE); return ERROR_INVALID_HANDLE; } delete pStiFileLog; return NOERROR; } DWORD WINAPI ReportStiLogMessage( IN HANDLE hFileLog, IN DWORD dwType, IN LPCTSTR psz, ... ) { STI_FILE_LOG* pStiFileLog = NULL; pStiFileLog = static_cast(hFileLog); if (IsBadWritePtr(pStiFileLog,sizeof(STI_FILE_LOG)) || !pStiFileLog->IsValid()) { ::SetLastError(ERROR_INVALID_HANDLE); return ERROR_INVALID_HANDLE; } va_list list; va_start (list, psz); pStiFileLog->vReportMessage(dwType,psz,list); va_end(list); return NOERROR; } /********************************* End of File ***************************/