API
logManager.hpp
Go to the documentation of this file.
1 /** \file logManager.hpp
2  * \brief The MagAO-X log manager.
3  * \author Jared R. Males (jaredmales@gmail.com)
4  *
5  * \ingroup logger_files
6  *
7  * History:
8  * - 2017-06-27 created by JRM
9  */
10 
11 #ifndef logger_logManager_hpp
12 #define logger_logManager_hpp
13 
14 #include <memory>
15 #include <list>
16 
17 #include <thread>
18 
19 #include <mutex>
20 #include <ratio>
21 
22 #include <mx/app/appConfigurator.hpp>
23 
24 #include <flatlogs/flatlogs.hpp>
25 
26 using namespace flatlogs;
27 
28 #include "../common/defaults.hpp"
29 
30 #include "generated/logTypes.hpp"
31 #include "generated/logStdFormat.hpp"
32 
33 namespace MagAOX
34 {
35 namespace logger
36 {
37 
38 /// The standard MagAOX log manager, used for both process logs and telemetry streams.
39 /** Manages the formatting and queueing of the log entries.
40  *
41  * A log entry is made using one of the standard log types. These are formatted into a binary stream and
42  * stored in a std::list. This occurs in the calling thread. The insertion into the list is mutex-ed, so
43  * it is safe to make logs from different threads concurrently.
44  *
45  * Write-to-disk occurs in a separate thread, which
46  * is normally set to the lowest priority so as not to interfere with higher-priority tasks. The
47  * log thread cycles through pending log entries in the list, dispatching them to the logFile.
48  *
49  * The template parameter logFileT is one of the logFile types, which is used to actually write to disk.
50  *
51  * Example:
52  \code
53  logManager<logFileRaw> log;
54  log.m_logThreadStart();
55 
56  //do stuff
57 
58  log<loop_closed>(); //An empty log, which is used to log discrete events.
59 
60  //more stuff happens
61 
62  log<text_log>("a log entry with a std::string message type");
63 
64  \endcode
65  *
66  * \todo document all the requirements of logFileT
67  *
68  * \tparam logFileT a logFile type with a writeLog method.
69  *
70  * \ingroup logger
71  */
72 template<class _parentT, class _logFileT>
73 struct logManager : public _logFileT
74 {
75  typedef _parentT parentT;
76  typedef _logFileT logFileT;
77 
78 protected:
79  parentT * m_parent {nullptr};
80 
81  ///\todo Make these protected members, with appropriate access methods
82  //-->
83 public:
84  std::string m_configSection {"logger"}; ///<The configuration files section name. Default is `logger`.
85 
86 protected:
87  std::list<bufferPtrT> m_logQueue; ///< Log entries are stored here, and writen to the file by the log thread.
88 
89  std::thread m_logThread; ///< A separate thread for actually writing to the file.
90  std::mutex m_qMutex; ///< Mutex for accessing the m_logQueue.
91 
92  bool m_logShutdown {false}; ///< Flag to signal the log thread to shutdown.
93 
94  unsigned long m_writePause {MAGAOX_default_writePause}; ///< Time, in nanoseconds, to pause between successive batch writes to the file. Default is 1e9. Configure with logger.writePause.
95 
96 public:
97  logPrioT m_logLevel {logPrio::LOG_INFO}; ///< The minimum log level to actually record. Logs with level below this are rejected. Default is INFO. Configure with logger.logLevel.
98 
99 protected:
100  int m_logThreadPrio {0};
101 
102  bool m_logThreadRunning {false};
103  //<--end of todo
104 
105 public:
106  /// Default c'tor.
108 
109  /// Destructor.
111 
112  /// Set the logger parent
113  /** The parent is used for interactive presentation of log messages
114  */
115  void parent( parentT * p /**< [in] pointer to the parent object*/);
116 
117  /// Get the logger parent
118  /**
119  * Returns a point to the parent object.
120  */
122 
123  /// Set a new value of writePause
124  /** Updates m_writePause with new value.
125  *
126  * \returns 0 on success
127  * \returns -1 on error (if wp == 0).
128  */
129  int writePause( const unsigned long & wp /**< [in] the new value of writePause */);
130 
131  /// Get the current value of writePause
132  /** \returns the value m_writePause.
133  */
134  unsigned long writePause();
135 
136  /// Set a new value of logLevel
137  /** Updates m_logLevel with new value.
138  * Will return an error and take no actions if the argument
139  * is outside the range of the logLevels enum.
140  *
141  * \returns 0 on success
142  * \returns -1 on error.
143  */
144  int logLevel( logPrioT newLev /**< [in] the new value of logLevel */);
145 
146  /// Get the current value of logLevel
147  /** \returns the value m_logLevel
148  */
150 
151  /// Set a new value of logThreadPrio
152  /** Updates m_logThreadPrio with new value.
153  * If the argument is < 0, this sets m_logThreadPrio to 0.
154  * Will not set > 98, and returns -1 with no changes in this case.
155  *
156  * \returns 0 on success
157  * \returns -1 on error (> 98).
158  */
159  int logThreadPrio( int newPrio /**< [in] the new value of logThreadPrio */);
160 
161  /// Get the current value of logThreadPrio
162  /** \returns the value m_logThreadPrio
163  */
165 
166  /// Get status of the log thread running flag
167  /** \returns the current value of m_logThreadRunning
168  */
170 
171  ///Setup an application configurator for the logger section
172  int setupConfig( mx::app::appConfigurator & config /**< [in] an application configuration to setup */);
173 
174  ///Load the logger section from an application configurator
175  /**
176  */
177  int loadConfig( mx::app::appConfigurator & config /**< [in] an application configuration from which to load values */);
178 
179 
180  ///Thread starter, called by logThreadStart on thread construction. Calls logThreadExec.
181  static void _logThreadStart( logManager * l /**< [in] a pointer to a logger instance (normally this) */);
182 
183  /// Start the logger thread.
185 
186  /// Execute the logger thread.
188 
189  /// Create a log formatted log entry, filling in a buffer.
190  /** This is where the timestamp of the log entry is set.
191  *
192  * \tparam logT is a log entry type
193  *
194  * \returns 0 on success, -1 on error.
195  */
196  template<typename logT>
197  static int createLog( bufferPtrT & logBuffer, ///< [out] a shared_ptr<logBuffer>, which will be allocated and populated with the log entry
198  const typename logT::messageT & msg, ///< [in] the message to log (could be of type emptyMessage)
199  const logPrioT & level ///< [in] the level (verbosity) of this log
200  );
201 
202  /// Create a log formatted log entry, filling in a buffer.
203  /** This version has the timestamp provided.
204  *
205  * \tparam logT is a log entry type
206  *
207  * \returns 0 on success, -1 on error.
208  */
209  template<typename logT>
210  static int createLog( bufferPtrT & logBuffer, ///< [out] a shared_ptr<logBuffer>, which will be allocated and populated with the log entry
211  const timespecX & ts, ///< [in] the timestamp of this log entry.
212  const typename logT::messageT & msg, ///< [in] the message to log (could be of type emptyMessage)
213  const logPrioT & level ///< [in] the level (verbosity) of this log
214  );
215 
216  /// Make a log entry, including a message.
217  /**
218  * \tparam logT is a log entry type
219  */
220  template<typename logT>
221  void log( const typename logT::messageT & msg, ///< [in] the message to log
222  logPrioT level = logPrio::LOG_DEFAULT ///< [in] [optional] the log level. The default is used if not specified.
223  );
224 
225  /// Make a log entry, including a message.
226  /**
227  * \tparam logT is a log entry type
228  */
229  template<typename logT>
230  void log( timespecX & ts, ///< [in] the timestamp of the log entry
231  const typename logT::messageT & msg, ///< [in] the message to log
232  logPrioT level = logPrio::LOG_DEFAULT ///< [in] [optional] the log level. The default is used if not specified.
233  );
234 
235  /// Make a log entry with no message.
236  /**
237  * \tparam logT is a log entry type
238  */
239  template<typename logT>
240  void log( logPrioT level = logPrio::LOG_DEFAULT /**< [in] [optional] the log level. The default is used if not specified.*/);
241 
242  /// Make a log entry with no message.
243  /**
244  * \tparam logT is a log entry type
245  */
246  template<typename logT>
247  void log( timespecX & ts, ///< [in] the timestamp of the log entry
248  logPrioT level = logPrio::LOG_DEFAULT ///< [in] [optional] the log level. The default is used if not specified.
249  );
250 
251 };
252 
253 template<class parentT, class logFileT>
255 {
256 }
257 
258 template<class parentT, class logFileT>
260 {
261  m_logShutdown = true;
262 
263  if(m_logThread.joinable()) m_logThread.join();
264 
265  //One last check to see if there are any unwritten logs.
266  if( !m_logQueue.empty() ) logThreadExec();
267 
268 }
269 
270 template<class parentT, class logFileT>
272 {
273  m_parent = p;
274 }
275 
276 template<class parentT, class logFileT>
278 {
279  return m_parent;
280 }
281 
282 template<class parentT, class logFileT>
283 int logManager<parentT, logFileT>::writePause( const unsigned long & wp )
284 {
285  if(wp == 0) return -1;
286 
287  m_writePause = wp;
288 
289  return 0;
290 }
291 
292 template<class parentT, class logFileT>
294 {
295  return m_writePause;
296 }
297 
298 template<class parentT, class logFileT>
300 {
301 
302 
303  m_logLevel = newLev;
304 
305  return 0;
306 }
307 
308 template<class parentT, class logFileT>
310 {
311  return m_logLevel;
312 }
313 
314 template<class parentT, class logFileT>
316 {
317  if(newPrio > 98) return -1; //clamped at 98 for safety.
318  if(newPrio < 0) newPrio = 0;
319 
320  m_logThreadPrio = newPrio;
321  return 0;
322 }
323 
324 template<class parentT, class logFileT>
326 {
327  return m_logThreadPrio;
328 }
329 
330 template<class parentT, class logFileT>
332 {
333  return m_logThreadRunning;
334 }
335 
336 template<class parentT, class logFileT>
337 int logManager<parentT, logFileT>::setupConfig( mx::app::appConfigurator & config )
338 {
339  config.add(m_configSection+".logDir","L", "logDir",mx::app::argType::Required, m_configSection, "logDir", false, "string", "The directory for log files");
340  config.add(m_configSection+".logExt","", "logExt",mx::app::argType::Required, m_configSection, "logExt", false, "string", "The extension for log files");
341  config.add(m_configSection+".maxLogSize","", "maxLogSize",mx::app::argType::Required, m_configSection, "maxLogSize", false, "string", "The maximum size of log files");
342  config.add(m_configSection+".writePause","", "writePause",mx::app::argType::Required, m_configSection, "writePause", false, "unsigned long", "The log thread pause time in ns");
343  config.add(m_configSection+".logThreadPrio", "", "logThreadPrio", mx::app::argType::Required, m_configSection, "logThreadPrio", false, "int", "The log thread priority");
344  config.add(m_configSection+".logLevel","l", "logLevel",mx::app::argType::Required, m_configSection, "logLevel", false, "string", "The log level");
345 
346  return 0;
347 }
348 
349 template<class parentT, class logFileT>
350 int logManager<parentT, logFileT>::loadConfig( mx::app::appConfigurator & config )
351 {
352  //-- logDir
353  std::string tmp;
354  config(tmp, m_configSection+".logDir");
355  if(tmp != "") this->logPath(tmp);
356 
357  //-- logLevel
358  tmp = "";
359  config(tmp, m_configSection+".logLevel");
360  if(tmp != "")
361  {
362  logPrioT lev;
363 
364  lev = logLevelFromString(tmp);
365 
366  if( lev == logPrio::LOG_DEFAULT ) lev = logPrio::LOG_INFO;
367  if( lev == logPrio::LOG_UNKNOWN )
368  {
369  std::cerr << "Unkown log level specified. Using default (INFO)\n";
370  lev = logPrio::LOG_INFO;
371  }
372  logLevel(lev);
373  }
374 
375 
376  //logExt
377  config(this->m_logExt, m_configSection+".logExt");
378 
379  //maxLogSize
380  config(this->m_maxLogSize, m_configSection+".maxLogSize");
381 
382  //writePause
383  config(m_writePause, m_configSection+".writePause");
384 
385  //logThreadPrio
386  config(m_logThreadPrio, m_configSection+".logThreadPrio");
387 
388  return 0;
389 }
390 
391 template<class parentT, class logFileT>
393 {
394  l->logThreadExec();
395 }
396 
397 template<class parentT, class logFileT>
399 {
400  try
401  {
402  m_logThread = std::thread( _logThreadStart, this);
403  }
404  catch( const std::exception & e )
405  {
406  log<software_error>({__FILE__,__LINE__, 0, 0, std::string("Exception on log thread start: ") + e.what()});
407  return -1;
408  }
409  catch( ... )
410  {
411  log<software_error>({__FILE__,__LINE__, 0, 0, "Unkown exception on log thread start"});
412  return -1;
413  }
414 
415  if(!m_logThread.joinable())
416  {
417  log<software_error>({__FILE__, __LINE__, 0, 0, "Log thread did not start"});
418  return -1;
419  }
420 
421  //Always set the m_logThread to lowest priority
422  sched_param sp;
423  sp.sched_priority = m_logThreadPrio;
424 
425  int rv = pthread_setschedparam( m_logThread.native_handle(), SCHED_OTHER, &sp);
426 
427  if(rv != 0)
428  {
429  log<software_error>({__FILE__, __LINE__, 0, rv, std::string("Error setting thread params: ") + strerror(rv)});
430  return -1;
431  }
432 
433  return 0;
434 
435 }
436 
437 template<class parentT, class logFileT>
439 {
440 
441  m_logThreadRunning = true;
442 
443  std::unique_lock<std::mutex> lock(m_qMutex, std::defer_lock);
444 
445  while(!m_logShutdown || !m_logQueue.empty())
446  {
447  std::list<bufferPtrT>::iterator beg, it, er, end;
448 
449  //Get begin and end once, so we only process logs in the list at this point.
450  //Do it locked so we don't have any collisions with new logs (maybe not necessary)
451  lock.lock();
452  beg = m_logQueue.begin();
453  end = m_logQueue.end();
454  lock.unlock();
455 
456  //Note: we're avoiding use of size() since that could be altered concurrently by a push_back.
457  if( beg != end )
458  {
459  it = beg;
460  while( it != end )
461  {
462  //m_logFile.
463  if( this->writeLog( *it ) < 0)
464  {
465  m_logThreadRunning = false;
466  return;
467  }
468 
469  if(m_parent)
470  {
471  m_parent->logMessage( *it );
472  }
473  else if( logHeader::logLevel( *it ) <= logPrio::LOG_NOTICE )
474  {
475  logStdFormat(std::cerr, *it);
476  std::cerr << "\n";
477  }
478 
479  er = it;
480  ++it;
481 
482  //Erase while locked so we don't collide with a push_back.
483  lock.lock();
484  m_logQueue.erase(er);
485  lock.unlock();
486  }
487  }
488 
489  //m_logFile.
490  ///\todo must check this for errors, and investigate how `fsyncgate` impacts us
491  this->flush();
492 
493  //We only pause if there's nothing to do.
494  if(m_logQueue.empty() && !m_logShutdown) std::this_thread::sleep_for( std::chrono::duration<unsigned long, std::nano>(m_writePause));
495  }
496 
497  m_logThreadRunning = false;
498 }
499 
500 template<class parentT, class logFileT>
501 template<typename logT>
503  const typename logT::messageT & msg,
504  const logPrioT & level
505  )
506 {
507  //Very first step is to get the current time.
508  timespecX ts;
509  ts.gettime();
510 
511  return logHeader::createLog<logT>(logBuffer, ts, msg, level);
512 }
513 
514 template<class parentT, class logFileT>
515 template<typename logT>
517  const timespecX & ts,
518  const typename logT::messageT & msg,
519  const logPrioT & level
520  )
521 {
522  return logHeader::createLog<logT>(logBuffer, ts, msg, level);
523 }
524 
525 template<class parentT, class logFileT>
526 template<typename logT>
527 void logManager<parentT, logFileT>::log( const typename logT::messageT & msg,
528  logPrioT level
529  )
530 {
531  //Step 0 check level.
532  if(level == logPrio::LOG_DEFAULT) level = logT::defaultLevel;
533 
534  if(level > m_logLevel) return; // We do nothing with this.
535 
536  //Step 1 create log
537  bufferPtrT logBuffer;
538  createLog<logT>(logBuffer, msg, level);
539 
540  //Step 2 add log to queue
541  std::lock_guard<std::mutex> guard(m_qMutex); //Lock the mutex before pushing back.
542  m_logQueue.push_back(logBuffer);
543 
544 
545 }
546 
547 template<class parentT, class logFileT>
548 template<typename logT>
550  const typename logT::messageT & msg,
551  logPrioT level
552  )
553 {
554  //Step 0 check level.
555  if(level == logPrio::LOG_DEFAULT) level = logT::defaultLevel;
556 
557  if(level > m_logLevel) return; // We do nothing with this.
558 
559  //Step 1 create log
560  bufferPtrT logBuffer;
561  createLog<logT>(logBuffer, ts, msg, level);
562 
563  //Step 2 add log to queue
564  std::lock_guard<std::mutex> guard(m_qMutex); //Lock the mutex before pushing back.
565  m_logQueue.push_back(logBuffer);
566 
567 }
568 
569 template<class parentT, class logFileT>
570 template<typename logT>
572 {
573  log<logT>( emptyMessage(), level );
574 }
575 
576 template<class parentT, class logFileT>
577 template<typename logT>
579  logPrioT level
580  )
581 {
582  log<logT>( ts, emptyMessage(), level );
583 }
584 
585 //class logFileRaw;
586 
587 //extern template struct logManager<logFileRaw>;
588 
589 } //namespace logger
590 } //namespace MagAOX
591 
592 #endif //logger_logger_hpp
Flatlogs single include file.
#define MAGAOX_default_writePause
The default logger writePause.
Definition: defaults.hpp:31
int8_t logPrioT
The type of the log priority code.
Definition: logDefs.hpp:21
std::shared_ptr< char > bufferPtrT
The log entry buffer smart pointer.
Definition: logHeader.hpp:58
static int logLevel(bufferPtrT &logBuffer, const logPrioT &lvl)
Set the level of a log entry in a logBuffer header.
Definition: logHeader.hpp:388
Empty type for resolving logs with no message.
Definition: empty_log.hpp:25
std::ostream & cerr()
std::stringstream msg
Definition: dm.hpp:24
constexpr static logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
Definition: logPriority.hpp:49
constexpr static logPrioT LOG_UNKNOWN
Used to denote an unkown log type for internal error handling.
Definition: logPriority.hpp:64
constexpr static logPrioT LOG_DEFAULT
Used to denote "use the default level for this log type".
Definition: logPriority.hpp:61
constexpr static logPrioT LOG_NOTICE
A normal but significant condition.
Definition: logPriority.hpp:46
logPrioT logLevelFromString(const std::string &str)
Get the log priority from a string, which might have the number or the name.
void logThreadStart()
The standard MagAOX log manager, used for both process logs and telemetry streams.
Definition: logManager.hpp:74
int logThreadStart()
Start the logger thread.
Definition: logManager.hpp:398
int loadConfig(mx::app::appConfigurator &config)
Load the logger section from an application configurator.
Definition: logManager.hpp:350
void log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry, including a message.
Definition: logManager.hpp:527
void log(timespecX &ts, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry with no message.
Definition: logManager.hpp:578
parentT * parent()
Get the logger parent.
Definition: logManager.hpp:277
static int createLog(bufferPtrT &logBuffer, const typename logT::messageT &msg, const logPrioT &level)
Create a log formatted log entry, filling in a buffer.
Definition: logManager.hpp:502
unsigned long writePause()
Get the current value of writePause.
Definition: logManager.hpp:293
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the logger section.
Definition: logManager.hpp:337
int logThreadPrio()
Get the current value of logThreadPrio.
Definition: logManager.hpp:325
std::thread m_logThread
A separate thread for actually writing to the file.
Definition: logManager.hpp:89
void parent(parentT *p)
Set the logger parent.
Definition: logManager.hpp:271
std::list< bufferPtrT > m_logQueue
Log entries are stored here, and writen to the file by the log thread.
Definition: logManager.hpp:87
void log(logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry with no message.
Definition: logManager.hpp:571
logPrioT logLevel()
Get the current value of logLevel.
Definition: logManager.hpp:309
void log(timespecX &ts, const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry, including a message.
Definition: logManager.hpp:549
void logThreadExec()
Execute the logger thread.
Definition: logManager.hpp:438
int logThreadPrio(int newPrio)
Set a new value of logThreadPrio.
Definition: logManager.hpp:315
int writePause(const unsigned long &wp)
Set a new value of writePause.
Definition: logManager.hpp:283
int logLevel(logPrioT newLev)
Set a new value of logLevel.
Definition: logManager.hpp:299
std::mutex m_qMutex
Mutex for accessing the m_logQueue.
Definition: logManager.hpp:90
static int createLog(bufferPtrT &logBuffer, const timespecX &ts, const typename logT::messageT &msg, const logPrioT &level)
Create a log formatted log entry, filling in a buffer.
Definition: logManager.hpp:516
logManager()
Default c'tor.
Definition: logManager.hpp:254
bool logThreadRunning()
Get status of the log thread running flag.
Definition: logManager.hpp:331
static void _logThreadStart(logManager *l)
Thread starter, called by logThreadStart on thread construction. Calls logThreadExec.
Definition: logManager.hpp:392
A fixed-width timespec structure.
Definition: timespecX.hpp:35
void gettime()
Fill the the timespecX with the current time.
Definition: timespecX.hpp:94