Pol  Revision:cb584c9
logfacility.cpp
Go to the documentation of this file.
1 
2 /*
3 logging stuff
4 basic idea is to have an extra thread which performs all the file operations
5 messages get send on deconstruction the fmt::Writer is used
6 the sinks are fixed defined which removes a bit the flexibility, but makes the code cleaner
7 and new sinks can be easily added.
8 
9 Usage:
10 POLLOG << "my text\n"
11 POLLOG.Format("hello {}") << "world";
12 */
13 
14 #include "logfacility.h"
15 
16 #include <chrono>
17 #include <fstream>
18 #include <functional>
19 #include <iostream>
20 #include <mutex>
21 #include <string.h>
22 #include <thread>
23 
24 #include "clib.h"
25 #include "message_queue.h"
26 #include "Header_Windows.h"
27 
28 namespace Pol
29 {
30 namespace Clib
31 {
33 namespace Logging
34 {
36 
37 // helper struct to define log file behaviour
39 {
40  std::string basename;
41  bool rollover;
42  std::ios_base::openmode openmode;
43  bool timestamps;
44 };
45 
46 // definitions of the logfile behaviours
47 static LogFileBehaviour startlogBehaviour = {"log/start", false,
48  std::ios_base::out | std::ios_base::trunc, false};
49 static LogFileBehaviour pollogBehaviour = {"log/pol", true, std::ios_base::out | std::ios_base::app,
50  true};
51 static LogFileBehaviour debuglogBehaviour = {"log/debug", false,
52  std::ios_base::out | std::ios_base::app, false};
53 static LogFileBehaviour scriptlogBehaviour = {"log/script", false,
54  std::ios_base::out | std::ios_base::app, false};
55 static LogFileBehaviour leaklogBehaviour = {"log/leak", false,
56  std::ios_base::out | std::ios_base::app, false};
57 static LogFileBehaviour flexlogBehaviour = {"", // dummy name
58  false, std::ios_base::out | std::ios_base::app, false};
59 
60 // due to a bug in VS a global cannot thread join in the deconstructor
61 // thats why this is only a pointer and owned somewhere in main
63 void initLogging( LogFacility* logger )
64 {
65  global_logger = logger;
66  // on start check if Visual Studio is attached
67  // if so print cout and cerr msgs also in the VS console
68 #if defined(WINDOWS)
69  LogFacility::_vsDebuggerPresent = IsDebuggerPresent();
70 #endif
71 }
72 
73 // internal worker class which performs the work in a additional thread
74 class LogFacility::LogWorker : boost::noncopyable
75 {
76  typedef std::function<void()> msg;
78 
79 public:
80  // run thread on construction
81  LogWorker() : _done( false ), _queue(), _work_thread() { run(); }
82  // on deconstruction send exit
84  {
85  if ( !_done )
86  {
87  exit();
88  }
89  }
90  // blocks till the queue is empty
91  void exit()
92  {
93  send( [&]() { _done = true; } );
94  _work_thread.join(); // wait for it
95  }
96  // send msg into queue
97  void send( const msg& msg_ ) { _queue.push( msg_ ); }
98 
99 private:
100  // endless loop in thread
101  void run()
102  {
103  _work_thread = std::thread( [&]() {
104  while ( !_done )
105  {
106  try
107  {
108  msg func;
109  _queue.pop_wait( &func );
110  func(); // execute
111  }
112  catch ( std::exception& msg )
113  {
114  std::cout << msg.what() << std::endl;
115  }
116  }
117  } );
118  }
119  bool _done;
120  msg_queue _queue;
121  std::thread _work_thread;
122 };
123 
124 
125 LogFacility::LogFacility() : _worker( new LogWorker ) {}
126 
127 // note this blocks till the worker is finished
129 {
130  _worker->exit();
131  for ( auto& sink : _registered_sinks )
132  delete sink;
133  global_logger = nullptr;
134 }
135 
136 // send logsink as a lambda to the worker
137 template <typename Sink>
138 void LogFacility::save( fmt::Writer* message, const std::string& id )
139 {
140  _worker->send( [message, id]() {
141  std::unique_ptr<fmt::Writer> msg( message );
142  try
143  {
144  getSink<Sink>()->addMessage( msg.get(), id );
145  }
146  catch ( std::exception& msg )
147  {
148  std::cout << msg.what() << std::endl;
149  }
150  } );
151 }
152 
153 // register sink for later deconstruction
155 {
156  _registered_sinks.push_back( sink );
157 }
158 
159 // disables debuglog
161 {
162  _worker->send( []() { getSink<LogSink_debuglog>()->disable(); } );
163 }
164 
165 // disables startlog ( activates pol.log )
167 {
168  _worker->send( []() { getSink<LogSink_pollog>()->deinitialize_startlog(); } );
169 }
170 
171 // closes flex sink of given id
172 void LogFacility::closeFlexLog( const std::string& id )
173 {
174  _worker->send( [id]() { getSink<LogSink_flexlog>()->close( id ); } );
175 }
176 
177 // register new flex sink with given filename
178 // blocks to return unique identifier
179 std::string LogFacility::registerFlexLogger( const std::string& logfilename, bool open_timestamp )
180 {
181  auto promise = std::make_shared<std::promise<std::string>>();
182  auto ret = promise->get_future();
183  _worker->send( [=]() {
184  try
185  {
186  promise->set_value( getSink<LogSink_flexlog>()->create( logfilename, open_timestamp ) );
187  }
188  catch ( ... )
189  {
190  promise->set_exception( std::current_exception() );
191  }
192  } );
193  return ret.get(); // block wait till valid
194 }
195 
196 // block waits till the queue is empty
198 {
199  auto promise = std::make_shared<std::promise<bool>>();
200  auto ret = promise->get_future();
201  _worker->send( [=]() { promise->set_value( true ); } );
202  ret.get(); // block wait till valid
203 }
204 
205 // Message default construct
206 template <typename Sink>
207 Message<Sink>::Message() : _formater( new fmt::Writer() ), _id( "" )
208 {
209 }
210 
211 template <typename Sink>
212 Message<Sink>::Message( const std::string& id ) : _formater( new fmt::Writer() ), _id( id )
213 {
214 }
215 // directly fill on construction the formater with file, line and function
216 template <typename Sink>
217 Message<Sink>::Message( const std::string& file, const int line, const std::string& function )
218  : _formater( new fmt::Writer() ), _id( "" )
219 {
220  *_formater << file << "[" << line << "] " << function << " : ";
221 }
222 // directly fill on construction the formater with file, line and function
223 template <typename Sink>
224 Message<Sink>::Message( const std::string& id, const std::string& file, const int line,
225  const std::string& function )
226  : _formater( new fmt::Writer() ), _id( id )
227 {
228  *_formater << file << "[" << line << "] " << function << " : ";
229 }
230 // on deconstruction transfer the formater to the facility
231 template <typename Sink>
233 {
234  if ( _formater->size() > 0 )
235  {
236  if ( global_logger == nullptr )
237  printf( "%s", _formater->c_str() );
238  else
239  global_logger->save<Sink>( _formater.release(), _id );
240  }
241 }
242 
243 
244 // create and get a sink
245 template <typename Sink>
246 Sink* getSink()
247 {
248  // does not need to be threadsafe since its only executed inside the worker thread
249  // with later vc its automatically threadsafe (magic statics)
250  static std::once_flag flag;
251  static Sink* sink = new Sink();
252  std::call_once( flag, []( Sink* s ) { global_logger->registerSink( s ); }, sink );
253  return sink;
254 }
255 
256 // first construction also opens the file
258  : LogSink(),
259  _behaviour( behaviour ),
260  _log_filename( behaviour->basename + ".log" ),
261  _active_line( false )
262 {
263  memset( &_opened, 0, sizeof( _opened ) );
264  open_log_file( true );
265 }
266 // default constructor does not open directly
268  : LogSink(), _behaviour(), _log_filename(), _active_line( false )
269 {
270  memset( &_opened, 0, sizeof( _opened ) );
271 }
273 {
274  if ( _filestream.is_open() )
275  {
276  _filestream.flush();
277  _filestream.close();
278  }
279 }
280 // set behaviour and logfilename, does not work with rollover
281 void LogSinkGenericFile::setBehaviour( const LogFileBehaviour* behaviour, std::string filename )
282 {
283  _behaviour = behaviour;
284  _log_filename = std::move( filename );
285 }
286 
287 // open file
288 void LogSinkGenericFile::open_log_file( bool open_timestamp )
289 {
291  if ( !_filestream.is_open() )
292  {
293  fmt::Writer tmp;
294  tmp << "failed to open logfile " << _log_filename << "\n";
295  getSink<LogSink_cerr>()->addMessage( &tmp );
296  return;
297  }
298  if ( open_timestamp )
299  {
301  _filestream << "Logfile opened." << std::endl;
302  }
303  _opened = Clib::localtime( std::chrono::system_clock::to_time_t(
304  std::chrono::system_clock::now() ) ); // mark current time for later possible rollover
305  _active_line = false;
306 }
307 
308 // print given msg into filestream
309 void LogSinkGenericFile::addMessage( fmt::Writer* msg )
310 {
311  if ( !_filestream.is_open() )
312  return;
313  if ( !msg->size() )
314  return;
315 
316  if ( !_active_line ) // only rollover or add timestamp if there is currently no open line
317  {
318  using std::chrono::system_clock;
319  std::chrono::time_point<system_clock> now = system_clock::now();
320  if ( now != _lasttimestamp )
321  {
322  if ( !test_for_rollover( now ) )
323  return;
324  if ( _behaviour->timestamps )
326  }
329  }
330  _active_line = ( msg->data()[msg->size() - 1] != '\n' ); // is the last character a newline?
331  _filestream << msg->str();
332  _filestream.flush();
333 }
334 void LogSinkGenericFile::addMessage( fmt::Writer* msg, const std::string& )
335 {
336  addMessage( msg );
337 }
338 
339 // check if a rollover is needed (new day)
341  std::chrono::time_point<std::chrono::system_clock>& now )
342 {
343  auto tm_now = Clib::localtime( std::chrono::system_clock::to_time_t( now ) );
344  if ( _behaviour->rollover &&
345  ( tm_now.tm_mday != _opened.tm_mday || tm_now.tm_mon != _opened.tm_mon ) )
346  {
347  // roll the log file over
348  char buffer[30];
349  strftime( buffer, sizeof buffer, "%Y-%m-%d", &_opened );
350  std::string archive_name = _behaviour->basename + "-" + buffer + ".log";
351  _filestream.flush();
352  _filestream.close();
353 
354  // whether the rename succeeds or fails, the action is the same.
355  rename( _log_filename.c_str(), archive_name.c_str() );
356 
357  // moved. open the new file
359  if ( !_filestream.is_open() )
360  return false;
361  _opened = tm_now;
362  }
363  return true;
364 }
365 
367 // print given msg into std::cout
368 void LogSink_cout::addMessage( fmt::Writer* msg )
369 {
370  std::cout << msg->str();
371  std::cout.flush();
372 #if defined(WINDOWS)
373  if (LogFacility::_vsDebuggerPresent)
374  OutputDebugString(msg->c_str());
375 #endif
376 }
377 void LogSink_cout::addMessage( fmt::Writer* msg, const std::string& )
378 {
379  addMessage( msg );
380 }
381 
383 // print given msg into std::cerr
384 void LogSink_cerr::addMessage( fmt::Writer* msg )
385 {
386  std::cerr << msg->str();
387  std::cerr.flush();
388 #if defined(WINDOWS)
389  if (LogFacility::_vsDebuggerPresent)
390  OutputDebugString(msg->c_str());
391 #endif
392 }
393 void LogSink_cerr::addMessage( fmt::Writer* msg, const std::string& )
394 {
395  addMessage( msg );
396 }
397 
398 // on construction this opens not pol.log instead start.log
400 
401 // performs the switch between start.log and pol.log
403 {
404  _filestream.flush();
405  _filestream.close();
407  _log_filename = _behaviour->basename + ".log";
408  open_log_file( true );
409 }
410 
411 // on construction opens script.log
413 
414 // on construction opens debug.log
416 
417 // debug.log can be disabled
419 {
420  if ( _filestream.is_open() )
421  {
422  _filestream.flush();
423  _filestream.close();
424  }
425  Disabled = true;
426 }
427 // only print the msg if not Disabled
428 void LogSink_debuglog::addMessage( fmt::Writer* msg )
429 {
430  if ( !Disabled )
432 }
433 void LogSink_debuglog::addMessage( fmt::Writer* msg, const std::string& )
434 {
435  addMessage( msg );
436 }
437 
438 // on construction opens leak.log
440 
443 
444 // create and open new logfile with given name, returns unique id
445 std::string LogSink_flexlog::create( std::string logfilename, bool open_timestamp )
446 {
447  auto itr = _logfiles.find( logfilename );
448  if ( itr != _logfiles.end() )
449  return logfilename;
450  _logfiles[logfilename] = std::make_shared<LogSinkGenericFile>();
451  auto log = _logfiles.at( logfilename );
452  log->setBehaviour( &flexlogBehaviour, logfilename );
453  log->open_log_file( open_timestamp );
454  return logfilename;
455 }
456 
457 // sink msg into sink of given id
458 void LogSink_flexlog::addMessage( fmt::Writer* msg, const std::string& id )
459 {
460  auto itr = _logfiles.find( id );
461  if ( itr != _logfiles.end() )
462  {
463  itr->second->addMessage( msg );
464  }
465 }
466 void LogSink_flexlog::addMessage( fmt::Writer* /*msg*/ )
467 {
468  // empty
469 }
470 
471 // closes logfile of given id
472 void LogSink_flexlog::close( const std::string& id )
473 {
474  auto itr = _logfiles.find( id );
475  if ( itr != _logfiles.end() )
476  _logfiles.erase( itr );
477 }
478 
479 template <typename log1, typename log2>
481 {
482 }
483 // performs the sink with given msg for both sinks
484 template <typename log1, typename log2>
485 void LogSink_dual<log1, log2>::addMessage( fmt::Writer* msg )
486 {
487  getSink<log1>()->addMessage( msg );
488  getSink<log2>()->addMessage( msg );
489 }
490 template <typename log1, typename log2>
491 void LogSink_dual<log1, log2>::addMessage( fmt::Writer* msg, const std::string& )
492 {
493  addMessage( msg );
494 }
495 }
496 }
498 }
499 
500 // forward define the templates
501 // dont want to add all the templates into the header
502 // could reduce the compilation time a bit
503 
504 #define SINK_TEMPLATE_DEFINES( sink ) \
505  template class Pol::Clib::Logging::Message<Pol::Clib::Logging::sink>; \
506  template Pol::Clib::Logging::sink* Pol::Clib::Logging::getSink<Pol::Clib::Logging::sink>(); \
507  template void Pol::Clib::Logging::LogFacility::save<Pol::Clib::Logging::sink>( \
508  fmt::Writer * message, const std::string& id );
509 
510 #define SINK_TEMPLATE_DEFINES_DUAL( sink1, sink2 ) \
511  template class Pol::Clib::Logging::Message< \
512  Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>>; \
513  template Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>* \
514  Pol::Clib::Logging::getSink< \
515  Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>>(); \
516  template void Pol::Clib::Logging::LogFacility::save< \
517  Pol::Clib::Logging::LogSink_dual<Pol::Clib::Logging::sink1, Pol::Clib::Logging::sink2>>( \
518  fmt::Writer * message, const std::string& id );
519 
520 
521 SINK_TEMPLATE_DEFINES( LogSink_cout )
522 SINK_TEMPLATE_DEFINES( LogSink_cerr )
523 SINK_TEMPLATE_DEFINES( LogSink_pollog )
524 SINK_TEMPLATE_DEFINES( LogSink_scriptlog )
525 SINK_TEMPLATE_DEFINES( LogSink_debuglog )
526 SINK_TEMPLATE_DEFINES( LogSink_leaklog )
527 SINK_TEMPLATE_DEFINES( LogSink_flexlog )
528 
529 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_pollog )
530 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_scriptlog )
531 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_debuglog )
532 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cout, LogSink_leaklog )
533 
534 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_pollog )
535 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_scriptlog )
536 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_debuglog )
537 SINK_TEMPLATE_DEFINES_DUAL( LogSink_cerr, LogSink_leaklog )
void initLogging(LogFacility *logger)
Definition: logfacility.cpp:63
std::string registerFlexLogger(const std::string &logfilename, bool open_timestamp)
bool test_for_rollover(std::chrono::time_point< std::chrono::system_clock > &now)
#define SINK_TEMPLATE_DEFINES(sink)
virtual void addMessage(fmt::Writer *msg) POL_OVERRIDE
std::map< std::string, std::shared_ptr< LogSinkGenericFile > > _logfiles
Definition: logfacility.h:126
virtual void addMessage(fmt::Writer *msg) POL_OVERRIDE
void save(fmt::Writer *message, const std::string &id)
std::tm localtime(const std::time_t &t)
threadsafe version of localtime
Definition: clib.h:143
bool LogfileTimestampEveryLine
Definition: logfacility.cpp:32
static LogFileBehaviour pollogBehaviour
Definition: logfacility.cpp:49
const LogFileBehaviour * _behaviour
Definition: logfacility.h:46
unsigned char buffer[10000]
Definition: UoToolMain.cpp:109
void setBehaviour(const LogFileBehaviour *behaviour, std::string filename)
static LogFileBehaviour flexlogBehaviour
Definition: logfacility.cpp:57
virtual void addMessage(fmt::Writer *msg) POL_OVERRIDE
static LogFileBehaviour debuglogBehaviour
Definition: logfacility.cpp:51
std::chrono::time_point< std::chrono::system_clock > _lasttimestamp
Definition: logfacility.h:50
std::unique_ptr< LogWorker > _worker
Definition: logfacility.h:157
std::vector< LogSink * > _registered_sinks
Definition: logfacility.h:159
void registerSink(LogSink *sink)
static LogFileBehaviour scriptlogBehaviour
Definition: logfacility.cpp:53
void closeFlexLog(const std::string &id)
void open_log_file(bool open_timestamp)
#define SINK_TEMPLATE_DEFINES_DUAL(sink1, sink2)
void close(const std::string &id)
static LogFileBehaviour startlogBehaviour
Definition: logfacility.cpp:47
virtual void addMessage(fmt::Writer *msg) POL_OVERRIDE
std::unique_ptr< fmt::Writer > _formater
Definition: logfacility.h:180
std::string create(std::string logfilename, bool open_timestamp)
static void addTimeStamp(std::ostream &stream)
Definition: LogSink.cpp:25
static LogFileBehaviour leaklogBehaviour
Definition: logfacility.cpp:55
virtual void addMessage(fmt::Writer *msg) POL_OVERRIDE
bool run(int argc, char **argv)
virtual void addMessage(fmt::Writer *msg) POL_OVERRIDE
std::ios_base::openmode openmode
Definition: logfacility.cpp:42
LogFacility * global_logger
Definition: logfacility.cpp:62
Definition: berror.cpp:12