// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ============================================================
//
// CDebugLog.cpp
//
//
// Implements the fusion-derived CDebugLog class
//
// ============================================================
#ifdef FEATURE_VERSIONING_LOG
#include "cdebuglog.hpp"
#include "applicationcontext.hpp"
#include "assemblyname.hpp"
#include "variables.hpp"
#include "utils.hpp"
#include "shlwapi.h"
#include "strsafe.h"
#include "../dlls/mscorrc/fusres.h"
#define MAX_DBG_STR_LEN 1024
#define MAX_DATE_LEN 128
#define DEBUG_LOG_HTML_START L"
\r\n"
#define DEBUG_LOG_HTML_META_LANGUAGE L""
#define DEBUG_LOG_MARK_OF_THE_WEB L""
#define DEBUG_LOG_HTML_END L"\r\n
"
#define DEBUG_LOG_NEW_LINE L"\r\n"
namespace BINDER_SPACE
{
namespace
{
inline LPCWSTR LogCategoryToString(DWORD dwLogCategory)
{
switch (dwLogCategory)
{
case FUSION_BIND_LOG_CATEGORY_DEFAULT:
return L"default";
case FUSION_BIND_LOG_CATEGORY_NGEN:
return L"Native";
default:
return L"Unknown";
}
}
HRESULT CreateFilePathHierarchy(LPCOLESTR pszName)
{
HRESULT hr=S_OK;
LPTSTR pszFileName;
PathString szPathString;
DWORD dw = 0;
size_t pszNameLen = wcslen(pszName);
WCHAR * szPath = szPathString.OpenUnicodeBuffer(static_cast(pszNameLen));
size_t cbSzPath = (sizeof(WCHAR)) * (pszNameLen + 1); // SString allocates extra byte for null
IF_FAIL_GO(StringCbCopy(szPath, cbSzPath, pszName));
szPathString.CloseBuffer(static_cast(pszNameLen));
pszFileName = PathFindFileName(szPath);
if (pszFileName <= szPath)
{
IF_FAIL_GO(E_INVALIDARG);
}
*(pszFileName-1) = 0;
dw = WszGetFileAttributes(szPath);
if (dw != INVALID_FILE_ATTRIBUTES)
{
return S_OK;
}
hr = HRESULT_FROM_GetLastError();
switch (hr)
{
case __HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND):
{
hr = CreateFilePathHierarchy(szPath);
if (hr != S_OK)
return hr;
}
case __HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
{
if (WszCreateDirectory(szPath, NULL))
return S_OK;
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
if(hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
hr = S_OK;
else
return hr;
}
}
default:
break;
}
Exit:
return hr;
}
HRESULT WriteLog(HANDLE hLogFile,
LPCWSTR pwzInfo)
{
HRESULT hr = S_OK;
DWORD dwLen = 0;
DWORD dwWritten = 0;
CHAR szBuf[MAX_DBG_STR_LEN];
dwLen = WszWideCharToMultiByte(CP_UTF8,
0,
pwzInfo,
-1,
szBuf,
MAX_DBG_STR_LEN,
NULL,
NULL);
if (!dwLen)
{
IF_FAIL_GO(HRESULT_FROM_GetLastError());
}
// dwLen includes NULL terminator. We don't want to write this out.
if (dwLen > 1) {
dwLen--;
if (!WriteFile(hLogFile, szBuf, dwLen, &dwWritten, NULL)) {
IF_FAIL_GO(HRESULT_FROM_GetLastError());
}
}
Exit:
return hr;
}
HRESULT GetBindTimeInfo(PathString &info)
{
HRESULT hr = S_OK;
SYSTEMTIME systime;
{
WCHAR wzDateBuffer[MAX_DATE_LEN];
WCHAR wzTimeBuffer[MAX_DATE_LEN];
GetLocalTime(&systime);
if (!WszGetDateFormat(LOCALE_USER_DEFAULT,
0,
&systime,
NULL,
wzDateBuffer,
MAX_DATE_LEN))
{
return HRESULT_FROM_GetLastError();
}
if (!WszGetTimeFormat(LOCALE_USER_DEFAULT,
0,
&systime,
NULL,
wzTimeBuffer,
MAX_DATE_LEN))
{
return HRESULT_FROM_GetLastError();
}
info.Printf(L"(%s @ %s)", wzDateBuffer, wzTimeBuffer);
}
return hr;
}
HRESULT GetHrResultInfo(PathString &info, HRESULT hrResult)
{
HRESULT hr = S_OK;
// TODO: Get the result information in here.
info.Printf(L"%p.", hrResult);
return hr;
}
inline BOOL IsInvalidCharacter(WCHAR wcChar)
{
switch (wcChar)
{
case L':':
case L'/':
case L'\\':
case L'*':
case L'<':
case L'>':
case L'?':
case L'|':
case L'"':
return TRUE;
default:
return FALSE;
}
}
inline void ReplaceInvalidFileCharacters(SString &assemblyName)
{
SString::Iterator pos = assemblyName.Begin();
SString::Iterator end = assemblyName.End();
while (pos < end)
{
if (IsInvalidCharacter(pos[0]))
{
assemblyName.Replace(pos, L'_');
}
pos++;
}
}
};
CDebugLog::CDebugLog()
{
m_cRef = 1;
}
CDebugLog::~CDebugLog()
{
// Nothing to do here
}
/* static */
HRESULT CDebugLog::Create(ApplicationContext *pApplicationContext,
AssemblyName *pAssemblyName,
SString &sCodeBase,
CDebugLog **ppCDebugLog)
{
HRESULT hr = S_OK;
BINDER_LOG_ENTER(L"CDebugLog::Create");
ReleaseHolder pDebugLog;
// Validate input arguments
IF_FALSE_GO(pApplicationContext != NULL);
IF_FALSE_GO(ppCDebugLog != NULL);
SAFE_NEW(pDebugLog, CDebugLog);
IF_FAIL_GO(pDebugLog->Init(pApplicationContext, pAssemblyName, sCodeBase));
*ppCDebugLog = pDebugLog.Extract();
Exit:
BINDER_LOG_LEAVE_HR(L"CDebugLog::Create", hr);
return hr;
}
ULONG CDebugLog::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG CDebugLog::Release()
{
ULONG ulRef;
ulRef = InterlockedDecrement(&m_cRef);
if (ulRef == 0)
{
delete this;
}
return ulRef;
}
HRESULT CDebugLog::SetResultCode(DWORD dwLogCategory,
HRESULT hrResult)
{
HRESULT hr = S_OK;
BINDER_LOG_ENTER(L"CDebugLog::SetResultCode");
IF_FALSE_GO(dwLogCategory < FUSION_BIND_LOG_CATEGORY_MAX);
m_HrResult[dwLogCategory] = hrResult;
Exit:
BINDER_LOG_LEAVE_HR(L"CDebugLog::SetResultCode", hr);
return hr;
}
HRESULT CDebugLog::LogMessage(DWORD,
DWORD dwLogCategory,
SString &sDebugString)
{
HRESULT hr = S_OK;
BINDER_LOG_ENTER(L"CDebugLog::LogMessage");
IF_FALSE_GO(dwLogCategory < FUSION_BIND_LOG_CATEGORY_MAX);
m_content[dwLogCategory].AddTail(const_cast(sDebugString));
Exit:
BINDER_LOG_LEAVE_HR(L"CDebugLog::LogMessage", hr);
return hr;
}
HRESULT CDebugLog::Flush(DWORD,
DWORD dwLogCategory)
{
HRESULT hr = S_OK;
BINDER_LOG_ENTER(L"CDebugLog::Flush");
SmallStackSString sCategory(LogCategoryToString(dwLogCategory));
PathString logFilePath;
ListNode *pListNode = NULL;
IF_FALSE_GO(dwLogCategory < FUSION_BIND_LOG_CATEGORY_MAX);
CombinePath(g_BinderVariables->logPath, sCategory, logFilePath);
CombinePath(logFilePath, m_applicationName, logFilePath);
CombinePath(logFilePath, m_logFileName, logFilePath);
BINDER_LOG_STRING(L"logFilePath", logFilePath);
IF_FAIL_GO(CreateFilePathHierarchy(logFilePath.GetUnicode()));
m_hLogFile = WszCreateFile(logFilePath.GetUnicode(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (m_hLogFile == INVALID_HANDLE_VALUE)
{
// Silently ignore unability to log.
BINDER_LOG(L"Unable to open binding log");
GO_WITH_HRESULT(S_OK);
}
LogHeader(dwLogCategory);
pListNode = static_cast *>(m_content[dwLogCategory].GetHeadPosition());
while (pListNode != NULL)
{
SString item = pListNode->GetItem();
IF_FAIL_GO(WriteLog(m_hLogFile, item.GetUnicode()));
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE));
pListNode = pListNode->GetNext();
}
LogFooter(dwLogCategory);
// Ignore failure
CloseHandle(m_hLogFile.Extract());
Exit:
BINDER_LOG_LEAVE_HR(L"CDebugLog::Flush", hr);
return hr;
}
HRESULT CDebugLog::Init(ApplicationContext *pApplicationContext,
AssemblyName *pAssemblyName,
SString &sCodeBase)
{
HRESULT hr = S_OK;
BINDER_LOG_ENTER(L"CDebugLog::Init");
m_applicationName.Set(pApplicationContext->GetApplicationName());
ReplaceInvalidFileCharacters(m_applicationName);
if (m_applicationName.IsEmpty())
{
BINDER_LOG(L"empty application name");
m_applicationName.Set(L"unknown");
}
if (pAssemblyName == NULL)
{
m_logFileName.Set(L"WhereRefBind!Host=(LocalMachine)!FileName=(");
LPCWSTR pwzFileName = PathFindFileNameW(sCodeBase.GetUnicode());
if (pwzFileName != NULL)
{
m_logFileName.Append(pwzFileName);
}
m_logFileName.Append(L").HTM");
}
else
{
PathString assemblyDisplayName;
pAssemblyName->GetDisplayName(assemblyDisplayName,
AssemblyName::INCLUDE_VERSION |
AssemblyName::INCLUDE_ARCHITECTURE |
AssemblyName::INCLUDE_RETARGETABLE);
ReplaceInvalidFileCharacters(assemblyDisplayName);
m_logFileName.Set(assemblyDisplayName);
m_logFileName.Append(L".HTM");
}
BINDER_LOG_LEAVE_HR(L"CDebugLog::Init", hr);
return hr;
}
HRESULT CDebugLog::LogHeader(DWORD dwLogCategory)
{
HRESULT hr = S_OK;
BINDER_LOG_ENTER(L"CDebugLog::LogHeader");
PathString info;
PathString temp;
PathString format;
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_HTML_META_LANGUAGE));
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_MARK_OF_THE_WEB));
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_HTML_START));
IF_FAIL_GO(GetBindTimeInfo(temp));
IF_FAIL_GO(format.LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BEGIN));
info.Printf(format.GetUnicode(), temp.GetUnicode());
IF_FAIL_GO(WriteLog(m_hLogFile, info.GetUnicode()));
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE DEBUG_LOG_NEW_LINE));
if (SUCCEEDED(m_HrResult[dwLogCategory]))
{
IF_FAIL_GO(temp.
LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BIND_RESULT_SUCCESS));
IF_FAIL_GO(WriteLog(m_hLogFile, temp.GetUnicode()));
}
else
{
IF_FAIL_GO(temp.
LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BIND_RESULT_ERROR));
IF_FAIL_GO(WriteLog(m_hLogFile, temp.GetUnicode()));
}
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE));
GetHrResultInfo(temp, m_HrResult[dwLogCategory]);
IF_FAIL_GO(format.LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_BIND_RESULT));
info.Printf(format.GetUnicode(), temp.GetUnicode());
IF_FAIL_GO(WriteLog(m_hLogFile, info.GetUnicode()));
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE DEBUG_LOG_NEW_LINE));
// TODO: Assembly Manager info + Executable info.
IF_FAIL_GO(info.LoadResourceAndReturnHR(CCompRC::Debugging, ID_FUSLOG_BINDING_HEADER_END));
IF_FAIL_GO(WriteLog(m_hLogFile, info.GetUnicode()));
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_NEW_LINE DEBUG_LOG_NEW_LINE));
Exit:
BINDER_LOG_LEAVE_HR(L"CDebugLog::LogHeader", hr);
return hr;
}
HRESULT CDebugLog::LogFooter(DWORD)
{
HRESULT hr = S_OK;
BINDER_LOG_ENTER(L"CDebugLog::LogFooter");
IF_FAIL_GO(WriteLog(m_hLogFile, DEBUG_LOG_HTML_END));
Exit:
BINDER_LOG_LEAVE_HR(L"CDebugLog::LogFooter", hr);
return hr;
}
};
#endif // FEATURE_VERSIONING_LOG