polserver/pol-core/clib/logfacility.cpp
2019-02-10 14:00:55 +01:00

553 lines
16 KiB
C++

/*
logging stuff
basic idea is to have an extra thread which performs all the file operations
messages get send on deconstruction the fmt::Writer is used
the sinks are fixed defined which removes a bit the flexibility, but makes the code cleaner
and new sinks can be easily added.
Usage:
POLLOG << "my text\n"
POLLOG.Format("hello {}") << "world";
*/
#include "logfacility.h"
#include <chrono>
#include <fstream>
#include <functional>
#include <iostream>
#include <mutex>
#include <string.h>
#include <thread>
#include "Header_Windows.h"
#include "clib.h"
#include "message_queue.h"
namespace Pol
{
namespace Clib
{
bool LogfileTimestampEveryLine = false;
namespace Logging
{
bool LogFacility::_vsDebuggerPresent = false;
bool LogSinkGenericFile::_disabled = false;
// helper struct to define log file behaviour
struct LogFileBehaviour
{
std::string basename;
bool rollover;
std::ios_base::openmode openmode;
bool timestamps;
};
// definitions of the logfile behaviours
static LogFileBehaviour startlogBehaviour = {"log/start", false,
std::ios_base::out | std::ios_base::trunc, false};
static LogFileBehaviour pollogBehaviour = {"log/pol", true, std::ios_base::out | std::ios_base::app,
true};
static LogFileBehaviour debuglogBehaviour = {"log/debug", false,
std::ios_base::out | std::ios_base::app, false};
static LogFileBehaviour scriptlogBehaviour = {"log/script", false,
std::ios_base::out | std::ios_base::app, false};
static LogFileBehaviour leaklogBehaviour = {"log/leak", false,
std::ios_base::out | std::ios_base::app, false};
static LogFileBehaviour flexlogBehaviour = {"", // dummy name
false, std::ios_base::out | std::ios_base::app, false};
// due to a bug in VS a global cannot thread join in the deconstructor
// thats why this is only a pointer and owned somewhere in main
LogFacility* global_logger = nullptr;
void initLogging( LogFacility* logger )
{
global_logger = logger;
// on start check if Visual Studio is attached
// if so print cout and cerr msgs also in the VS console
#if defined( WINDOWS )
LogFacility::_vsDebuggerPresent = IsDebuggerPresent();
#endif
}
// internal worker class which performs the work in a additional thread
class LogFacility::LogWorker
{
typedef std::function<void()> msg;
typedef message_queue<msg> msg_queue;
public:
// run thread on construction
LogWorker() : _done( false ), _queue(), _work_thread() { run(); }
LogWorker( const LogWorker& ) = delete;
LogWorker& operator=( const LogWorker& ) = delete;
// on deconstruction send exit
~LogWorker()
{
if ( !_done )
{
exit();
}
}
// blocks till the queue is empty
void exit()
{
send( [&]() { _done = true; } );
_work_thread.join(); // wait for it
}
// send msg into queue
void send( const msg& msg_ ) { _queue.push( msg_ ); }
private:
// endless loop in thread
void run()
{
_work_thread = std::thread( [&]() {
while ( !_done )
{
try
{
msg func;
_queue.pop_wait( &func );
func(); // execute
}
catch ( std::exception& msg )
{
std::cout << msg.what() << std::endl;
}
}
} );
}
bool _done;
msg_queue _queue;
std::thread _work_thread;
};
LogFacility::LogFacility() : _worker( new LogWorker ) {}
// note this blocks till the worker is finished
LogFacility::~LogFacility()
{
_worker->exit();
for ( auto& sink : _registered_sinks )
delete sink;
global_logger = nullptr;
}
// send logsink as a lambda to the worker
template <typename Sink>
void LogFacility::save( fmt::Writer* message, const std::string& id )
{
_worker->send( [message, id]() {
std::unique_ptr<fmt::Writer> msg( message );
try
{
getSink<Sink>()->addMessage( msg.get(), id );
}
catch ( std::exception& msg )
{
std::cout << msg.what() << std::endl;
}
} );
}
// register sink for later deconstruction
void LogFacility::registerSink( LogSink* sink )
{
_registered_sinks.push_back( sink );
}
// disables debuglog
void LogFacility::disableDebugLog()
{
_worker->send( []() { getSink<LogSink_debuglog>()->disable(); } );
}
void LogFacility::disableFileLog()
{
LogSinkGenericFile::_disabled = true;
}
// disables startlog ( activates pol.log )
void LogFacility::deinitializeStartLog()
{
_worker->send( []() { getSink<LogSink_pollog>()->deinitialize_startlog(); } );
}
// closes flex sink of given id
void LogFacility::closeFlexLog( const std::string& id )
{
_worker->send( [id]() { getSink<LogSink_flexlog>()->close( id ); } );
}
// register new flex sink with given filename
// blocks to return unique identifier
std::string LogFacility::registerFlexLogger( const std::string& logfilename, bool open_timestamp )
{
auto promise = std::make_shared<std::promise<std::string>>();
auto ret = promise->get_future();
_worker->send( [=]() {
try
{
promise->set_value( getSink<LogSink_flexlog>()->create( logfilename, open_timestamp ) );
}
catch ( ... )
{
promise->set_exception( std::current_exception() );
}
} );
return ret.get(); // block wait till valid
}
// block waits till the queue is empty
void LogFacility::wait_for_empty_queue()
{
auto promise = std::make_shared<std::promise<bool>>();
auto ret = promise->get_future();
_worker->send( [=]() { promise->set_value( true ); } );
ret.get(); // block wait till valid
}
// Message default construct
template <typename Sink>
Message<Sink>::Message() : _formater( new fmt::Writer() ), _id( "" )
{
}
template <typename Sink>
Message<Sink>::Message( const std::string& id ) : _formater( new fmt::Writer() ), _id( id )
{
}
// directly fill on construction the formater with file, line and function
template <typename Sink>
Message<Sink>::Message( const std::string& file, const int line, const std::string& function )
: _formater( new fmt::Writer() ), _id( "" )
{
*_formater << file << "[" << line << "] " << function << " : ";
}
// directly fill on construction the formater with file, line and function
template <typename Sink>
Message<Sink>::Message( const std::string& id, const std::string& file, const int line,
const std::string& function )
: _formater( new fmt::Writer() ), _id( id )
{
*_formater << file << "[" << line << "] " << function << " : ";
}
// on deconstruction transfer the formater to the facility
template <typename Sink>
Message<Sink>::~Message()
{
if ( _formater->size() > 0 )
{
if ( global_logger == nullptr )
printf( "%s", _formater->c_str() );
else
global_logger->save<Sink>( _formater.release(), _id );
}
}
// create and get a sink
template <typename Sink>
Sink* getSink()
{
// does not need to be threadsafe since its only executed inside the worker thread
// with later vc its automatically threadsafe (magic statics)
static std::once_flag flag;
static Sink* sink = new Sink();
std::call_once( flag, []( Sink* s ) { global_logger->registerSink( s ); }, sink );
return sink;
}
// first construction also opens the file
LogSinkGenericFile::LogSinkGenericFile( const LogFileBehaviour* behaviour )
: LogSink(),
_behaviour( behaviour ),
_log_filename( behaviour->basename + ".log" ),
_active_line( false )
{
memset( &_opened, 0, sizeof( _opened ) );
open_log_file( true );
}
// default constructor does not open directly
LogSinkGenericFile::LogSinkGenericFile()
: LogSink(), _behaviour(), _log_filename(), _active_line( false )
{
memset( &_opened, 0, sizeof( _opened ) );
}
LogSinkGenericFile::~LogSinkGenericFile()
{
if ( _filestream.is_open() )
{
_filestream.flush();
_filestream.close();
}
}
// set behaviour and logfilename, does not work with rollover
void LogSinkGenericFile::setBehaviour( const LogFileBehaviour* behaviour, std::string filename )
{
_behaviour = behaviour;
_log_filename = std::move( filename );
}
// open file
void LogSinkGenericFile::open_log_file( bool open_timestamp )
{
if ( _disabled )
return;
_filestream.open( _log_filename, _behaviour->openmode );
if ( !_filestream.is_open() )
{
fmt::Writer tmp;
tmp << "failed to open logfile " << _log_filename << "\n";
getSink<LogSink_cerr>()->addMessage( &tmp );
return;
}
if ( open_timestamp )
{
addTimeStamp( _filestream );
_filestream << "Logfile opened." << std::endl;
}
_opened = Clib::localtime( std::chrono::system_clock::to_time_t(
std::chrono::system_clock::now() ) ); // mark current time for later possible rollover
_active_line = false;
}
// print given msg into filestream
void LogSinkGenericFile::addMessage( fmt::Writer* msg )
{
if ( _disabled )
{
std::cerr << msg->str();
std::cerr.flush();
return;
}
if ( !_filestream.is_open() )
return;
if ( !msg->size() )
return;
if ( !_active_line ) // only rollover or add timestamp if there is currently no open line
{
using std::chrono::system_clock;
std::chrono::time_point<system_clock> now = system_clock::now();
if ( now != _lasttimestamp )
{
if ( !test_for_rollover( now ) )
return;
if ( _behaviour->timestamps )
addTimeStamp( _filestream );
}
else if ( _behaviour->timestamps && Clib::LogfileTimestampEveryLine )
addTimeStamp( _filestream );
}
_active_line = ( msg->data()[msg->size() - 1] != '\n' ); // is the last character a newline?
_filestream << msg->str();
_filestream.flush();
}
void LogSinkGenericFile::addMessage( fmt::Writer* msg, const std::string& )
{
addMessage( msg );
}
// check if a rollover is needed (new day)
bool LogSinkGenericFile::test_for_rollover(
std::chrono::time_point<std::chrono::system_clock>& now )
{
auto tm_now = Clib::localtime( std::chrono::system_clock::to_time_t( now ) );
if ( _behaviour->rollover &&
( tm_now.tm_mday != _opened.tm_mday || tm_now.tm_mon != _opened.tm_mon ) )
{
// roll the log file over
char buffer[30];
strftime( buffer, sizeof buffer, "%Y-%m-%d", &_opened );
std::string archive_name = _behaviour->basename + "-" + buffer + ".log";
_filestream.flush();
_filestream.close();
// whether the rename succeeds or fails, the action is the same.
rename( _log_filename.c_str(), archive_name.c_str() );
// moved. open the new file
_filestream.open( _log_filename, _behaviour->openmode );
if ( !_filestream.is_open() )
return false;
_opened = tm_now;
}
return true;
}
LogSink_cout::LogSink_cout() : LogSink() {}
// print given msg into std::cout
void LogSink_cout::addMessage( fmt::Writer* msg )
{
std::cout << msg->str();
std::cout.flush();
#if defined( WINDOWS )
if ( LogFacility::_vsDebuggerPresent )
OutputDebugString( msg->c_str() );
#endif
}
void LogSink_cout::addMessage( fmt::Writer* msg, const std::string& )
{
addMessage( msg );
}
LogSink_cerr::LogSink_cerr() : LogSink() {}
// print given msg into std::cerr
void LogSink_cerr::addMessage( fmt::Writer* msg )
{
std::cerr << msg->str();
std::cerr.flush();
#if defined( WINDOWS )
if ( LogFacility::_vsDebuggerPresent )
OutputDebugString( msg->c_str() );
#endif
}
void LogSink_cerr::addMessage( fmt::Writer* msg, const std::string& )
{
addMessage( msg );
}
// on construction this opens not pol.log instead start.log
LogSink_pollog::LogSink_pollog() : LogSinkGenericFile( &startlogBehaviour ) {}
// performs the switch between start.log and pol.log
void LogSink_pollog::deinitialize_startlog()
{
_filestream.flush();
_filestream.close();
_behaviour = &pollogBehaviour;
_log_filename = _behaviour->basename + ".log";
open_log_file( true );
}
// on construction opens script.log
LogSink_scriptlog::LogSink_scriptlog() : LogSinkGenericFile( &scriptlogBehaviour ) {}
// on construction opens debug.log
LogSink_debuglog::LogSink_debuglog() : LogSinkGenericFile( &debuglogBehaviour ) {}
// debug.log can be disabled
void LogSink_debuglog::disable()
{
if ( _filestream.is_open() )
{
_filestream.flush();
_filestream.close();
}
Disabled = true;
}
// only print the msg if not Disabled
void LogSink_debuglog::addMessage( fmt::Writer* msg )
{
if ( !Disabled )
LogSinkGenericFile::addMessage( msg );
}
void LogSink_debuglog::addMessage( fmt::Writer* msg, const std::string& )
{
addMessage( msg );
}
// on construction opens leak.log
LogSink_leaklog::LogSink_leaklog() : LogSinkGenericFile( &leaklogBehaviour ) {}
LogSink_flexlog::LogSink_flexlog() : LogSink() {}
// create and open new logfile with given name, returns unique id
std::string LogSink_flexlog::create( std::string logfilename, bool open_timestamp )
{
auto itr = _logfiles.find( logfilename );
if ( itr != _logfiles.end() )
return logfilename;
_logfiles[logfilename] = std::make_shared<LogSinkGenericFile>();
auto log = _logfiles.at( logfilename );
log->setBehaviour( &flexlogBehaviour, logfilename );
log->open_log_file( open_timestamp );
return logfilename;
}
// sink msg into sink of given id
void LogSink_flexlog::addMessage( fmt::Writer* msg, const std::string& id )
{
auto itr = _logfiles.find( id );
if ( itr != _logfiles.end() )
{
itr->second->addMessage( msg );
}
}
void LogSink_flexlog::addMessage( fmt::Writer* /*msg*/ )
{
// empty
}
// closes logfile of given id
void LogSink_flexlog::close( const std::string& id )
{
auto itr = _logfiles.find( id );
if ( itr != _logfiles.end() )
_logfiles.erase( itr );
}
template <typename log1, typename log2>
LogSink_dual<log1, log2>::LogSink_dual() : LogSink()
{
}
// performs the sink with given msg for both sinks
template <typename log1, typename log2>
void LogSink_dual<log1, log2>::addMessage( fmt::Writer* msg )
{
getSink<log1>()->addMessage( msg );
getSink<log2>()->addMessage( msg );
}
template <typename log1, typename log2>
void LogSink_dual<log1, log2>::addMessage( fmt::Writer* msg, const std::string& )
{
addMessage( msg );
}
} // namespace Logging
} // namespace Clib
bool Clib::Logging::LogSink_debuglog::Disabled = false;
} // namespace Pol
// forward define the templates
// dont want to add all the templates into the header
// could reduce the compilation time a bit
#define SINK_TEMPLATE_DEFINES( sink ) \
template class Pol::Clib::Logging::Message<Pol::Clib::Logging::sink>; \
template Pol::Clib::Logging::sink* Pol::Clib::Logging::getSink<Pol::Clib::Logging::sink>(); \
template void Pol::Clib::Logging::LogFacility::save<Pol::Clib::Logging::sink>( \
fmt::Writer * message, const std::string& id );
#define SINK_TEMPLATE_DEFINES_DUAL( sink1, sink2 ) \
template class Pol::Clib::Logging::Message< \
Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>>; \
template Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>* \
Pol::Clib::Logging::getSink< \
Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>>(); \
template void Pol::Clib::Logging::LogFacility::save< \
Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>>( \
fmt::Writer * message, const std::string& id );
SINK_TEMPLATE_DEFINES( LogSink_cout )
SINK_TEMPLATE_DEFINES( LogSink_cerr )
SINK_TEMPLATE_DEFINES( LogSink_pollog )
SINK_TEMPLATE_DEFINES( LogSink_scriptlog )
SINK_TEMPLATE_DEFINES( LogSink_debuglog )
SINK_TEMPLATE_DEFINES( LogSink_leaklog )
SINK_TEMPLATE_DEFINES( LogSink_flexlog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_pollog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_scriptlog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_debuglog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_leaklog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_pollog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_scriptlog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_debuglog )
SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_leaklog )