/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ #include "stdafx.h" #include #include // _get_osfhandle ?? #include "MyLog.h" #include "Generic.h" #include "MyButtonResource.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // MyLog // Constructor. MyLog::MyLog(const TCHAR* pszFileName) : m_hwndStatusBar(NULL) , m_eLevelApp(Warns) , m_eLevelCurrent(Nolog) { _ASSERTE(pszFileName); _ASSERTE(pszFileName[0]); // The sent "pszFileName" can be one of two things. If it begins with a // double-backslash ("\\"), then we assume it's an absolute path to a // network file. Otherwise, we just append it to the current directory, // which is the much more common case. if ('\\' == pszFileName[0] && '\\' == pszFileName[1]) { m_sPath = pszFileName; } else { // This is the more common case. Append it to the current directory. DWORD dwErr(::GetModuleFileName(NULL, m_sPath.GetBuffer(MAX_PATH), MAX_PATH)); m_sPath.ReleaseBuffer(); if (dwErr) { m_sPath = Generic::DirFromPath(m_sPath); m_sPath += CString("\\") + pszFileName; } else { _ASSERTE(! "Error - ::GetModuleFileName() failed"); } } // See if the file already exists and is too big (currently hardcoded at // four Megabytes). Since we want this to be a circular log, we don't just // delete the file, we delete enough of the oldest entries to leave us a // four Meg file. TRACE(" Log will be created at path <%s>\n", m_sPath); DWORD dwMaxLogSize(1024 * 1024 * 4); DWORD dwLogSize(0); int nErr(0); if (Generic::IsFile(pszFileName) && Generic::GetFileSize(pszFileName, dwLogSize)) { // Very important to disable logging during this operation. if (dwMaxLogSize < dwLogSize) { Level levelOld(m_eLevelApp); SetLevel(Nolog); VERIFY(SUCCEEDED(Generic::TrimFileToSize(pszFileName, dwMaxLogSize))); SetLevel(m_eLevelApp); } } // Create and open the file. We fix up the errno to be neat. m_pOutFile = new ofstream(m_sPath, ios_base::out | ios_base::app); nErr = ::GetLastError(); if (ERROR_ALREADY_EXISTS == nErr || ERROR_SUCCESS == nErr) { ::SetLastError(0); } else { _ASSERTE(! "Error - unexpected errno"); } if (m_pOutFile->is_open()) { ; } else { _ASSERTE(! "Unable to create log file!"); delete m_pOutFile; m_pOutFile = NULL; } // Always on in debug mode, and at this level. #ifdef _DEBUG SetLevel(Debug); #endif /* ?? Can't figger how to convert ofstream to HANDLE. // In _DEBUG builds, make TRACE and ASSERTs also write to our log file. // This is just nice to have for debugging. int nMode(0); HANDLE hOutFile(_get_osfhandle(m_pOutFile->fd())); _ASSERTE(hOutFile); nMode = _CrtSetReportMode(_CRT_WARN, _CRTDBG_REPORT_MODE); VERIFY(-1 != (nMode = _CrtSetReportMode(_CRT_WARN, nMode | _CRTDBG_MODE_FILE))); _CrtSetReportFile(_CRT_WARN, hOutFile); nMode = _CrtSetReportMode(_CRT_ERROR, _CRTDBG_REPORT_MODE); VERIFY(-1 != (nMode = _CrtSetReportMode(_CRT_ERROR, nMode | _CRTDBG_MODE_FILE))); _CrtSetReportFile(_CRT_ERROR, hOutFile); nMode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_REPORT_MODE); VERIFY(-1 != (nMode = _CrtSetReportMode(_CRT_ASSERT, nMode | _CRTDBG_MODE_FILE))); _CrtSetReportFile(_CRT_ASSERT, hOutFile); */ } // Destructor. /* virtual */ MyLog::~MyLog() { if (m_pOutFile) { m_pOutFile->close(); delete m_pOutFile; m_pOutFile = NULL; } } // Or on not? bool MyLog::IsEnabled() const { return Nolog != m_eLevelApp; } // Set lowest level that will log to file. Setting to Warning is normal for // general use, Trace or Debug for debugging, Debug for beta Testing. void MyLog::SetLevel(Level eLevel) { static bool bFirstTime = true; m_eLevelApp = eLevel; Header(__FILE__, __LINE__, MyLog::Audit); CString sLevel; switch (m_eLevelApp) { case MyLog::Audit: sLevel = "Audit"; break; case MyLog::Error: sLevel = "Error"; break; case MyLog::Warns: sLevel = "Warns"; break; case MyLog::Trace: sLevel = "Trace"; break; case MyLog::Debug: sLevel = "Debug"; break; case MyLog::Nolog: sLevel = "Nolog"; break; default: _ASSERTE(! "Impossible default case!"); break; } if (bFirstTime) { bFirstTime = false; Log("-----------------------------------------------\n" "Log <%s> level set to <%d> (<%s>).\n", m_sPath, m_eLevelApp, sLevel); } else { Log("Log level set to <%d> (<%s>).\n", m_eLevelApp, sLevel); } } // Must clear it after sending so that it's empty the next time. CString MyLog::GetLastOutput() { CString sReturn(m_sLastOutput); m_sLastOutput.Empty(); return sReturn; } // Prints the file, line, date/time header. (We generally don't want the // header to be in the last output string.) The level is a single character // that we mark with an "@" to make it easier to find in NotePad. void MyLog::Header(const CString& sFile, int nLine, MyLog::Level eLevel) { _ASSERTE(! sFile.IsEmpty()); _ASSERTE(0 < nLine); // Set this so that the Log() method knows what was sent, and so if // it needs to log. m_eLevelCurrent = eLevel; // Now log the header for this message. We cannot use leading zeros, because // then we can't double-click on them in the MSVC Output Window - they get // interpreted as octal! if (m_eLevelCurrent >= m_eLevelApp) { PrintCurTimeAndLevel( /* bTrace */ false); TRACE(" "); CString sLine; sLine.Format("%4d", nLine); if (m_sBaseDirectory.IsEmpty()) { Log("%s(%s): ", Generic::FileFromPath(sFile), sLine); } else { // Convert full path to path relative to base path. Log("%s(%s): ", Generic::RelativeFromBase(m_sBaseDirectory, sFile), sLine); } m_sLastOutput.Empty(); } } // The (optional) call should be made as soon as possible in the caller's code. // The "base" directory is where the project source code is; this is done so // that double-clicking in the MSVC Output window works for DLLs, too. // // In other words, we want to log this: // // ..\\EdapTools\\MyLog.cpp(64): Log enable set to 1. // // ... instead of this: // // MyLog.cpp(64): Log enable set to 1.\n"); // // Send the full path name of the project's source code. void MyLog::SetBaseDirectory(const CString& sDir) { _ASSERTE(! sDir.IsEmpty()); _ASSERTE((0 == sDir.Find("\\\\") || ':' == sDir.GetAt(1)) && "Must be a fully qualified path!"); m_sBaseDirectory = sDir; } // This lets the developed, in _DEBUG mode only, write logged messages to the // Status Bar (or any other window). void MyLog::SetStatusBarHwnd(HWND hwndStatusBar) { m_hwndStatusBar = hwndStatusBar; } // void MyLog::Log(const TCHAR* pszFormatString, ...) { _ASSERTE(pszFormatString); if (m_eLevelCurrent >= m_eLevelApp) { va_list argList; va_start(argList, pszFormatString); const TCHAR* ptr = pszFormatString; while (*ptr) { TCHAR* str = NULL; _bstr_t bstr; int nInteger(0); unsigned int unInt(0U); long lLong(0); unsigned long ulLong(0U); double dDoub(0.0); __int64 i64(0); if ('%' == *ptr) { switch (*(ptr+1)) { case '6': { CString s; i64 = va_arg(argList, __int64); s.Format("%.0I64d", i64); *this << s; ptr++; break; } case 'x': { CString s; nInteger = va_arg(argList, int); s.Format("%08X", nInteger); *this << s; ptr++; break; } case 'p': { CString s; nInteger = va_arg(argList, int); s.Format("%04X:%04X", nInteger / 65536, nInteger % 65536); *this << s; ptr++; break; } case 's': str = va_arg(argList, TCHAR*); if (! str) break; *this << str; ptr++; break; case 't': bstr = va_arg(argList, _bstr_t); if (! bstr) break; *this << (LPCTSTR) bstr; ptr++; break; case 'd': nInteger = va_arg(argList, int); *this << nInteger; ptr++; break; case 'u': unInt = va_arg(argList, unsigned int); *this << unInt; ptr++; break; case 'l': ptr++; if ('d' == *(ptr+1)) { lLong = va_arg(argList, long); *this << lLong; ptr++; } else if ('u' == *(ptr+1)) { ulLong = va_arg(argList, unsigned long); *this << ulLong; ptr++; } break; case 'f': case 'g': dDoub = va_arg(argList, double); *this << dDoub; ptr++; break; default: *this << "%%"; _ASSERTE(! "Unhandled MyLog format-specifier type!"); break; } } else { // Not a '%', just a literal character. *this << *ptr; } ptr++; } // Done iterating characters. va_end(argList); DEBUG_ONLY(::Sleep(20)); // Needed to make TRACEs appear. } // Write this message to the status bar. This requires that MyLog be a // friend of CMainFrame (only in _DEBUG). #ifdef _DEBUG if (m_eLevelCurrent >= m_eLevelApp) { if (::IsWindow(m_hwndStatusBar)) { ::SendMessage(m_hwndStatusBar, SB_SETTEXT, SBT_NOBORDERS, reinterpret_cast (static_cast (m_sLastOutput))); } } #endif } // MyLog& MyLog::operator <<(unsigned int unVal) { if (m_eLevelCurrent >= m_eLevelApp) { strstream tmp; tmp << unVal; tmp << '\0'; TCHAR* pszOutput = tmp.str(); Output(pszOutput); tmp.freeze(false); } return *this; } // MyLog& MyLog::operator <<(long lVal) { if (m_eLevelCurrent >= m_eLevelApp) { strstream tmp; tmp << lVal; tmp << '\0'; TCHAR* pszOutput = tmp.str(); Output(pszOutput); tmp.freeze(false); } return *this; } // MyLog& MyLog::operator <<(const TCHAR* str) { if (m_eLevelCurrent >= m_eLevelApp) { Output(str); } return *this; } // MyLog& MyLog::operator <<(TCHAR tch) { if (m_eLevelCurrent >= m_eLevelApp) { TCHAR szCh[2]; szCh[0] = tch; szCh[1] = '\0'; Output(szCh); } return *this; } // MyLog& MyLog::operator <<(int nVal) { if (m_eLevelCurrent >= m_eLevelApp) { strstream tmp; tmp << nVal; tmp << '\0'; TCHAR* pszOutput = tmp.str(); Output(pszOutput); tmp.freeze(false); } return *this; } // MyLog& MyLog::operator <<(unsigned long ulVal) { if (m_eLevelCurrent >= m_eLevelApp) { strstream tmp; tmp << ulVal; tmp << '\0'; TCHAR* pszOutput = tmp.str(); Output(pszOutput); tmp.freeze(false); } return *this; } // MyLog& MyLog::operator <<(double dVal) { if (m_eLevelCurrent >= m_eLevelApp) { strstream tmp; tmp << dVal; tmp << '\0'; TCHAR* pszOutput = tmp.str(); Output(pszOutput); tmp.freeze(false); } return *this; } // This prints the current time to the file, but not to the output window. void MyLog::PrintCurTimeAndLevel(bool bTrace /* = true */ ) { if (m_eLevelCurrent >= m_eLevelApp) { TCHAR szDateString[128]; SYSTEMTIME cur; ::GetLocalTime(&cur); CString sLevel(CString("DTWEAN").GetAt(m_eLevelCurrent)); sprintf(szDateString, "%4d/%02d/%02d %02d:%02d:%02d.%03d @%s ", cur.wYear, cur.wMonth, cur.wDay, cur.wHour, cur.wMinute, cur.wSecond, cur.wMilliseconds, sLevel); Output(szDateString, bTrace); } } // The one and only place the text is written out. void MyLog::Output(const TCHAR* pszData, bool bTrace /* = true */ ) { _ASSERTE(pszData); if (m_eLevelCurrent >= m_eLevelApp) { if (m_pOutFile && pszData) { m_sLastOutput += pszData; m_pOutFile->write(pszData, _tcslen(pszData)); // In Release mode. #ifndef _DEBUG m_pOutFile->flush(); #endif // If we're debugging and want to send this on the "Build, Settings" // menu item, "C/C++" tab, "Preprocessor" combobox item, "Preprocessor // Definitions" edit. #ifdef _MYLOGFLUSH m_pOutFile->flush(); #endif if (bTrace) { if (512 <= lstrlen(pszData)) { _ASSERTE(! "Too long to TRACE - will be truncated"); TCHAR szDataNew[512] = { '\0' }; lstrcpyn(szDataNew, pszData, 512 - 1); TRACE(szDataNew); } else { TRACE(pszData); } } } } } // View the log. void MyLog::ViewLog() const { m_pOutFile->flush(); int nReturn(reinterpret_cast (ShellExecute(AfxGetMainWnd()->GetSafeHwnd(), "open", m_sPath, /* lpctstrParameters */ NULL, /* lpcstrsDirectory */ NULL, SW_SHOWMAXIMIZED))); if (32 < nReturn) { ; } else { // Browse for ERROR_FILE_NOT_FOUND, for example. _ASSERTE(! "ShellExecute() failed!"); TRACE(" *** ShellExecute() failed with return code <%d>\n", nReturn); } } // Return the location of the log file. CString MyLog::GetLogPath() const { return m_sPath; } // Pop the level-setting dialog box. MyLog::Level MyLog::EnableLoggingDlg(CWnd* pwndParent, const CString& sRegPlacementsKey) { ASSERT_VALID(pwndParent); _ASSERTE(! sRegPlacementsKey.IsEmpty()); MyLogDlg dlg(pwndParent, sRegPlacementsKey); dlg.m_eLevel = m_eLevelApp; if (IDOK == dlg.DoModal()) { SetLevel(dlg.m_eLevel); } return dlg.m_eLevel; } ///////////////////////////////////////////////////////////////////////////// // MyLogDlg dialog // Constructor. MyLogDlg::MyLogDlg(CWnd* pParent, const CString& sRegPlacementsKey) : MyDialog(MyLogDlg::IDD, "MyLogDlg", sRegPlacementsKey, pParent) { //{{AFX_DATA_INIT(MyLogDlg) //}}AFX_DATA_INIT } // void MyLogDlg::DoDataExchange(CDataExchange* pDX) { MyDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(MyLogDlg) DDX_Control(pDX, IDOK, m_buttonOk); DDX_Control(pDX, IDCANCEL, m_buttonCancel); DDX_Control(pDX, IDC_STATIC_MYLOG_WARNS, m_staticWarns); DDX_Control(pDX, IDC_STATIC_MYLOG_TRACE, m_staticTrace); DDX_Control(pDX, IDC_STATIC_MYLOG_NOLOG, m_staticNolog); DDX_Control(pDX, IDC_STATIC_MYLOG_ERROR, m_staticError); DDX_Control(pDX, IDC_STATIC_MYLOG_DEBUG, m_staticDebug); DDX_Control(pDX, IDC_STATIC_MYLOG_AUDIT, m_staticAudit); DDX_Control(pDX, IDC_SLIDER_MYLOG, m_slider); DDX_Control(pDX, IDC_STATIC_MYLOG_DESC, m_staticDesc); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(MyLogDlg, MyDialog) //{{AFX_MSG_MAP(MyLogDlg) ON_WM_HSCROLL() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MyLogDlg message handlers // WM_INITDIALOG. BOOL MyLogDlg::OnInitDialog() { VALIDATE; // You MUST set the controls before you call the base OnInitDialog! SetControlInfo(IDC_SLIDER_MYLOG, ANCHOR_RIGHT | ANCHOR_BOTTOM); SetControlInfo(IDC_STATIC_MYLOG_LESSLOGGING, ANCHOR_LEFT | ANCHOR_BOTTOM); SetControlInfo(IDC_STATIC_MYLOG_FASTER, ANCHOR_LEFT | ANCHOR_BOTTOM); SetControlInfo(IDC_STATIC_MYLOG_MORELOGGING, ANCHOR_RIGHT | ANCHOR_BOTTOM); SetControlInfo(IDC_STATIC_MYLOG_SLOWER, ANCHOR_RIGHT | ANCHOR_BOTTOM); SetControlInfo(IDC_STATIC_MYLOG_DESC, ANCHOR_LEFT | ANCHOR_BOTTOM | RESIZE_HORZ); SetControlInfo(IDC_STATIC_MYLOG_NOLOG, ANCHOR_RIGHT | ANCHOR_BOTTOM | RESIZE_VERT); SetControlInfo(IDC_STATIC_MYLOG_AUDIT, ANCHOR_RIGHT | ANCHOR_BOTTOM | RESIZE_VERT); SetControlInfo(IDC_STATIC_MYLOG_ERROR, ANCHOR_RIGHT | ANCHOR_BOTTOM | RESIZE_VERT); SetControlInfo(IDC_STATIC_MYLOG_WARNS, ANCHOR_RIGHT | ANCHOR_BOTTOM | RESIZE_VERT); SetControlInfo(IDC_STATIC_MYLOG_TRACE, ANCHOR_RIGHT | ANCHOR_BOTTOM | RESIZE_VERT); SetControlInfo(IDC_STATIC_MYLOG_DEBUG, ANCHOR_RIGHT | ANCHOR_BOTTOM | RESIZE_VERT); MyDialog::OnInitDialog(); // Set labels. m_staticNolog.SetFontBold(true); m_staticAudit.SetFontBold(true); m_staticError.SetFontBold(true); m_staticWarns.SetFontBold(true); m_staticTrace.SetFontBold(true); m_staticDebug.SetFontBold(true); m_staticNolog.SetTextColor(RGB_WHITE); m_staticNolog.SetBkColor(RGB_BLACK); m_staticAudit.SetBkColor(RGB_WHITE); m_staticError.SetBkColor(RGB_RED); m_staticWarns.SetBkColor(RGB_YELLOW); m_staticTrace.SetBkColor(RGB_GRAY); m_staticDebug.SetBkColor(RGB_GRAY_DARK); // Set buttons. m_buttonOk.SetIcon(IDI_MYBUTTON_OK, 16, 16); m_buttonCancel.SetIcon(IDI_MYBUTTON_CANCEL, 16, 16); // Set slider. // Level: Debug = 0, Trace = 1, Warns = 2, Error = 3, Audit = 4, Nolog = 5 // Slider: Nolog = 0, Audit = 1, Error = 2, Warns = 3, Trace = 4, Debug = 5 m_slider.SetLineSize(1); m_slider.SetPageSize(1); m_slider.SetRange(0, 5); m_slider.SetPos(abs(m_eLevel - 5)); // Set description. m_staticDesc.SetBkColor(RGB_WHITE); m_staticDesc.SetFontBold(true); // Set the appropriate labels visible or not. UpdateLabels(); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } // WM_HSCROLL. void MyLogDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { VALIDATE; UpdateLabels(); MyDialog::OnHScroll(nSBCode, nPos, pScrollBar); } // Make the labels to the left of the current position visible. void MyLogDlg::UpdateLabels() { VALIDATE; // Make the lables appear, or not. MyLog::Level eSetLevel( static_cast (abs(m_slider.GetPos() - 5))); m_staticNolog.ShowWindow(eSetLevel == MyLog::Nolog ? SW_SHOW : SW_HIDE); m_staticAudit.ShowWindow(eSetLevel <= MyLog::Audit ? SW_SHOW : SW_HIDE); m_staticError.ShowWindow(eSetLevel <= MyLog::Error ? SW_SHOW : SW_HIDE); m_staticWarns.ShowWindow(eSetLevel <= MyLog::Warns ? SW_SHOW : SW_HIDE); m_staticTrace.ShowWindow(eSetLevel <= MyLog::Trace ? SW_SHOW : SW_HIDE); m_staticDebug.ShowWindow(eSetLevel <= MyLog::Debug ? SW_SHOW : SW_HIDE); // Update the description. switch (eSetLevel) { case MyLog::Nolog: m_staticDesc.SetText(Generic::LoadString(IDS_MYLOG_NOLOG_DESC)); break; case MyLog::Audit: m_staticDesc.SetText(Generic::LoadString(IDS_MYLOG_AUDIT_DESC)); break; case MyLog::Error: m_staticDesc.SetText(Generic::LoadString(IDS_MYLOG_ERROR_DESC)); break; case MyLog::Warns: m_staticDesc.SetText(Generic::LoadString(IDS_MYLOG_WARNS_DESC)); break; case MyLog::Trace: m_staticDesc.SetText(Generic::LoadString(IDS_MYLOG_TRACE_DESC)); break; case MyLog::Debug: m_staticDesc.SetText(Generic::LoadString(IDS_MYLOG_DEBUG_DESC)); break; } } // User clicked the OK button. void MyLogDlg::OnOK() { VALIDATE; UpdateData(true); m_eLevel = static_cast (abs(m_slider.GetPos() - 5)); MyDialog::OnOK(); }