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

////////////////////////////////////////////////////////////////////
// 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));
}
/*----------------------------------------------------------------*/