// Created by Larry Leonard, Definitive Solutions, Inc. #include "stdafx.h" #include #include "MyAtlLog.h" #include "GenericAtl.h" ///////////////////////////////////////////////////////////////////////////// // MyAtlLog - created as a static member of the GenericAtl class. // Constructor. MyAtlLog::MyAtlLog() : m_LoggingLevel(SEV_NOLOG) , m_pspXMLDOMDocument(NULL) { } // Destructor. You cannot log in here, as this is called after WinMain exits. /* virtual */ MyAtlLog::~MyAtlLog() { } // This MUST be called first after the constructor. Because this object is // a static member of another class, it's constructor is called before // CoInitialixe() is called - which is a problem, because it uses COM. So, // we have to have this separate Initialize() method, so that callers can // call this when they are sure the COM has been initialized (like in the // app's InitInstance() or WinMain() implementation). HRESULT MyAtlLog::Initialize() { VALIDATE; HRESULT hr(E_FAIL); // Get the EXE's filename. TCHAR szFileName[MAX_PATH * sizeof(TCHAR)]; DWORD dwReturn(GetModuleFileName( /* hModule */ NULL, szFileName, MAX_PATH)); ATLASSERT(0 < dwReturn); ATLASSERT(0 < _tcslen(szFileName)); // Name the log after the EXE's filename. m_strFileName = szFileName; int nLen(m_strFileName.length()); m_strFileName = m_strFileName.substr(0, nLen - 3); m_strFileName += "LOG.XML"; // See if the file is too big (4 Meg); if so, delete it. _fsize_t n64MaxLogSize(1024 * 1024 * 4); DEBUG_ONLY(n64MaxLogSize /= 16); // 256K _fsize_t n64FileSize(GenericAtl::GetFileSize(m_strFileName)); if (n64MaxLogSize < n64FileSize) { VERIFY(GenericAtl::RemoveFile(m_strFileName)); } // Create the smart pointer. Can't be done in the constructor because // we are a static object. m_pspXMLDOMDocument = new IXMLDOMDocumentPtr(__uuidof(DOMDocument)); // "When set to True (the default setting), the load method returns control // to the caller before the download is finished." Not what we want here. hr = (*m_pspXMLDOMDocument)->put_async(VARIANT_FALSE); if (SUCCEEDED(hr)) { _variant_t varURL; varURL.SetString(m_strFileName.data()); VARIANT_BOOL varResult((*m_pspXMLDOMDocument)->load(varURL)); if (VARIANT_TRUE == varResult) { ATLTRACE(" XMLFile::load() ok for log <%s>.\n", m_strFileName.data()); hr = S_OK; } else { // This just means the log file doesn't exist yet - no problem. ATLTRACE(" m_spXMLDOCDocument->load() failed for path <%s> with XML Parse Error <%s> - probably just means the log didn't exist yet.\n", m_strFileName.data(), GenericAtl::GetXMLDOMErrorString((*m_pspXMLDOMDocument))); hr = S_FALSE; } // Always enabled in _DEBUG mode. DEBUG_ONLY(SetLoggingLevel(Debug)); // Mark when the application starts. Header(__FILE__, __LINE__); Log(SEV_AUDIT, "Startup -----------------------------------------------------"); } else { ATLTRACE(" (*m_pspXMLDOMDocument)->put_async() failed with HRESULT <%x: %s>\n", hr, GenericAtl::GetHrErrorString(hr)); ATLASSERT(! "(*m_pspXMLDOMDocument)->put_async() failed!"); } return hr; } // Optional, but nice to have. HRESULT MyAtlLog::Shutdown() { VALIDATE; HRESULT hr(S_OK); if (m_pspXMLDOMDocument) { // Should be called when the application ends. Header(__FILE__, __LINE__); Log(SEV_AUDIT, "Shutdown ----------------------------------------------------"); // This is the only guaranteed save to disk. We don't want to check the // error here. hr = SaveDOMToDisk(); ATLASSERT(SUCCEEDED(hr)); // This cannot be done in the destructor - COM is dead by then (because // we are a static object. delete m_pspXMLDOMDocument; m_pspXMLDOMDocument = NULL; } return hr; } // You might want to turn it off for speed. void MyAtlLog::SetLoggingLevel(Severity sev) { VALIDATE; if (SEV_NOLOG == sev) { Header(__FILE__, __LINE__); Log(SEV_AUDIT, "Logging disabled."); } m_LoggingLevel = sev; } // Must clear it after sending so that it's empty the next time. string MyAtlLog::GetLastBuffer() { VALIDATE; string strReturn(m_strLastBuffer); m_strLastBuffer.erase(); return strReturn; } // Stores the file, line, date/time header. void MyAtlLog::Header(const string& strFile, DWORD dwLine) { VALIDATE; ATLASSERT(! strFile.empty()); ATLASSERT(0 < dwLine); // Populate the structure. m_Msg.strFile = GenericAtl::FileFromPath(strFile); m_Msg.dwLine = dwLine; m_Msg.dwThreadId = GetCurrentThreadId(); m_Msg.dwProcessId = GetCurrentProcessId(); // Do the timestamp. TCHAR szTS[128]; SYSTEMTIME cur; ::GetLocalTime(&cur); wsprintf(szTS, "%4d/%02d/%02d %02d:%02d:%02d.%03d", cur.wYear, cur.wMonth, cur.wDay, cur.wHour, cur.wMinute, cur.wSecond, cur.wMilliseconds); m_Msg.strTimestamp = szTS; // This is a good place to empty this from the *last* call. m_strLastBuffer.empty(); } // Store the variable-arg message in a member string via the insertion ops. void MyAtlLog::Log(Severity sev, const TCHAR* pszFormatString, ...) { VALIDATE; ATLASSERT(pszFormatString); if (pszFormatString) { if (m_LoggingLevel >= sev) { m_Msg.sev = sev; va_list argList; va_start(argList, pszFormatString); const TCHAR* ptr = pszFormatString; while (*ptr) { TCHAR* str = NULL; int nInteger(0); unsigned int unInt(0U); long lLong(0); unsigned long ulLong(0U); double dDoub(0.0); char cChar(NULL); if ('%' == *ptr) { switch (*(ptr+1)) { case 'p': { nInteger = va_arg(argList, int); strstream tmp1; tmp1 << hex << nInteger / 65536 << ends; strstream tmp2; tmp2 << hex << nInteger % 65536 << ends; *this << tmp1.str() << ":" << tmp2.str(); ptr++; break; } case 'x': { nInteger = va_arg(argList, int); strstream tmp; tmp << hex << nInteger << ends; *this << "0x" << tmp.str(); ptr++; break; } case 's': str = va_arg(argList, TCHAR*); if(! str) break; *this << str; ptr++; break; case 'c': cChar = va_arg(argList, char); *this << cChar; 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: ATLASSERT(! "Unhandled MyAtlLog format-specifier type!"); *this << *ptr; } } else { *this << *ptr; } ptr++; } va_end(argList); InsertMsgToDOM(); // Needed to make TRACEs appear. DEBUG_ONLY(::Sleep(2)); } } } // Insertion operator. MyAtlLog& MyAtlLog::operator <<(unsigned int unVal) { VALIDATE; strstream tmp; tmp << unVal; tmp << '\0'; TCHAR* pszText = tmp.str(); m_Msg.strMsg += pszText; tmp.freeze(false); return *this; } // Insertion operator. MyAtlLog& MyAtlLog::operator <<(long lVal) { VALIDATE; strstream tmp; tmp << lVal; tmp << '\0'; TCHAR* pszText = tmp.str(); m_Msg.strMsg += pszText; tmp.freeze(false); return *this; } // Insertion operator. MyAtlLog& MyAtlLog::operator <<(const TCHAR* str) { VALIDATE; m_Msg.strMsg += str; return *this; } // Insertion operator. MyAtlLog& MyAtlLog::operator <<(TCHAR tch) { VALIDATE; TCHAR szCh[2]; szCh[0] = tch; szCh[1] = '\0'; m_Msg.strMsg += szCh; return *this; } // Insertion operator. MyAtlLog& MyAtlLog::operator <<(int nVal) { VALIDATE; strstream tmp; tmp << nVal; tmp << '\0'; TCHAR* pszText = tmp.str(); m_Msg.strMsg += pszText; tmp.freeze(false); return *this; } // Insertion operator. MyAtlLog& MyAtlLog::operator <<(unsigned long ulVal) { VALIDATE; strstream tmp; tmp << ulVal; tmp << '\0'; TCHAR* pszText = tmp.str(); m_Msg.strMsg += pszText; tmp.freeze(false); return *this; } // Insertion operator. MyAtlLog& MyAtlLog::operator <<(double dVal) { VALIDATE; strstream tmp; tmp << dVal; tmp << '\0'; TCHAR* pszText = tmp.str(); m_Msg.strMsg += pszText; tmp.freeze(false); return *this; } // The one and only place the text is written out to the DOM. HRESULT MyAtlLog::InsertMsgToDOM() { VALIDATE; // We don't assert here, as someone may (incorrectly) try to log before // Initialize() has been called. //ATLASSERT(m_pspXMLDOMDocument); HRESULT hr(S_OK); if (m_pspXMLDOMDocument) { // Get the root element. IXMLDOMElementPtr pRootElement; hr = (*m_pspXMLDOMDocument)->get_documentElement(&pRootElement); if (S_FALSE == hr) { ATLASSERT(NULL == pRootElement && "Expected if hr is S_FALSE"); // Create the root element if need be. pRootElement = (*m_pspXMLDOMDocument)->createElement(_T("MYATLLOG")); ATLASSERT(NULL != pRootElement); if (pRootElement) { // Set attributes of the root. // Now insert the root element at the end of the (empty) DOM. VARIANT vAfter; vAfter.vt = VT_EMPTY; IXMLDOMNodePtr pNodeRoot((*m_pspXMLDOMDocument)-> insertBefore(pRootElement, vAfter)); ATLASSERT(NULL != pNodeRoot); if (pNodeRoot) { hr = S_OK; } else { hr = E_FAIL; DOMYLOG (SEV_ERROR, "Null pointer for variable(s): <%s>", "pNodeRoot"); ATLASSERT(! "Null pointer!"); } } else { hr = E_FAIL; DOMYLOG (SEV_ERROR, "Null pointer for variable(s): <%s>", "pRootElement"); ATLASSERT(! "Null pointer!"); } } // Only continue if adding the root element succeeded. if (SUCCEEDED(hr)) { ATLASSERT(NULL != pRootElement); // Store most recently logged message. m_strLastBuffer = m_Msg.strMsg; // Can only TRACE 512 chars at once. We make it smaller just to make // it easier to read in the Debug Window in MSVC, and in XMLTree. int nMaxChunk(255); for (int nX = 0; nX < m_Msg.strMsg.length(); nX += nMaxChunk) { // Create our substring. string strSub(m_Msg.strMsg); strSub = strSub.substr( /* nStart */ nX, /* nLen */ nMaxChunk); // Insert an element to the end of the DOM. hr = AppendMessageNodeToDOM(pRootElement, strSub); ATLASSERT(SUCCEEDED(hr)); ATLTRACE(" "); ATLTRACE(strSub.data()); ATLTRACE("\n"); } // We *always* write to disk after every message in Release Mode. // We *only* do so in Debug Mode *if* the #ifndef is commented out. #ifndef _DEBUG hr = SaveDOMToDisk(); ATLASSERT(SUCCEEDED(hr)); #endif } } // Clear it for next time (whether it succeeded or not). m_Msg.strMsg.erase(); return hr; } // Insert an element to the end of the DOM. HRESULT MyAtlLog::AppendMessageNodeToDOM(IXMLDOMElementPtr& pRootElement, const string& strMsg) { VALIDATE; HRESULT hr(E_FAIL); // Create the node object for this Message. IXMLDOMElementPtr pMsgElement((*m_pspXMLDOMDocument)->createElement(_T("MSG"))); ATLASSERT(NULL != pMsgElement); if (pMsgElement) { // Set this Element node's name (the message). CComBSTR bstrText(strMsg.data()); hr = pMsgElement->put_text(bstrText); ATLASSERT(SUCCEEDED(hr)); // Set this Element's attributes. CComBSTR bstrAttr; _variant_t vValue; bstrAttr = _T("stamp"); vValue = m_Msg.strTimestamp.data(); hr = pMsgElement->setAttribute(bstrAttr.m_str, vValue); ATLASSERT(SUCCEEDED(hr)); bstrAttr = _T("file"); vValue = m_Msg.strFile.data(); hr = pMsgElement->setAttribute(bstrAttr.m_str, vValue); ATLASSERT(SUCCEEDED(hr)); bstrAttr = _T("line"); vValue = static_cast (m_Msg.dwLine); hr = pMsgElement->setAttribute(bstrAttr.m_str, vValue); ATLASSERT(SUCCEEDED(hr)); bstrAttr = _T("process"); vValue = static_cast (m_Msg.dwProcessId); hr = pMsgElement->setAttribute(bstrAttr.m_str, vValue); ATLASSERT(SUCCEEDED(hr)); bstrAttr = _T("thread"); vValue = static_cast (m_Msg.dwThreadId); hr = pMsgElement->setAttribute(bstrAttr.m_str, vValue); ATLASSERT(SUCCEEDED(hr)); bstrAttr = _T("severity"); vValue = GetStringForSeverity(m_Msg.sev).data(); hr = pMsgElement->setAttribute(bstrAttr.m_str, vValue); ATLASSERT(SUCCEEDED(hr)); // Now insert this node at the end. VARIANT vAfter; vAfter.vt = VT_EMPTY; IXMLDOMNodePtr pNodeMsg(pRootElement->insertBefore(pMsgElement, vAfter)); ATLASSERT(NULL != pNodeMsg); if (pNodeMsg) { hr = S_OK; } else { DOMYLOG (SEV_ERROR, "Null pointer for variable(s): <%s>", "pNodeMsg"); ATLASSERT(! "Null pointer!"); } } else { DOMYLOG (SEV_ERROR, "Null pointer for variable(s): <%s>", "pMsgElement"); ATLASSERT(! "Null pointer!"); } return hr; } // Write the DOM to the disk. HRESULT MyAtlLog::SaveDOMToDisk() { VALIDATE; ATLTRACE(" MyAtlLog::SaveDOMToDisk() starting for path <%s>\n", m_strFileName.data()); _variant_t varURL; varURL.SetString(m_strFileName.data()); HRESULT hr((*m_pspXMLDOMDocument)->save(varURL)); if (SUCCEEDED(hr)) { ATLTRACE(" MyAtlLog::SaveDOMToDisk() ok for path <%s>\n", m_strFileName.data()); } else { ATLTRACE(" m_spXMLDOCDocument->save() failed for path <%s> with XML Parse Error <%s>\n", m_strFileName.data(), GenericAtl::GetXMLDOMErrorString((*m_pspXMLDOMDocument))); ATLASSERT(! "m_spXMLDOCDocument->save() failed!"); } return hr; } // Translate to English. /* static */ string MyAtlLog::GetStringForSeverity(Severity sev) { // Not for static members: VALIDATE; string str; switch (sev) { case Audit: str = _T("Audit"); break; case Error: str = _T("Error"); break; case Warns: str = _T("Warns"); break; case Debug: str = _T("Debug"); break; case NoLog: str = _T("NoLog"); break; default: str = _T("*** PROBLEM *** "); ATLASSERT(! "Illegal default case!"); break; } return str; }