API
 
Loading...
Searching...
No Matches
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
26using namespace flatlogs;
27
28#include "../common/defaults.hpp"
29
30#include "generated/logTypes.hpp"
31#include "generated/logStdFormat.hpp"
32
33namespace MagAOX
34{
35namespace 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 */
72template<class _parentT, class _logFileT>
73struct logManager : public _logFileT
74{
76 typedef _logFileT logFileT;
77
78protected:
79 parentT * m_parent {nullptr};
80
81 ///\todo Make these protected members, with appropriate access methods
82 //-->
83public:
84 std::string m_configSection {"logger"}; ///<The configuration files section name. Default is `logger`.
85
86protected:
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
96public:
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
99protected:
101
102 bool m_logThreadRunning {false};
103 //<--end of todo
104
105public:
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
253template<class parentT, class logFileT>
257
258template<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
270template<class parentT, class logFileT>
272{
273 m_parent = p;
274}
275
276template<class parentT, class logFileT>
278{
279 return m_parent;
280}
281
282template<class parentT, class logFileT>
283int 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
292template<class parentT, class logFileT>
294{
295 return m_writePause;
296}
297
298template<class parentT, class logFileT>
300{
301
302
303 m_logLevel = newLev;
304
305 return 0;
306}
307
308template<class parentT, class logFileT>
310{
311 return m_logLevel;
312}
313
314template<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
324template<class parentT, class logFileT>
326{
327 return m_logThreadPrio;
328}
329
330template<class parentT, class logFileT>
332{
333 return m_logThreadRunning;
334}
335
336template<class parentT, class logFileT>
337int 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
349template<class parentT, class logFileT>
350int 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
391template<class parentT, class logFileT>
396
397template<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
437template<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
500template<class parentT, class logFileT>
501template<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
514template<class parentT, class logFileT>
515template<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
525template<class parentT, class logFileT>
526template<typename logT>
527void 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
547template<class parentT, class logFileT>
548template<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
569template<class parentT, class logFileT>
570template<typename logT>
572{
573 log<logT>( emptyMessage(), level );
574}
575
576template<class parentT, class logFileT>
577template<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.
Empty type for resolving logs with no message.
Definition empty_log.hpp:25
Definition dm.hpp:24
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
static constexpr logPrioT LOG_INFO
Informational. The info log level is the lowest level recorded during normal operations.
static constexpr logPrioT LOG_DEFAULT
Used to denote "use the default level for this log type".
static constexpr logPrioT LOG_UNKNOWN
Used to denote an unkown log type for internal error handling.
logPrioT logLevelFromString(const std::string &str)
Get the log priority from a string, which might have the number or the name.
The standard MagAOX log manager, used for both process logs and telemetry streams.
int logThreadStart()
Start the logger thread.
int loadConfig(mx::app::appConfigurator &config)
Load the logger section from an application configurator.
void log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry, including a message.
void log(timespecX &ts, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry with no message.
parentT * parent()
Get the logger parent.
static int createLog(bufferPtrT &logBuffer, const typename logT::messageT &msg, const logPrioT &level)
Create a log formatted log entry, filling in a buffer.
std::string m_configSection
The configuration files section name. Default is logger.
unsigned long writePause()
Get the current value of writePause.
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the logger section.
int logThreadPrio()
Get the current value of logThreadPrio.
bool m_logShutdown
Flag to signal the log thread to shutdown.
unsigned long m_writePause
Time, in nanoseconds, to pause between successive batch writes to the file. Default is 1e9....
std::thread m_logThread
A separate thread for actually writing to the file.
void parent(parentT *p)
Set the logger parent.
std::list< bufferPtrT > m_logQueue
Log entries are stored here, and writen to the file by the log thread.
void log(logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry with no message.
logPrioT logLevel()
Get the current value of logLevel.
logPrioT m_logLevel
The minimum log level to actually record. Logs with level below this are rejected....
void log(timespecX &ts, const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry, including a message.
void logThreadExec()
Execute the logger thread.
int logThreadPrio(int newPrio)
Set a new value of logThreadPrio.
int writePause(const unsigned long &wp)
Set a new value of writePause.
int logLevel(logPrioT newLev)
Set a new value of logLevel.
std::mutex m_qMutex
Mutex for accessing the m_logQueue.
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.
bool logThreadRunning()
Get status of the log thread running flag.
static void _logThreadStart(logManager *l)
Thread starter, called by logThreadStart on thread construction. Calls logThreadExec.
A fixed-width timespec structure.
Definition timespecX.hpp:35
void gettime()
Fill the the timespecX with the current time.
Definition timespecX.hpp:94