forked from aya/aya
635 lines
16 KiB
C++
635 lines
16 KiB
C++
#ifndef AYA_STUDIO
|
|
|
|
#undef min
|
|
#undef max
|
|
|
|
#include "format_string.hpp"
|
|
#include "AyaFormat.hpp"
|
|
#include "Debug.hpp"
|
|
#include "boost.hpp"
|
|
#include "Utility/StandardOut.hpp"
|
|
#include "Utility/FileSystem.hpp"
|
|
#include "Utility/Guid.hpp"
|
|
#include "Utility/Http.hpp"
|
|
#include "Utility/Statistics.hpp"
|
|
|
|
#include "debugAssert.hpp"
|
|
#include <direct.h>
|
|
|
|
#include "atltime.h"
|
|
#include "atlfile.h"
|
|
|
|
#include "TaskScheduler.hpp"
|
|
#include "DumpErrorUploader.hpp"
|
|
#include "Log.hpp"
|
|
#include "FastLog.hpp"
|
|
#include <Windows.h>
|
|
#include <DbgHelp.h>
|
|
#include <string.h>
|
|
#include <atlbase.h>
|
|
#include <boost/format.hpp>
|
|
|
|
LOGGROUP(CrashReporterInit)
|
|
|
|
bool LogManager::logsEnabled = false; // to be honest we only really need crash dmps & the logs outputted are not working
|
|
|
|
MainLogManager* LogManager::mainLogManager = NULL;
|
|
|
|
Aya::mutex MainLogManager::fastLogChannelsLock;
|
|
|
|
static const ATL::CPath& DoGetPath()
|
|
{
|
|
static ATL::CPath path(CString(Aya::FileSystem::getUserDirectory(true, Aya::DirAppData, "logs").native().c_str()));
|
|
return path;
|
|
}
|
|
|
|
|
|
void InitPath()
|
|
{
|
|
DoGetPath();
|
|
}
|
|
|
|
std::string GetAppVersion()
|
|
{
|
|
CVersionInfo vi;
|
|
FASTLOG1(FLog::CrashReporterInit, "Getting app version, module handle: %p", _AtlBaseModule.m_hInst);
|
|
vi.Load(_AtlBaseModule.m_hInst);
|
|
return vi.GetFileVersionAsString();
|
|
}
|
|
|
|
const ATL::CPath& LogManager::GetLogPath() const
|
|
{
|
|
static boost::once_flag flag = BOOST_ONCE_INIT;
|
|
boost::call_once(&InitPath, flag);
|
|
return DoGetPath();
|
|
}
|
|
|
|
const std::string LogManager::GetLogPathString() const
|
|
{
|
|
CStringA path = (LPCTSTR)GetLogPath();
|
|
return std::string(path.GetString());
|
|
}
|
|
|
|
void MainLogManager::fastLogMessage(FLog::Channel id, const char* message)
|
|
{
|
|
Aya::mutex::scoped_lock lock(fastLogChannelsLock);
|
|
|
|
if (mainLogManager)
|
|
{
|
|
if (id >= mainLogManager->fastLogChannels.size())
|
|
mainLogManager->fastLogChannels.resize(id + 1, NULL);
|
|
|
|
if (mainLogManager->fastLogChannels[id] == NULL)
|
|
{
|
|
|
|
mainLogManager->fastLogChannels[id] = new Aya::Log(mainLogManager->getFastLogFileName(id).c_str(), "Log Channel");
|
|
}
|
|
|
|
mainLogManager->fastLogChannels[id]->writeEntry(Aya::Log::Information, message);
|
|
}
|
|
}
|
|
|
|
std::string MainLogManager::getSessionId()
|
|
{
|
|
std::string id = guid;
|
|
return id;
|
|
}
|
|
|
|
std::string MainLogManager::getCrashEventName()
|
|
{
|
|
#ifdef WIN32
|
|
FASTLOG(FLog::CrashReporterInit, "Getting crash event name");
|
|
std::string path = GetLogPathString();
|
|
|
|
std::string fileName = "log_";
|
|
fileName += getSessionId();
|
|
fileName += " ";
|
|
|
|
fileName += GetAppVersion();
|
|
fileName += crashEventExtention;
|
|
|
|
path.append(fileName);
|
|
|
|
return path;
|
|
#endif
|
|
|
|
return "";
|
|
}
|
|
|
|
std::string MainLogManager::getLogFileName()
|
|
{
|
|
#ifdef WIN32
|
|
std::string path = GetLogPathString();
|
|
|
|
std::string fileName = "log_";
|
|
fileName += getSessionId();
|
|
fileName += ".txt";
|
|
|
|
path.append(fileName);
|
|
|
|
return path;
|
|
#endif
|
|
return "";
|
|
}
|
|
|
|
std::string MainLogManager::getFastLogFileName(FLog::Channel channelId)
|
|
{
|
|
#ifdef WIN32
|
|
std::string path = GetLogPathString();
|
|
std::string filename = Aya::format("log_%s_%d.txt", getSessionId().c_str(), channelId);
|
|
|
|
path.append(filename);
|
|
|
|
return path;
|
|
#endif
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
std::string MainLogManager::MakeLogFileName(const char* postfix)
|
|
{
|
|
#ifdef WIN32
|
|
std::string path = GetLogPathString();
|
|
|
|
std::string fileName = "log_";
|
|
fileName += getSessionId();
|
|
fileName += postfix;
|
|
fileName += ".txt";
|
|
|
|
path.append(fileName);
|
|
|
|
return path;
|
|
#endif
|
|
return "";
|
|
}
|
|
|
|
std::string ThreadLogManager::getLogFileName()
|
|
{
|
|
#ifdef WIN32
|
|
std::string fileName = mainLogManager->getLogFileName();
|
|
std::string id = Aya::format("_%s_%d", name.c_str(), threadID);
|
|
fileName.insert(fileName.size() - 4, id);
|
|
return fileName;
|
|
#endif
|
|
return "";
|
|
}
|
|
|
|
Aya::Log* LogManager::getLog()
|
|
{
|
|
if (!logsEnabled)
|
|
return NULL;
|
|
if (log == NULL)
|
|
{
|
|
log = new Aya::Log(getLogFileName().c_str(), name.c_str());
|
|
// TODO: delete an old log that isn't in use
|
|
}
|
|
return log;
|
|
}
|
|
|
|
|
|
Aya::Log* MainLogManager::provideLog()
|
|
{
|
|
if (GetCurrentThreadId() == threadID)
|
|
return this->getLog();
|
|
|
|
return ThreadLogManager::getCurrent()->getLog();
|
|
}
|
|
|
|
|
|
#include <process.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
|
|
#define MAX_CONSOLE_LINES 250;
|
|
|
|
HANDLE g_hConsoleOut; // Handle to debug console
|
|
|
|
|
|
|
|
RobloxCrashReporter::RobloxCrashReporter(const char* outputPath, const char* appName, const char* crashExtention)
|
|
{
|
|
controls.minidumpType = MiniDumpWithDataSegs;
|
|
|
|
controls.minidumpType |= MiniDumpWithIndirectlyReferencedMemory;
|
|
|
|
// null terminate just in case long paths & make safe
|
|
strncpy(controls.pathToMinidump, outputPath, sizeof(controls.pathToMinidump) - 1);
|
|
controls.pathToMinidump[sizeof(controls.pathToMinidump) - 1] = '\0';
|
|
|
|
strncpy(controls.appName, appName, sizeof(controls.appName) - 1);
|
|
controls.appName[sizeof(controls.appName) - 1] = '\0';
|
|
|
|
strncpy(controls.appVersion, GetAppVersion().c_str(), sizeof(controls.appVersion) - 1);
|
|
controls.appVersion[sizeof(controls.appVersion) - 1] = '\0';
|
|
|
|
strncpy(controls.crashExtention, crashExtention, sizeof(controls.crashExtention) - 1);
|
|
controls.crashExtention[sizeof(controls.crashExtention) - 1] = '\0';
|
|
}
|
|
|
|
bool RobloxCrashReporter::silent;
|
|
|
|
LONG RobloxCrashReporter::ProcessException(struct _EXCEPTION_POINTERS* info, bool noMsg)
|
|
{
|
|
LogManager::ReportEvent(EVENTLOG_INFORMATION_TYPE, "StartProcessException...");
|
|
|
|
LONG result = __super::ProcessException(info, noMsg);
|
|
static bool showedMessage = silent;
|
|
if (!showedMessage && !noMsg)
|
|
{
|
|
showedMessage = true;
|
|
::MessageBoxA(NULL, "An unexpected error occurred and " AYA_PROJECT_NAME " needs to quit. We're sorry!", AYA_PROJECT_NAME " Crash", MB_OK);
|
|
}
|
|
|
|
LogManager::ReportEvent(EVENTLOG_INFORMATION_TYPE, "DoneProcessException");
|
|
|
|
LogManager::ReportEvent(EVENTLOG_INFORMATION_TYPE, "Uploading .crashevent...");
|
|
DumpErrorUploader::UploadCrashEventFile(info);
|
|
LogManager::ReportEvent(EVENTLOG_INFORMATION_TYPE, "Done uploading .crashevent...");
|
|
|
|
return result;
|
|
}
|
|
|
|
void RobloxCrashReporter::logEvent(const char* msg)
|
|
{
|
|
LogManager::ReportEvent(EVENTLOG_INFORMATION_TYPE, msg);
|
|
}
|
|
|
|
void MainLogManager::WriteCrashDump()
|
|
{
|
|
std::string appName = "log_";
|
|
appName += getSessionId();
|
|
crashReporter.reset(new RobloxCrashReporter(GetLogPathString().c_str(), appName.c_str(), crashExtention));
|
|
crashReporter->Start();
|
|
};
|
|
|
|
bool MainLogManager::CreateFakeCrashDump()
|
|
{
|
|
if (!crashReporter)
|
|
{
|
|
// start the service if not started.
|
|
WriteCrashDump();
|
|
}
|
|
|
|
// First, write FastLog
|
|
char dumpFilepath[_MAX_PATH];
|
|
if (FAILED(crashReporter->GenerateDmpFileName(dumpFilepath, _MAX_PATH, true)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FLog::WriteFastLogDump(dumpFilepath, 2000);
|
|
|
|
if (FAILED(crashReporter->GenerateDmpFileName(dumpFilepath, _MAX_PATH)))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
HANDLE hFile = CreateFileA(dumpFilepath, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DWORD cb;
|
|
WriteFile(hFile, "Fake", 5, &cb, NULL);
|
|
|
|
CloseHandle(hFile);
|
|
return true;
|
|
}
|
|
|
|
void MainLogManager::EnableImmediateCrashUpload(bool enabled)
|
|
{
|
|
if (crashReporter)
|
|
{
|
|
crashReporter->EnableImmediateUpload(enabled);
|
|
}
|
|
}
|
|
|
|
void MainLogManager::DisableHangReporting()
|
|
{
|
|
if (crashReporter)
|
|
{
|
|
crashReporter->DisableHangReporting();
|
|
}
|
|
}
|
|
|
|
void MainLogManager::NotifyFGThreadAlive()
|
|
{
|
|
if (crashReporter)
|
|
{
|
|
#if 0
|
|
// for debugging only:
|
|
static int alivecount = 0;
|
|
if(alivecount++ % 60 == 0)
|
|
{
|
|
CString eventMessage;
|
|
eventMessage.Format("FGAlive %d", alivecount);
|
|
LogManager::ReportEvent(EVENTLOG_INFORMATION_TYPE, eventMessage);
|
|
}
|
|
#endif
|
|
|
|
crashReporter->NotifyAlive();
|
|
}
|
|
}
|
|
|
|
|
|
static void purecallHandler(void)
|
|
{
|
|
#ifdef _DEBUG
|
|
_CrtDbgBreak();
|
|
#endif
|
|
// Cause a crash
|
|
AYACRASH();
|
|
}
|
|
|
|
MainLogManager::MainLogManager(LPCTSTR productName, const char* crashExtention, const char* crashEventExtention)
|
|
: LogManager("Aya")
|
|
, crashExtention(crashExtention)
|
|
, crashEventExtention(crashEventExtention)
|
|
, gameState(MainLogManager::GameState::UN_INITIALIZED)
|
|
{
|
|
Aya::Guid::generateRBXGUID(guid);
|
|
|
|
AYAASSERT(mainLogManager == NULL);
|
|
mainLogManager = this;
|
|
|
|
Aya::Log::setLogProvider(this);
|
|
|
|
Aya::setAssertionHook(&MainLogManager::handleDebugAssert);
|
|
Aya::setFailureHook(&MainLogManager::handleFailure);
|
|
|
|
_set_purecall_handler(purecallHandler);
|
|
|
|
FLog::SetExternalLogFunc(fastLogMessage);
|
|
}
|
|
|
|
MainLogManager* LogManager::getMainLogManager()
|
|
{
|
|
return mainLogManager;
|
|
}
|
|
|
|
|
|
ThreadLogManager::ThreadLogManager()
|
|
: LogManager(Aya::get_thread_name())
|
|
{
|
|
}
|
|
|
|
ThreadLogManager::~ThreadLogManager() {}
|
|
|
|
static float getThisYearTimeInMinutes(SYSTEMTIME time)
|
|
{
|
|
return (time.wMonth * 43829.0639f) + (time.wDay * 1440) + (time.wHour * 60) + time.wMinute;
|
|
}
|
|
|
|
MainLogManager::~MainLogManager()
|
|
{
|
|
Aya::mutex::scoped_lock lock(fastLogChannelsLock);
|
|
|
|
FLog::SetExternalLogFunc(NULL);
|
|
|
|
for (std::size_t i = 0; i < fastLogChannels.size(); i++)
|
|
delete fastLogChannels[i];
|
|
|
|
mainLogManager = NULL;
|
|
}
|
|
|
|
|
|
LogManager::~LogManager()
|
|
{
|
|
if (log != NULL)
|
|
{
|
|
std::string logFile = log->logFile;
|
|
delete log; // this will close the file so that we can move it
|
|
log = NULL;
|
|
}
|
|
}
|
|
|
|
inline HRESULT WINAPI RbxReportError(
|
|
const CLSID& clsid, LPCSTR lpszDesc, DWORD dwHelpID, LPCSTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0)
|
|
{
|
|
ATLASSERT(lpszDesc != NULL);
|
|
if (lpszDesc == NULL)
|
|
return E_POINTER;
|
|
|
|
USES_CONVERSION_EX;
|
|
CString strDesc(lpszDesc);
|
|
CComBSTR desc = strDesc.AllocSysString(); // Convert CString to BSTR
|
|
if (desc == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
CComBSTR helpFile = NULL;
|
|
if (lpszHelpFile != NULL)
|
|
{
|
|
CString strHelpFile(lpszHelpFile);
|
|
helpFile = strHelpFile.AllocSysString(); // Convert CString to BSTR
|
|
if (helpFile == NULL)
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
return AtlSetErrorInfo(clsid, desc.Detach(), dwHelpID, helpFile.Detach(), iid, hRes, NULL);
|
|
}
|
|
|
|
inline HRESULT WINAPI RbxReportError(
|
|
const CLSID& clsid, UINT nID, const IID& iid = GUID_NULL, HRESULT hRes = 0, HINSTANCE hInst = _AtlBaseModule.GetResourceInstance())
|
|
{
|
|
return AtlSetErrorInfo(clsid, (LPCOLESTR)MAKEINTRESOURCE(nID), 0, NULL, iid, hRes, hInst);
|
|
}
|
|
|
|
inline HRESULT WINAPI RbxReportError(const CLSID& clsid, UINT nID, DWORD dwHelpID, LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL,
|
|
HRESULT hRes = 0, HINSTANCE hInst = _AtlBaseModule.GetResourceInstance())
|
|
{
|
|
return AtlSetErrorInfo(clsid, (LPCOLESTR)MAKEINTRESOURCE(nID), dwHelpID, lpszHelpFile, iid, hRes, hInst);
|
|
}
|
|
|
|
inline HRESULT WINAPI RbxReportError(const CLSID& clsid, LPCSTR lpszDesc, const IID& iid = GUID_NULL, HRESULT hRes = 0)
|
|
{
|
|
return RbxReportError(clsid, lpszDesc, 0, NULL, iid, hRes);
|
|
}
|
|
|
|
inline HRESULT WINAPI RbxReportError(const CLSID& clsid, LPCOLESTR lpszDesc, const IID& iid = GUID_NULL, HRESULT hRes = 0)
|
|
{
|
|
return AtlSetErrorInfo(clsid, lpszDesc, 0, NULL, iid, hRes, NULL);
|
|
}
|
|
|
|
inline HRESULT WINAPI RbxReportError(
|
|
const CLSID& clsid, LPCOLESTR lpszDesc, DWORD dwHelpID, LPCOLESTR lpszHelpFile, const IID& iid = GUID_NULL, HRESULT hRes = 0)
|
|
{
|
|
return AtlSetErrorInfo(clsid, lpszDesc, dwHelpID, lpszHelpFile, iid, hRes, NULL);
|
|
}
|
|
|
|
HRESULT LogManager::ReportCOMError(const CLSID& clsid, LPCOLESTR lpszDesc, HRESULT hRes)
|
|
{
|
|
return RbxReportError(clsid, lpszDesc, GUID_NULL, hRes);
|
|
}
|
|
|
|
HRESULT LogManager::ReportCOMError(const CLSID& clsid, LPCSTR lpszDesc, HRESULT hRes)
|
|
{
|
|
return RbxReportError(clsid, lpszDesc, GUID_NULL, hRes);
|
|
}
|
|
|
|
HRESULT LogManager::ReportCOMError(const CLSID& clsid, HRESULT hRes)
|
|
{
|
|
std::string message = Aya::format("HRESULT 0x%X", hRes);
|
|
LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, message.c_str());
|
|
return RbxReportError(clsid, message.c_str(), GUID_NULL, hRes);
|
|
}
|
|
|
|
#ifdef _MFC_VER
|
|
HRESULT LogManager::ReportCOMError(const CLSID& clsid, CException* exception)
|
|
{
|
|
CString fullError;
|
|
HRESULT hr = COleException::Process(exception);
|
|
CString sError;
|
|
if (exception->GetErrorMessage(sError.GetBuffer(1024), 1023))
|
|
{
|
|
sError.ReleaseBuffer();
|
|
fullError.Format("%s (0x%X)", sError, hr);
|
|
}
|
|
else
|
|
fullError.Format("Error 0x%X", hr);
|
|
|
|
LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, fullError);
|
|
return RbxReportError(clsid, fullError, GUID_NULL, hr);
|
|
}
|
|
#endif
|
|
|
|
bool MainLogManager::handleG3DDebugAssert(
|
|
const char* _expression, const std::string& message, const char* filename, int lineNumber, bool useGuiPrompt)
|
|
{
|
|
return handleDebugAssert(_expression, filename, lineNumber);
|
|
}
|
|
|
|
bool MainLogManager::handleDebugAssert(const char* expression, const char* filename, int lineNumber)
|
|
{
|
|
#ifdef _DEBUG
|
|
LogManager::ReportEvent(EVENTLOG_WARNING_TYPE,
|
|
std::string("Assertion failed: " + std::string(expression) + "\n" + std::string(filename) + "(" + std::to_string(lineNumber) + ")").c_str());
|
|
AYACRASH();
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
bool MainLogManager::handleG3DFailure(const char* _expression, const std::string& message, const char* filename, int lineNumber, bool useGuiPrompt)
|
|
{
|
|
return handleFailure(_expression, filename, lineNumber);
|
|
}
|
|
bool MainLogManager::handleFailure(const char* expression, const char* filename, int lineNumber)
|
|
{
|
|
#ifdef _DEBUG
|
|
_CrtDbgBreak();
|
|
#endif
|
|
// Cause a crash
|
|
AYACRASH();
|
|
return false;
|
|
}
|
|
|
|
HRESULT LogManager::ReportExceptionAsCOMError(const CLSID& clsid, std::exception const& exp)
|
|
{
|
|
return ReportCOMError(clsid, exp.what());
|
|
}
|
|
|
|
void LogManager::ReportException(std::exception const& exp)
|
|
{
|
|
Aya::StandardOut::singleton()->print(Aya::MESSAGE_ERROR, exp);
|
|
}
|
|
|
|
void LogManager::ReportLastError(LPCSTR message)
|
|
{
|
|
DWORD error = GetLastError();
|
|
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "%s, GetLastError=%d", message, error);
|
|
}
|
|
|
|
void LogManager::ReportEvent(WORD type, LPCSTR message)
|
|
{
|
|
switch (type)
|
|
{
|
|
case EVENTLOG_SUCCESS:
|
|
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "%s", message);
|
|
break;
|
|
case EVENTLOG_ERROR_TYPE:
|
|
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "%s", message);
|
|
break;
|
|
case EVENTLOG_INFORMATION_TYPE:
|
|
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "%s", message);
|
|
break;
|
|
case EVENTLOG_AUDIT_SUCCESS:
|
|
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "%s", message);
|
|
break;
|
|
case EVENTLOG_AUDIT_FAILURE:
|
|
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_ERROR, "%s", message);
|
|
break;
|
|
}
|
|
#ifdef _DEBUG
|
|
switch (type)
|
|
{
|
|
case EVENTLOG_SUCCESS:
|
|
ATLTRACE("EVENTLOG_SUCCESS %s\n", message);
|
|
break;
|
|
case EVENTLOG_ERROR_TYPE:
|
|
ATLTRACE("EVENTLOG_ERROR_TYPE %s\n", message);
|
|
break;
|
|
case EVENTLOG_INFORMATION_TYPE:
|
|
ATLTRACE("EVENTLOG_INFORMATION_TYPE %s\n", message);
|
|
break;
|
|
case EVENTLOG_AUDIT_SUCCESS:
|
|
ATLTRACE("EVENTLOG_AUDIT_SUCCESS %s\n", message);
|
|
break;
|
|
case EVENTLOG_AUDIT_FAILURE:
|
|
ATLTRACE("EVENTLOG_AUDIT_FAILURE %s\n", message);
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void LogManager::ReportEvent(WORD type, LPCSTR message, LPCSTR fileName, int lineNumber)
|
|
{
|
|
// CString m;
|
|
// m.Format(convert_s2w("%s\n%s(%d)"), message, fileName, lineNumber);
|
|
// LogManager::ReportEvent(type, m);
|
|
}
|
|
|
|
#ifdef _MFC_VER
|
|
void LogManager::ReportEvent(WORD type, HRESULT hr, LPCSTR fileName, int lineNumber)
|
|
{
|
|
COleException e;
|
|
e.m_sc = hr;
|
|
TCHAR s[1024];
|
|
e.GetErrorMessage(s, 1024);
|
|
|
|
CString m;
|
|
m.Format("HRESULT = %d: %s\n%s(%d)", hr, s, fileName, lineNumber);
|
|
LogManager::ReportEvent(type, m);
|
|
}
|
|
|
|
#endif
|
|
|
|
namespace log_detail
|
|
{
|
|
boost::once_flag once_init = BOOST_ONCE_INIT;
|
|
static boost::thread_specific_ptr<ThreadLogManager>* ts;
|
|
void init(void)
|
|
{
|
|
static boost::thread_specific_ptr<ThreadLogManager> value;
|
|
ts = &value;
|
|
}
|
|
} // namespace log_detail
|
|
|
|
ThreadLogManager* ThreadLogManager::getCurrent()
|
|
{
|
|
boost::call_once(log_detail::init, log_detail::once_init);
|
|
ThreadLogManager* logManager = log_detail::ts->get();
|
|
if (!logManager)
|
|
{
|
|
logManager = new ThreadLogManager();
|
|
log_detail::ts->reset(logManager);
|
|
}
|
|
return logManager;
|
|
}
|
|
|
|
#endif |