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
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 logShutdown
124 /** Updates m_logShutdown with new value.
125 *
126 * \returns 0 on success
127 * \returns -1 on error (if wp == 0).
128 */
129 int logShutdown( bool ls );
130
131 /// Get the current value of logShutdown
132 /** \returns the value m_logShutdown.
133 */
135
136 /// Set a new value of writePause
137 /** Updates m_writePause with new value.
138 *
139 * \returns 0 on success
140 * \returns -1 on error (if wp == 0).
141 */
142 int writePause( const unsigned long & wp /**< [in] the new value of writePause */);
143
144 /// Get the current value of writePause
145 /** \returns the value m_writePause.
146 */
147 unsigned long writePause();
148
149 /// Set a new value of logLevel
150 /** Updates m_logLevel with new value.
151 * Will return an error and take no actions if the argument
152 * is outside the range of the logLevels enum.
153 *
154 * \returns 0 on success
155 * \returns -1 on error.
156 */
157 int logLevel( logPrioT newLev /**< [in] the new value of logLevel */);
158
159 /// Get the current value of logLevel
160 /** \returns the value m_logLevel
161 */
163
164 /// Set a new value of logThreadPrio
165 /** Updates m_logThreadPrio with new value.
166 * If the argument is < 0, this sets m_logThreadPrio to 0.
167 * Will not set > 98, and returns -1 with no changes in this case.
168 *
169 * \returns 0 on success
170 * \returns -1 on error (> 98).
171 */
172 int logThreadPrio( int newPrio /**< [in] the new value of logThreadPrio */);
173
174 /// Get the current value of logThreadPrio
175 /** \returns the value m_logThreadPrio
176 */
178
179 /// Get status of the log thread running flag
180 /** \returns the current value of m_logThreadRunning
181 */
183
184 ///Setup an application configurator for the logger section
185 int setupConfig( mx::app::appConfigurator & config /**< [in] an application configuration to setup */);
186
187 ///Load the logger section from an application configurator
188 /**
189 */
190 int loadConfig( mx::app::appConfigurator & config /**< [in] an application configuration from which to load values */);
191
192
193 ///Thread starter, called by logThreadStart on thread construction. Calls logThreadExec.
194 static void _logThreadStart( logManager * l /**< [in] a pointer to a logger instance (normally this) */);
195
196 /// Start the logger thread.
198
199 /// Execute the logger thread.
201
202 /// Create a log formatted log entry, filling in a buffer.
203 /** This is where the timestamp of the log entry is set.
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 typename logT::messageT & msg, ///< [in] the message to log (could be of type emptyMessage)
212 const logPrioT & level ///< [in] the level (verbosity) of this log
213 );
214
215 /// Create a log formatted log entry, filling in a buffer.
216 /** This version has the timestamp provided.
217 *
218 * \tparam logT is a log entry type
219 *
220 * \returns 0 on success, -1 on error.
221 */
222 template<typename logT>
223 static int createLog( bufferPtrT & logBuffer, ///< [out] a shared_ptr<logBuffer>, which will be allocated and populated with the log entry
224 const timespecX & ts, ///< [in] the timestamp of this log entry.
225 const typename logT::messageT & msg, ///< [in] the message to log (could be of type emptyMessage)
226 const logPrioT & level ///< [in] the level (verbosity) of this log
227 );
228
229 /// Make a log entry, including a message.
230 /**
231 * \tparam logT is a log entry type
232 */
233 template<typename logT>
234 void log( const typename logT::messageT & msg, ///< [in] the message to log
235 logPrioT level = logPrio::LOG_DEFAULT ///< [in] [optional] the log level. The default is used if not specified.
236 );
237
238 /// Make a log entry, including a message.
239 /**
240 * \tparam logT is a log entry type
241 */
242 template<typename logT>
243 void log( timespecX & ts, ///< [in] the timestamp of the log entry
244 const typename logT::messageT & msg, ///< [in] the message to log
245 logPrioT level = logPrio::LOG_DEFAULT ///< [in] [optional] the log level. The default is used if not specified.
246 );
247
248 /// Make a log entry with no message.
249 /**
250 * \tparam logT is a log entry type
251 */
252 template<typename logT>
253 void log( logPrioT level = logPrio::LOG_DEFAULT /**< [in] [optional] the log level. The default is used if not specified.*/);
254
255 /// Make a log entry with no message.
256 /**
257 * \tparam logT is a log entry type
258 */
259 template<typename logT>
260 void log( timespecX & ts, ///< [in] the timestamp of the log entry
261 logPrioT level = logPrio::LOG_DEFAULT ///< [in] [optional] the log level. The default is used if not specified.
262 );
263
264};
265
266template<class parentT, class logFileT>
270
271template<class parentT, class logFileT>
273{
274 m_logShutdown = true;
275
276 if(m_logThread.joinable()) m_logThread.join();
277
278 //One last check to see if there are any unwritten logs.
279 if( !m_logQueue.empty() ) logThreadExec();
280
281}
282
283template<class parentT, class logFileT>
285{
286 m_parent = p;
287}
288
289template<class parentT, class logFileT>
291{
292 return m_parent;
293}
294
295template<class parentT, class logFileT>
297{
298 m_logShutdown = ls;
299 return 0;
300}
301
302template<class parentT, class logFileT>
304{
305 return m_logShutdown;
306}
307
308template<class parentT, class logFileT>
309int logManager<parentT, logFileT>::writePause( const unsigned long & wp )
310{
311 if(wp == 0) return -1;
312
313 m_writePause = wp;
314
315 return 0;
316}
317
318template<class parentT, class logFileT>
320{
321 return m_writePause;
322}
323
324template<class parentT, class logFileT>
326{
327
328
329 m_logLevel = newLev;
330
331 return 0;
332}
333
334template<class parentT, class logFileT>
336{
337 return m_logLevel;
338}
339
340template<class parentT, class logFileT>
342{
343 if(newPrio > 98) return -1; //clamped at 98 for safety.
344 if(newPrio < 0) newPrio = 0;
345
346 m_logThreadPrio = newPrio;
347 return 0;
348}
349
350template<class parentT, class logFileT>
352{
353 return m_logThreadPrio;
354}
355
356template<class parentT, class logFileT>
358{
359 return m_logThreadRunning;
360}
361
362template<class parentT, class logFileT>
363int logManager<parentT, logFileT>::setupConfig( mx::app::appConfigurator & config )
364{
365 config.add(m_configSection+".logDir","L", "logDir",mx::app::argType::Required, m_configSection, "logDir", false, "string", "The directory for log files");
366 config.add(m_configSection+".logExt","", "logExt",mx::app::argType::Required, m_configSection, "logExt", false, "string", "The extension for log files");
367 config.add(m_configSection+".maxLogSize","", "maxLogSize",mx::app::argType::Required, m_configSection, "maxLogSize", false, "string", "The maximum size of log files");
368 config.add(m_configSection+".writePause","", "writePause",mx::app::argType::Required, m_configSection, "writePause", false, "unsigned long", "The log thread pause time in ns");
369 config.add(m_configSection+".logThreadPrio", "", "logThreadPrio", mx::app::argType::Required, m_configSection, "logThreadPrio", false, "int", "The log thread priority");
370 config.add(m_configSection+".logLevel","l", "logLevel",mx::app::argType::Required, m_configSection, "logLevel", false, "string", "The log level");
371
372 return 0;
373}
374
375template<class parentT, class logFileT>
376int logManager<parentT, logFileT>::loadConfig( mx::app::appConfigurator & config )
377{
378 //-- logDir
379 std::string tmp;
380 config(tmp, m_configSection+".logDir");
381 if(tmp != "") this->logPath(tmp);
382
383 //-- logLevel
384 tmp = "";
385 config(tmp, m_configSection+".logLevel");
386 if(tmp != "")
387 {
388 logPrioT lev;
389
390 lev = logLevelFromString(tmp);
391
392 if( lev == logPrio::LOG_DEFAULT ) lev = logPrio::LOG_INFO;
393 if( lev == logPrio::LOG_UNKNOWN )
394 {
395 std::cerr << "Unkown log level specified. Using default (INFO)\n";
396 lev = logPrio::LOG_INFO;
397 }
398 logLevel(lev);
399 }
400
401
402 //logExt
403 config(this->m_logExt, m_configSection+".logExt");
404
405 //maxLogSize
406 config(this->m_maxLogSize, m_configSection+".maxLogSize");
407
408 //writePause
409 config(m_writePause, m_configSection+".writePause");
410
411 //logThreadPrio
412 config(m_logThreadPrio, m_configSection+".logThreadPrio");
413
414 return 0;
415}
416
417template<class parentT, class logFileT>
422
423template<class parentT, class logFileT>
425{
426 try
427 {
428 m_logThread = std::thread( _logThreadStart, this);
429 }
430 catch( const std::exception & e )
431 {
432 log<software_error>({__FILE__,__LINE__, 0, 0, std::string("Exception on log thread start: ") + e.what()});
433 return -1;
434 }
435 catch( ... )
436 {
437 log<software_error>({__FILE__,__LINE__, 0, 0, "Unkown exception on log thread start"});
438 return -1;
439 }
440
441 if(!m_logThread.joinable())
442 {
443 log<software_error>({__FILE__, __LINE__, 0, 0, "Log thread did not start"});
444 return -1;
445 }
446
447 //Always set the m_logThread to lowest priority
448 sched_param sp;
449 sp.sched_priority = m_logThreadPrio;
450
451 int rv = pthread_setschedparam( m_logThread.native_handle(), SCHED_OTHER, &sp);
452
453 if(rv != 0)
454 {
455 log<software_error>({__FILE__, __LINE__, 0, rv, std::string("Error setting thread params: ") + strerror(rv)});
456 return -1;
457 }
458
459 return 0;
460
461}
462
463template<class parentT, class logFileT>
465{
466
467 m_logThreadRunning = true;
468
469 std::unique_lock<std::mutex> lock(m_qMutex, std::defer_lock);
470
471 //Run until shutdown. If shutdown is true we keep running while the queue is not empty.
472 while(!m_logShutdown || !m_logQueue.empty())
473 {
474 std::list<bufferPtrT>::iterator beg, it, er, end;
475
476 //Get begin and end once, so we only process logs in the list at this point.
477 //Do it locked so we don't have any collisions with new logs (maybe not necessary)
478 lock.lock();
479 beg = m_logQueue.begin();
480 end = m_logQueue.end();
481 lock.unlock();
482
483 //Note: we're avoiding use of size() since that could be altered concurrently by a push_back.
484 if( beg != end )
485 {
486 it = beg;
487 while( it != end )
488 {
489 //m_logFile.
490 mx::error_t errc = this->writeLog( *it );
491 if( errc != mx::error_t::noerror)
492 {
493 m_logThreadRunning = false;
494 return;
495 }
496
497 if(m_parent)
498 {
499 m_parent->logMessage( *it );
500 }
501 else if( logHeader::logLevel( *it ) <= logPrio::LOG_NOTICE )
502 {
503 logStdFormat(std::cerr, *it);
504 std::cerr << '\n';
505 }
506
507 er = it;
508 ++it;
509
510 //Erase while locked so we don't collide with a push_back.
511 lock.lock();
512 m_logQueue.erase(er);
513 lock.unlock();
514 }
515 }
516
517 //m_logFile.
518 ///\todo must check this for errors, and investigate how `fsyncgate` impacts us
519 this->flush();
520
521 //We only pause if there's nothing to do.
522 if(m_logQueue.empty() && !m_logShutdown) std::this_thread::sleep_for( std::chrono::duration<unsigned long, std::nano>(m_writePause));
523 }
524
525 m_logThreadRunning = false;
526}
527
528template<class parentT, class logFileT>
529template<typename logT>
531 const typename logT::messageT & msg,
532 const logPrioT & level
533 )
534{
535 //Very first step is to get the current time.
536 timespecX ts;
537 ts.gettime();
538
539 return logHeader::createLog<logT>(logBuffer, ts, msg, level);
540}
541
542template<class parentT, class logFileT>
543template<typename logT>
545 const timespecX & ts,
546 const typename logT::messageT & msg,
547 const logPrioT & level
548 )
549{
550 return logHeader::createLog<logT>(logBuffer, ts, msg, level);
551}
552
553template<class parentT, class logFileT>
554template<typename logT>
555void logManager<parentT, logFileT>::log( const typename logT::messageT & msg,
556 logPrioT level
557 )
558{
559 //Step 0 check level.
560 if(level == logPrio::LOG_DEFAULT) level = logT::defaultLevel;
561
562 if(level > m_logLevel) return; // We do nothing with this.
563
564 //Step 1 create log
565 bufferPtrT logBuffer;
566 createLog<logT>(logBuffer, msg, level);
567
568 //Step 2 add log to queue
569 std::lock_guard<std::mutex> guard(m_qMutex); //Lock the mutex before pushing back.
570 m_logQueue.push_back(logBuffer);
571
572
573}
574
575template<class parentT, class logFileT>
576template<typename logT>
578 const typename logT::messageT & msg,
579 logPrioT level
580 )
581{
582 //Step 0 check level.
583 if(level == logPrio::LOG_DEFAULT) level = logT::defaultLevel;
584
585 if(level > m_logLevel) return; // We do nothing with this.
586
587 //Step 1 create log
588 bufferPtrT logBuffer;
589 createLog<logT>(logBuffer, ts, msg, level);
590
591 //Step 2 add log to queue
592 std::lock_guard<std::mutex> guard(m_qMutex); //Lock the mutex before pushing back.
593 m_logQueue.push_back(logBuffer);
594
595}
596
597template<class parentT, class logFileT>
598template<typename logT>
600{
601 log<logT>( emptyMessage(), level );
602}
603
604template<class parentT, class logFileT>
605template<typename logT>
607 logPrioT level
608 )
609{
610 log<logT>( ts, emptyMessage(), level );
611}
612
613//class logFileRaw;
614
615//extern template struct logManager<logFileRaw>;
616
617} //namespace logger
618} //namespace MagAOX
619
620#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
iosT & logStdFormat(iosT &ios, flatlogs::bufferPtrT &buffer)
Definition dm.hpp:28
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 logShutdown(bool ls)
Set a new value of logShutdown.
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.
bool logShutdown()
Get the current value of logShutdown.
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