You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
12 KiB
453 lines
12 KiB
//////////////////////////////////////////////////////////////////// |
|
// Log.cpp |
|
// |
|
// Copyright 2007 cDc@seacave |
|
// Distributed under the Boost Software License, Version 1.0 |
|
// (See http://www.boost.org/LICENSE_1_0.txt) |
|
|
|
#include "Common.h" |
|
#include "Log.h" |
|
|
|
using namespace SEACAVE; |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
/*-----------------------------------------------------------* |
|
* Log class implementation * |
|
*-----------------------------------------------------------*/ |
|
|
|
#ifndef DEFAULT_LOGTYPE |
|
Log::LogType Log::g_appType; |
|
#endif |
|
|
|
/** |
|
* Constructor |
|
*/ |
|
Log::Log() |
|
{ |
|
// generate default log type |
|
#ifndef DEFAULT_LOGTYPE |
|
String appName = Util::getAppName(); |
|
appName = (Util::getFileExt(appName) == _T(".exe") ? Util::getFileName(appName) : Util::getFileNameExt(appName)); |
|
Idx n = MINF((Idx)appName.length(), (Idx)LOGTYPE_SIZE); |
|
_tcsncpy(g_appType.szName, appName, n); |
|
while (n < LOGTYPE_SIZE) |
|
g_appType.szName[n++] = _T(' '); |
|
g_appType.szName[LOGTYPE_SIZE] = _T('\0'); |
|
#endif |
|
ResetTypes(); |
|
} |
|
|
|
void Log::RegisterListener(ClbkRecordMsg clbk) |
|
{ |
|
ASSERT(m_arrRecordClbk != NULL); |
|
if (m_arrRecordClbk == NULL) |
|
return; |
|
m_arrRecordClbk->Insert(clbk); |
|
} |
|
void Log::UnregisterListener(ClbkRecordMsg clbk) |
|
{ |
|
ASSERT(m_arrRecordClbk != NULL); |
|
if (m_arrRecordClbk == NULL) |
|
return; |
|
m_arrRecordClbk->Remove(clbk); |
|
} |
|
|
|
//Register a new type of log messages (LOGTYPE_SIZE chars) |
|
Log::Idx Log::RegisterType(LPCTSTR lt) |
|
{ |
|
ASSERT(strlen(lt) == LOGTYPE_SIZE); |
|
const Idx idx = (Idx)m_arrLogTypes.GetSize(); |
|
LogType& logType = m_arrLogTypes.AddEmpty(); |
|
Idx n = MINF((Idx)strlen(lt), (Idx)LOGTYPE_SIZE); |
|
_tcsncpy(logType.szName, lt, n); |
|
while (n < LOGTYPE_SIZE) |
|
logType.szName[n++] = _T(' '); |
|
logType.szName[LOGTYPE_SIZE] = _T('\0'); |
|
return idx; |
|
} |
|
|
|
/** |
|
* Empty the array with registered log types |
|
*/ |
|
void Log::ResetTypes() |
|
{ |
|
m_arrLogTypes.Empty(); |
|
} |
|
|
|
void Log::Write(LPCTSTR szFormat, ...) |
|
{ |
|
if (m_arrRecordClbk == NULL) |
|
return; |
|
va_list args; |
|
va_start(args, szFormat); |
|
_Record(NO_ID, szFormat, args); |
|
va_end(args); |
|
} |
|
void Log::Write(Idx lt, LPCTSTR szFormat, ...) |
|
{ |
|
if (m_arrRecordClbk == NULL) |
|
return; |
|
va_list args; |
|
va_start(args, szFormat); |
|
_Record(lt, szFormat, args); |
|
va_end(args); |
|
} |
|
|
|
/** |
|
* Write message to the log if this exists |
|
* -> IN: Idx - log type |
|
* LPCTSTR - format message |
|
* ... - values |
|
*/ |
|
void Log::_Record(Idx lt, LPCTSTR szFormat, va_list args) |
|
{ |
|
ASSERT(m_arrRecordClbk != NULL); |
|
if (m_arrRecordClbk->IsEmpty()) |
|
return; |
|
|
|
// Format a message by adding the date (auto adds new line) |
|
TCHAR szTime[256]; |
|
TCHAR szBuffer[2048]; |
|
#if defined(LOG_DATE) || defined(LOG_TIME) |
|
TCHAR* szPtrTime = szTime; |
|
#ifdef _MSC_VER |
|
SYSTEMTIME st; |
|
GetLocalTime(&st); |
|
#ifdef LOG_THREAD |
|
WLock l(m_lock); |
|
#endif |
|
#ifdef LOG_DATE |
|
szPtrTime += GetDateFormat(LOCALE_USER_DEFAULT,0,&st,_T("dd'.'MM'.'yy"),szPtrTime,80)-1; |
|
#endif |
|
#if defined(LOG_DATE) && defined(LOG_TIME) |
|
szPtrTime[0] = _T('-'); ++szPtrTime; |
|
#endif |
|
#ifdef LOG_TIME |
|
szPtrTime += GetTimeFormat(LOCALE_USER_DEFAULT,0,&st,_T("HH':'mm':'ss"),szPtrTime,80)-1; |
|
#endif |
|
#else // _MSC_VER |
|
const time_t t = time(NULL); |
|
const struct tm *tmp = localtime(&t); |
|
#ifdef LOG_DATE |
|
szPtrTime += strftime(szPtrTime, 80, "%y.%m.%d", tmp); |
|
#endif |
|
#if defined(LOG_DATE) && defined(LOG_TIME) |
|
szPtrTime[0] = _T('-'); ++szPtrTime; |
|
#endif |
|
#ifdef LOG_TIME |
|
szPtrTime += strftime(szPtrTime, 80, "%H:%M:%S", tmp); |
|
#endif |
|
#endif // _MSC_VER |
|
#endif // LOG_DATE || LOG_TIME |
|
#ifdef DEFAULT_LOGTYPE |
|
LPCTSTR const logType(lt<m_arrLogTypes.GetSize() ? m_arrLogTypes[lt] : (LPCSTR)DEFAULT_LOGTYPE); |
|
#else |
|
LPCTSTR const logType(lt<m_arrLogTypes.GetSize() ? m_arrLogTypes[lt] : g_appType); |
|
#endif |
|
if ((size_t)_vsntprintf(szBuffer, 2048, szFormat, args) > 2048) { |
|
// not enough space for the full string, reprint dynamically |
|
m_message.FormatSafe("%s [%s] %s" LINE_SEPARATOR_STR, szTime, logType, String::FormatStringSafe(szFormat, args).c_str()); |
|
} else { |
|
// enough space for all the string, print directly |
|
m_message.Format("%s [%s] %s" LINE_SEPARATOR_STR, szTime, logType, szBuffer); |
|
} |
|
TRACE(m_message); |
|
|
|
// signal listeners |
|
FOREACHPTR(pClbk, *m_arrRecordClbk) |
|
(*pClbk)(m_message); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/*-----------------------------------------------------------* |
|
* LogFile class implementation * |
|
*-----------------------------------------------------------*/ |
|
|
|
/** |
|
* Constructor |
|
*/ |
|
LogFile::LogFile() |
|
{ |
|
} |
|
|
|
bool LogFile::Open(LPCTSTR logName) |
|
{ |
|
Util::ensureFolder(logName); |
|
m_ptrFile = new File(logName, File::WRITE, File::CREATE | File::TRUNCATE); |
|
if (!m_ptrFile->isOpen()) { |
|
m_ptrFile = NULL; |
|
return false; |
|
} |
|
GET_LOG().RegisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogFile::Record, this)); |
|
return true; |
|
} |
|
void LogFile::Close() |
|
{ |
|
if (m_ptrFile == NULL) |
|
return; |
|
GET_LOG().UnregisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogFile::Record, this)); |
|
m_ptrFile = NULL; |
|
} |
|
|
|
void LogFile::Pause() |
|
{ |
|
if (m_ptrFile == NULL) |
|
return; |
|
GET_LOG().UnregisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogFile::Record, this)); |
|
} |
|
void LogFile::Play() |
|
{ |
|
if (m_ptrFile == NULL) |
|
return; |
|
GET_LOG().RegisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogFile::Record, this)); |
|
} |
|
|
|
void LogFile::Record(const String& msg) |
|
{ |
|
ASSERT(m_ptrFile != NULL); |
|
m_ptrFile->print(msg); |
|
m_ptrFile->flush(); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
/*-----------------------------------------------------------* |
|
* LogConsole class implementation * |
|
*-----------------------------------------------------------*/ |
|
|
|
#define MAX_CONSOLE_WIDTH 100 |
|
#define MAX_CONSOLE_LINES 2000 |
|
|
|
#ifdef _MSC_VER |
|
#include <io.h> |
|
#define STDIN_FILENO 0 /* file descriptor for stdin */ |
|
#define STDOUT_FILENO 1 /* file descriptor for stdout */ |
|
#define STDERR_FILENO 2 /* file descriptor for stderr */ |
|
#else |
|
#include <unistd.h> |
|
#endif |
|
#include <fcntl.h> |
|
|
|
class LogConsoleOutbuf : public std::streambuf { |
|
public: |
|
LogConsoleOutbuf() { |
|
setp(0, 0); |
|
} |
|
|
|
virtual int_type overflow(int_type c = traits_type::eof()) { |
|
return fputc(c, stdout) == EOF ? traits_type::eof() : c; |
|
} |
|
}; |
|
|
|
/** |
|
* Constructor |
|
*/ |
|
LogConsole::LogConsole() |
|
: |
|
#ifdef _USE_COSOLEFILEHANDLES |
|
m_fileIn(NULL), m_fileOut(NULL), m_fileErr(NULL), |
|
#else |
|
m_fileIn(-1), m_fileOut(-1), m_fileErr(-1), |
|
#endif |
|
m_cout(NULL), m_coutOld(NULL), |
|
m_cerr(NULL), m_cerrOld(NULL), |
|
bManageConsole(false) |
|
{ |
|
} |
|
|
|
bool LogConsole::IsOpen() const |
|
{ |
|
#ifdef _USE_COSOLEFILEHANDLES |
|
return (m_fileOut != NULL || m_fileIn != NULL || m_fileErr != NULL); |
|
#else |
|
return (m_fileOut != -1 || m_fileIn != -1 || m_fileErr != -1); |
|
#endif |
|
} |
|
|
|
#ifdef _MSC_VER |
|
|
|
void LogConsole::Open() |
|
{ |
|
if (IsOpen()) |
|
return; |
|
|
|
// allocate a console for this app |
|
bManageConsole = (AllocConsole()!=FALSE?true:false); |
|
|
|
// capture std::cout and std::cerr |
|
if (bManageConsole && !m_cout) { |
|
// set std::cout to use our custom streambuf |
|
m_cout = new LogConsoleOutbuf; |
|
m_coutOld = std::cout.rdbuf(m_cout); |
|
// use same buffer for std::cerr as well |
|
m_cerr = NULL; |
|
m_cerrOld = std::cerr.rdbuf(m_cout); |
|
} |
|
|
|
// set the screen buffer to be big enough to let us scroll text |
|
CONSOLE_SCREEN_BUFFER_INFO coninfo; |
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); |
|
coninfo.dwSize.X = MAX_CONSOLE_WIDTH; // does not resize the console window |
|
coninfo.dwSize.Y = MAX_CONSOLE_LINES; |
|
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); |
|
|
|
#if 1 && defined(_USE_COSOLEFILEHANDLES) |
|
// redirect standard stream to the console |
|
m_fileIn = freopen("CONIN$", "r", stdin); |
|
m_fileOut = freopen("CONOUT$", "w", stdout); |
|
// there isn't any CONERR$, so that we merge stderr into CONOUT$ |
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231%28v=vs.85%29.aspx |
|
m_fileErr = freopen("CONOUT$", "w", stderr); |
|
#elif 1 && defined(_USE_COSOLEFILEHANDLES) |
|
// Fix up the allocated console so that output works in all cases. |
|
// Change std handles to refer to new console handles. Before doing so, ensure |
|
// that stdout/stderr haven't been redirected to a valid file |
|
// See http://support.microsoft.com/kb/105305/en-us |
|
// stdout |
|
if (_fileno(stdout) == -1 || _get_osfhandle(fileno(stdout)) == -1) { |
|
int hCrt = ::_open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT); |
|
if (hCrt != -1) { |
|
m_fileOut = ::_fdopen(hCrt, "w"); |
|
if (m_fileOut) |
|
*stdout = *m_fileOut; |
|
} |
|
} |
|
// stderr |
|
if (_fileno(stderr) == -1 || _get_osfhandle(fileno(stderr)) == -1) { |
|
int hCrt = ::_open_osfhandle((intptr_t)::GetStdHandle(STD_ERROR_HANDLE), _O_TEXT); |
|
if (hCrt != -1) { |
|
m_fileErr = ::_fdopen(hCrt, "w"); |
|
if (m_fileErr) |
|
*stderr = *m_fileErr; |
|
} |
|
} |
|
// stdin |
|
if (_fileno(stdin) == -1 || _get_osfhandle(fileno(stdin)) == -1) { |
|
int hCrt = ::_open_osfhandle((intptr_t)::GetStdHandle(STD_INPUT_HANDLE), _O_TEXT); |
|
if (hCrt != -1) { |
|
m_fileIn = ::_fdopen(hCrt, "r"); |
|
if (m_fileIn) |
|
*stdin = *m_fileIn; |
|
} |
|
} |
|
#else |
|
int hConHandle; |
|
// redirect unbuffered STDIN to the console |
|
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT); |
|
m_fileIn = _dup(STDIN_FILENO); |
|
_dup2(hConHandle, STDIN_FILENO); |
|
_close(hConHandle); |
|
setvbuf(stdin, NULL, _IONBF, 0); |
|
// redirect unbuffered STDOUT to the console |
|
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT); |
|
m_fileOut = _dup(STDOUT_FILENO); |
|
_dup2(hConHandle, STDOUT_FILENO); |
|
_close(hConHandle); |
|
setvbuf(stdout, NULL, _IONBF, 0); |
|
// redirect unbuffered STDERR to the console |
|
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT); |
|
m_fileErr = _dup(STDERR_FILENO); |
|
_dup2(hConHandle, STDERR_FILENO); |
|
_close(hConHandle); |
|
setvbuf(stderr, NULL, _IONBF, 0); |
|
// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog |
|
// point to console as well |
|
std::ios::sync_with_stdio(); |
|
#endif |
|
|
|
// register with our log system |
|
GET_LOG().RegisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogConsole::Record, this)); |
|
} |
|
|
|
void LogConsole::Close() |
|
{ |
|
if (!IsOpen()) |
|
return; |
|
GET_LOG().UnregisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogConsole::Record, this)); |
|
#ifdef _USE_COSOLEFILEHANDLES |
|
// close console stream handles |
|
fclose(m_fileIn); m_fileIn = NULL; |
|
fclose(m_fileOut); m_fileOut = NULL; |
|
fclose(m_fileErr); m_fileErr = NULL; |
|
#else |
|
// restore STDIN |
|
_dup2(m_fileIn, STDIN_FILENO); |
|
_close(m_fileIn); |
|
m_fileIn = -1; |
|
// restore STDOUT |
|
_dup2(m_fileOut, STDOUT_FILENO); |
|
_close(m_fileOut); |
|
m_fileOut = -1; |
|
// restore STDERR |
|
_dup2(m_fileErr, STDERR_FILENO); |
|
_close(m_fileErr); |
|
m_fileErr = -1; |
|
#endif |
|
// close console |
|
if (bManageConsole) { |
|
if (m_cout) { |
|
// set std::cout to the original streambuf |
|
std::cout.rdbuf(m_coutOld); |
|
std::cerr.rdbuf(m_cerrOld); |
|
delete m_cout; m_cout = NULL; |
|
} |
|
FreeConsole(); |
|
} |
|
#ifndef _USE_COSOLEFILEHANDLES |
|
std::ios::sync_with_stdio(); |
|
#endif |
|
} |
|
|
|
void LogConsole::Record(const String& msg) |
|
{ |
|
ASSERT(IsOpen()); |
|
printf(msg); |
|
fflush(stdout); |
|
} |
|
|
|
#else |
|
|
|
void LogConsole::Open() |
|
{ |
|
if (IsOpen()) |
|
return; |
|
++m_fileIn; |
|
// register with our log system |
|
GET_LOG().RegisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogConsole::Record, this)); |
|
} |
|
|
|
void LogConsole::Close() |
|
{ |
|
if (!IsOpen()) |
|
return; |
|
// unregister with our log system |
|
GET_LOG().UnregisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogConsole::Record, this)); |
|
--m_fileIn; |
|
} |
|
|
|
void LogConsole::Record(const String& msg) |
|
{ |
|
ASSERT(IsOpen()); |
|
printf(_T("%s"), msg.c_str()); |
|
fflush(stdout); |
|
} |
|
|
|
#endif // _MSC_VER |
|
|
|
void LogConsole::Pause() |
|
{ |
|
if (IsOpen()) |
|
GET_LOG().UnregisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogConsole::Record, this)); |
|
} |
|
void LogConsole::Play() |
|
{ |
|
if (IsOpen()) |
|
GET_LOG().RegisterListener(DELEGATEBINDCLASS(Log::ClbkRecordMsg, &LogConsole::Record, this)); |
|
} |
|
/*----------------------------------------------------------------*/
|
|
|