// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdmisc.h"
#include "nel/misc/log.h"
#ifdef NL_OS_WINDOWS
# include
#else
# include
#endif
#include "nel/misc/displayer.h"
#include "nel/misc/log.h"
#include "nel/misc/debug.h"
#include "nel/misc/path.h"
using namespace std;
#ifdef DEBUG_NEW
#define new DEBUG_NEW
#endif
namespace NLMISC
{
string *CLog::_ProcessName = NULL;
CLog::CLog( TLogType logType) : _LogType (logType), _FileName(NULL), _Line(-1), _FuncName(NULL), _Mutex("LOG"+toString((uint)logType)), _PosSet(false)
{
}
void CLog::setDefaultProcessName ()
{
if (_ProcessName == NULL)
{
_ProcessName = (string *)INelContext::getInstance().getSingletonPointer("NLMISC::CLog::_ProcessName");
if (_ProcessName == NULL)
{
_ProcessName = new string;
INelContext::getInstance().setSingletonPointer("NLMISC::CLog::_ProcessName", _ProcessName);
}
}
#ifdef NL_OS_WINDOWS
if ((*_ProcessName).empty())
{
char name[1024];
GetModuleFileName (NULL, name, 1023);
(*_ProcessName) = CFile::getFilename(name);
}
#else
if ((*_ProcessName).empty())
{
*_ProcessName = "";
}
#endif
}
void CLog::setProcessName (const std::string &processName)
{
if (_ProcessName == NULL)
{
_ProcessName = (string *)INelContext::getInstance().getSingletonPointer("NLMISC::CLog::_ProcessName");
if (_ProcessName == NULL)
{
_ProcessName = new string;
INelContext::getInstance().setSingletonPointer("NLMISC::CLog::_ProcessName", _ProcessName);
}
}
// keep only filename without path
*_ProcessName = CFile::getFilename(processName);
}
void CLog::setPosition (sint line, const char *fileName, const char *funcName)
{
if ( !noDisplayer() )
{
_Mutex.enter();
_PosSet++;
_FileName = fileName;
_Line = line;
_FuncName = funcName;
}
}
/// Symetric to setPosition(). Automatically called by display...(). Do not call if noDisplayer().
void CLog::unsetPosition()
{
nlassert( !noDisplayer() );
if ( _PosSet > 0 )
{
_FileName = NULL;
_Line = -1;
_FuncName = NULL;
_PosSet--;
_Mutex.leave(); // needs setPosition() to have been called
}
}
void CLog::addDisplayer (IDisplayer *displayer, bool bypassFilter)
{
if (displayer == NULL)
{
// Can't nlwarning because recursive call
printf ("Trying to add a NULL displayer\n");
return;
}
if (bypassFilter)
{
CDisplayers::iterator idi = std::find (_BypassFilterDisplayers.begin (), _BypassFilterDisplayers.end (), displayer);
if (idi == _BypassFilterDisplayers.end ())
{
_BypassFilterDisplayers.push_back (displayer);
}
else
{
nlwarning ("LOG: Couldn't add the displayer, it was already added");
}
}
else
{
CDisplayers::iterator idi = std::find (_Displayers.begin (), _Displayers.end (), displayer);
if (idi == _Displayers.end ())
{
_Displayers.push_back (displayer);
}
else
{
nlwarning ("LOG: Couldn't add the displayer, it was already added");
}
}
}
void CLog::removeDisplayer (IDisplayer *displayer)
{
if (displayer == NULL)
{
nlwarning ("LOG: Trying to remove a NULL displayer");
return;
}
CDisplayers::iterator idi = std::find (_Displayers.begin (), _Displayers.end (), displayer);
if (idi != _Displayers.end ())
{
_Displayers.erase (idi);
}
idi = std::find (_BypassFilterDisplayers.begin (), _BypassFilterDisplayers.end (), displayer);
if (idi != _BypassFilterDisplayers.end ())
{
_BypassFilterDisplayers.erase (idi);
}
}
void CLog::removeDisplayer (const char *displayerName)
{
if (displayerName == NULL || displayerName[0] == '\0')
{
nlwarning ("LOG: Trying to remove an empty displayer name");
return;
}
CDisplayers::iterator idi;
for (idi = _Displayers.begin (); idi != _Displayers.end ();)
{
if ((*idi)->DisplayerName == displayerName)
{
idi = _Displayers.erase (idi);
}
else
{
idi++;
}
}
for (idi = _BypassFilterDisplayers.begin (); idi != _BypassFilterDisplayers.end ();)
{
if ((*idi)->DisplayerName == displayerName)
{
idi = _BypassFilterDisplayers.erase (idi);
}
else
{
idi++;
}
}
}
IDisplayer *CLog::getDisplayer (const char *displayerName)
{
if (displayerName == NULL || displayerName[0] == '\0')
{
nlwarning ("LOG: Trying to get an empty displayer name");
return NULL;
}
CDisplayers::iterator idi;
for (idi = _Displayers.begin (); idi != _Displayers.end (); idi++)
{
if ((*idi)->DisplayerName == displayerName)
{
return *idi;
}
}
for (idi = _BypassFilterDisplayers.begin (); idi != _BypassFilterDisplayers.end (); idi++)
{
if ((*idi)->DisplayerName == displayerName)
{
return *idi;
}
}
return NULL;
}
/*
* Returns true if the specified displayer is attached to the log object
*/
bool CLog::attached(IDisplayer *displayer) const
{
return (find( _Displayers.begin(), _Displayers.end(), displayer ) != _Displayers.end()) ||
(find( _BypassFilterDisplayers.begin(), _BypassFilterDisplayers.end(), displayer ) != _BypassFilterDisplayers.end());
}
void CLog::displayString (const char *str)
{
const char *disp = NULL;
TDisplayInfo localargs, *args = NULL;
setDefaultProcessName ();
if(strchr(str,'\n') == NULL)
{
if (TempString.empty())
{
time (&TempArgs.Date);
TempArgs.LogType = _LogType;
TempArgs.ProcessName = *_ProcessName;
TempArgs.ThreadId = getThreadId();
TempArgs.FileName = _FileName;
TempArgs.Line = _Line;
TempArgs.FuncName = _FuncName;
TempArgs.CallstackAndLog = "";
TempString = str;
}
else
{
TempString += str;
}
return;
}
else
{
if (TempString.empty())
{
time (&localargs.Date);
localargs.LogType = _LogType;
localargs.ProcessName = *_ProcessName;
localargs.ThreadId = getThreadId();
localargs.FileName = _FileName;
localargs.Line = _Line;
localargs.FuncName = _FuncName;
localargs.CallstackAndLog = "";
disp = str;
args = &localargs;
}
else
{
TempString += str;
disp = TempString.c_str();
args = &TempArgs;
}
}
// send to all bypass filter displayers
for (CDisplayers::iterator idi=_BypassFilterDisplayers.begin(); idi!=_BypassFilterDisplayers.end(); idi++ )
{
(*idi)->display( *args, disp );
}
// get the log at the last minute to be sure to have everything
if(args->LogType == LOG_ERROR || args->LogType == LOG_ASSERT)
{
getCallStackAndLog (args->CallstackAndLog, 4);
}
if (passFilter (disp))
{
// Send to the attached displayers
for (CDisplayers::iterator idi=_Displayers.begin(); idi!=_Displayers.end(); idi++ )
{
(*idi)->display( *args, disp );
}
}
TempString = "";
unsetPosition();
}
/*
* Display the string with decoration and final new line to all attached displayers
*/
#ifdef NL_OS_WINDOWS
void CLog::_displayNL (const char *format, ...)
#else
void CLog::displayNL (const char *format, ...)
#endif
{
if ( noDisplayer() )
{
return;
}
char *str;
NLMISC_CONVERT_VARGS (str, format, 256/*NLMISC::MaxCStringSize*/);
if (strlen(str)<256/*NLMISC::MaxCStringSize*/-1)
strcat (str, "\n");
else
str[256/*NLMISC::MaxCStringSize*/-2] = '\n';
displayString (str);
}
/*
* Display the string with decoration to all attached displayers
*/
#ifdef NL_OS_WINDOWS
void CLog::_display (const char *format, ...)
#else
void CLog::display (const char *format, ...)
#endif
{
if ( noDisplayer() )
{
return;
}
char *str;
NLMISC_CONVERT_VARGS (str, format, 256/*NLMISC::MaxCStringSize*/);
displayString (str);
}
void CLog::displayRawString (const char *str)
{
const char *disp = NULL;
TDisplayInfo localargs, *args = NULL;
setDefaultProcessName ();
if(strchr(str,'\n') == NULL)
{
if (TempString.empty())
{
localargs.Date = 0;
localargs.LogType = CLog::LOG_NO;
localargs.ProcessName = "";
localargs.ThreadId = 0;
localargs.FileName = NULL;
localargs.Line = -1;
localargs.CallstackAndLog = "";
TempString = str;
}
else
{
TempString += str;
}
return;
}
else
{
if (TempString.empty())
{
localargs.Date = 0;
localargs.LogType = CLog::LOG_NO;
localargs.ProcessName = "";
localargs.ThreadId = 0;
localargs.FileName = NULL;
localargs.Line = -1;
localargs.CallstackAndLog = "";
disp = str;
args = &localargs;
}
else
{
TempString += str;
disp = TempString.c_str();
args = &TempArgs;
}
}
// send to all bypass filter displayers
for (CDisplayers::iterator idi=_BypassFilterDisplayers.begin(); idi!=_BypassFilterDisplayers.end(); idi++ )
{
(*idi)->display( *args, disp );
}
// get the log at the last minute to be sure to have everything
if(args->LogType == LOG_ERROR || args->LogType == LOG_ASSERT)
{
getCallStackAndLog (args->CallstackAndLog, 4);
}
if ( passFilter( disp ) )
{
// Send to the attached displayers
for ( CDisplayers::iterator idi=_Displayers.begin(); idi!=_Displayers.end(); idi++ )
{
(*idi)->display( *args, disp );
}
}
TempString.clear();
unsetPosition();
}
/*
* Display a string (and nothing more) to all attached displayers
*/
#ifdef NL_OS_WINDOWS
void CLog::_displayRawNL( const char *format, ... )
#else
void CLog::displayRawNL( const char *format, ... )
#endif
{
if ( noDisplayer() )
{
return;
}
char *str;
NLMISC_CONVERT_VARGS (str, format, 256/*NLMISC::MaxCStringSize*/);
if (strlen(str)<256/*NLMISC::MaxCStringSize*/-1)
strcat (str, "\n");
else
str[256/*NLMISC::MaxCStringSize*/-2] = '\n';
displayRawString(str);
}
/*
* Display a string (and nothing more) to all attached displayers
*/
#ifdef NL_OS_WINDOWS
void CLog::_displayRaw( const char *format, ... )
#else
void CLog::displayRaw( const char *format, ... )
#endif
{
if ( noDisplayer() )
{
return;
}
char *str;
NLMISC_CONVERT_VARGS (str, format, 256/*NLMISC::MaxCStringSize*/);
displayRawString(str);
}
#ifdef NL_OS_WINDOWS
void CLog::_forceDisplayRaw (const char *format, ...)
#else
void CLog::forceDisplayRaw (const char *format, ...)
#endif
{
if ( noDisplayer() )
{
return;
}
char *str;
NLMISC_CONVERT_VARGS (str, format, 256/*NLMISC::MaxCStringSize*/);
TDisplayInfo args;
CDisplayers::iterator idi;
// send to all bypass filter displayers
for (idi=_BypassFilterDisplayers.begin(); idi!=_BypassFilterDisplayers.end(); idi++ )
{
(*idi)->display( args, str );
}
// Send to the attached displayers
for ( idi=_Displayers.begin(); idi!=_Displayers.end(); idi++ )
{
(*idi)->display( args, str );
}
}
/*
* Returns true if the string must be logged, according to the current filter
*/
bool CLog::passFilter( const char *filter )
{
bool yes = _PositiveFilter.empty();
bool found;
list::iterator ilf;
// 1. Positive filter
for ( ilf=_PositiveFilter.begin(); ilf!=_PositiveFilter.end(); ++ilf )
{
found = ( strstr( filter, (*ilf).c_str() ) != NULL );
if ( found )
{
yes = true; // positive filter passed (no need to check another one)
break;
}
// else try the next one
}
if ( ! yes )
{
return false; // positive filter not passed
}
// 2. Negative filter
for ( ilf=_NegativeFilter.begin(); ilf!=_NegativeFilter.end(); ++ilf )
{
found = ( strstr( filter, (*ilf).c_str() ) != NULL );
if ( found )
{
return false; // negative filter not passed (no need to check another one)
}
}
return true; // negative filter passed
}
/*
* Removes a filter by name. Returns true if it was found.
*/
void CLog::removeFilter( const char *filterstr )
{
if (filterstr == NULL)
{
_PositiveFilter.clear();
_NegativeFilter.clear();
//displayNL ("CLog::addNegativeFilter('%s')", filterstr);
}
else
{
_PositiveFilter.remove( filterstr );
_NegativeFilter.remove( filterstr );
//displayNL ("CLog::removeFilter('%s')", filterstr);
}
}
void CLog::displayFilter( CLog &log )
{
std::list::iterator it;
log.displayNL ("Positive Filter(s):");
for (it = _PositiveFilter.begin (); it != _PositiveFilter.end (); it++)
{
log.displayNL ("'%s'", (*it).c_str());
}
log.displayNL ("Negative Filter(s):");
for (it = _NegativeFilter.begin (); it != _NegativeFilter.end (); it++)
{
log.displayNL ("'%s'", (*it).c_str());
}
}
void CLog::addPositiveFilter( const char *filterstr )
{
//displayNL ("CLog::addPositiveFilter('%s')", filterstr);
_PositiveFilter.push_back( filterstr );
}
void CLog::addNegativeFilter( const char *filterstr )
{
//displayNL ("CLog::addNegativeFilter('%s')", filterstr);
_NegativeFilter.push_back( filterstr );
}
void CLog::resetFilters()
{
//displayNL ("CLog::resetFilter()");
_PositiveFilter.clear();
_NegativeFilter.clear();
}
/// Do not call this unless you know why you're doing it, it kills the debug/log system!
void CLog::releaseProcessName()
{
if (INelContext::isContextInitialised())
{
INelContext::getInstance().releaseSingletonPointer("NLMISC::CLog::_ProcessName", _ProcessName);
}
delete _ProcessName;
_ProcessName = NULL;
}
} // NLMISC