API
 
Loading...
Searching...
No Matches
logdump.hpp
Go to the documentation of this file.
1/** \file logdump.hpp
2 * \brief A simple utility to dump MagAO-X binary logs to stdout.
3 *
4 * \ingroup logdump_files
5 */
6
7#ifndef logdump_hpp
8#define logdump_hpp
9
10#include <iostream>
11#include <cstring>
12
13#include <mx/ioutils/fileUtils.hpp>
14
15#include "../../libMagAOX/libMagAOX.hpp"
16using namespace MagAOX::logger;
17
18using namespace flatlogs;
19
20/** \defgroup logdump logdump: MagAO-X Log Reader
21 * \brief Read a MagAO-X binary log file.
22 *
23 * <a href="../handbook/utils/logdump.html">Utility Documentation</a>
24 *
25 * \ingroup utils
26 *
27 */
28
29/** \defgroup logdump_files logdump Files
30 * \ingroup logdump
31 */
32
33/// An application to dump MagAo-X binary logs to the terminal.
34/** \todo document this
35 * \todo add config for colors, both on/off and options to change.
36 *
37 * \ingroup logdump
38 */
39class logdump : public mx::app::application
40{
41protected:
42
43 std::string m_dir;
44 std::string m_ext;
45 std::string m_file;
46
47 bool m_time {false};
48 bool m_jsonMode {false};
49
50 unsigned long m_pauseTime {250}; ///When following, pause time to check for new data. msec. Default is 250 msec.
51 int m_fileCheckInterval {20}; ///When following, number of loops to wait before checking for a new file. Default is 4.
52
53 std::vector<std::string> m_prefixes;
54
55 size_t m_nfiles {0}; ///Number of files to dump. Default is 0, unless following then the default is 1.
56
57 bool m_follow {false};
58
60
61 std::vector<eventCodeT> m_codes;
62
63 void printLogBuff( const logPrioT & lvl,
64 const eventCodeT & ec,
65 const msgLenT & len,
66 bufferPtrT & logBuff
67 );
68 void printLogJson( const msgLenT & len,
69 bufferPtrT & logBuff
70 );
71
72public:
73 virtual void setupConfig();
74
75 virtual void loadConfig();
76
77 virtual int execute();
78
79 virtual int gettimes(std::vector<std::string> & logs);
80
81};
82
84{
85 config.add("pauseTime","p", "pauseTime" , argType::Required, "", "pauseTime", false, "int", "When following, time in milliseconds to pause before checking for new entries.");
86 config.add("fileCheckInterval","", "fileCheckInterval" , argType::Required, "", "fileCheckInterval", false, "int", "When following, number of pause intervals between checks for new files.");
87
88 config.add("dir","d", "dir" , argType::Required, "", "dir", false, "string", "Directory to search for logs. MagAO-X default is normally used.");
89 config.add("ext","e", "ext" , argType::Required, "", "ext", false, "string", "The file extension of log files. MagAO-X default is normally used.");
90 config.add("nfiles","n", "nfiles" , argType::Required, "", "nfiles", false, "int", "Number of log files to dump. If 0, then all matching files dumped. Default: 0, 1 if following.");
91 config.add("follow","f", "follow" , argType::True, "", "follow", false, "bool", "Follow the log, printing new entries as they appear.");
92 config.add("level","L", "level" , argType::Required, "", "level", false, "int/string", "Minimum log level to dump, either an integer or a string. -1/TELEMETRY [the default], 0/DEFAULT, 1/D1/DBG1/DEBUG2, 2/D2/DBG2/DEBUG1,3/INFO,4/WARNING,5/ERROR,6/CRITICAL,7/FATAL. Note that only the mininum unique string is required.");
93 config.add("code","C", "code" , argType::Required, "", "code", false, "int", "The event code, or vector of codes, to dump. If not specified, all codes are dumped. See logCodes.hpp for a complete list of codes.");
94 config.add("file","F", "file" , argType::Required, "", "file", false, "string", "A single file to process. If no / are found in name it will look in the specified directory (or MagAO-X default).");
95 config.add("time","T", "time" , argType::True, "", "time", false, "bool", "time span mode: prints the ISO 8601 UTC timestamps of the first and last entry, the elapsed time in seconds, and the number of records in the file as a space-delimited string");
96 config.add("json","J", "json" , argType::True, "", "json", false, "bool", "JSON mode: emits one JSON document per line for each record in the log");
97
98
99}
100
102{
103 config(m_pauseTime, "pauseTime");
104 config(m_fileCheckInterval, "fileCheckInterval");
105
106 //Get default log dir
107 std::string tmpstr = mx::sys::getEnv(MAGAOX_env_path);
108 if(tmpstr == "")
109 {
110 tmpstr = MAGAOX_path;
111 }
112 m_dir = tmpstr + "/" + MAGAOX_logRelPath;;
113
114 //Now check for config option for dir
115 config(m_dir, "dir");
116
117 m_ext = ".";
119 config(m_ext, "ext");
120 ///\todo need to check for lack of "." and error or fix
121
122 config(m_file, "file");
123
124 if(m_file == "" && config.nonOptions.size() < 1)
125 {
126 std::cerr << "logdump: need application name. Try logdump -h for help.\n";
127 }
128
129 if(m_file == "" && config.nonOptions.size() > 1)
130 {
131 std::cerr << "logdump: only one application at a time supported. Try logdump -h for help.\n";
132 }
133
134 m_prefixes.resize(config.nonOptions.size());
135 for(size_t i=0;i<config.nonOptions.size(); ++i)
136 {
137 m_prefixes[i] = config.nonOptions[i];
138 }
139
140 if(config.isSet("time")) m_time = true;
141 if(config.isSet("json")) m_jsonMode = true;
142
143 config(m_follow, "follow");
144
145 if(m_follow) m_nfiles = 1; //default to 1 if follow is set.
146 config(m_nfiles, "nfiles");
147
148 tmpstr = "";
149 config(tmpstr, "level");
150 if(tmpstr != "")
151 {
152 m_level = logLevelFromString(tmpstr);
153 }
154
155 config(m_codes, "code");
156
157 std::cerr << m_codes.size() << "\n";
158}
159
161{
162
163 if(m_file == "" && m_prefixes.size() !=1 ) return -1; //error message will have been printed in loadConfig.
164
165 std::vector<std::string> logs;
166
167 if(m_file != "")
168 {
169 if(m_file.find('/') == std::string::npos)
170 {
171 m_file = m_dir + '/' + m_file;
172 }
173 std::cerr << "m_file: " << m_file << "\n";
174
175 logs.push_back(m_file);
176 }
177 else
178 {
179 logs = mx::ioutils::getFileNames( m_dir, m_prefixes[0], "", m_ext);
180 }
181
182 ///\todo if follow is set, then should nfiles default to 1 unless explicitly set?
183 if(m_nfiles == 0)
184 {
185 m_nfiles = logs.size();
186 }
187
188 if(m_nfiles > logs.size()) m_nfiles = logs.size();
189
190 if(m_time)
191 {
192 return gettimes(logs);
193 }
194
195 bool firstRun = true; //for only showing latest entries on first run when following.
196
197 for(size_t i=logs.size() - m_nfiles; i < logs.size(); ++i)
198 {
199 std::string fname = logs[i];
200 FILE * fin;
201
202 bufferPtrT head(new char[logHeader::maxHeadSize]);
203
204 bufferPtrT logBuff;
205
206 fin = fopen(fname.c_str(), "rb");
207
208 //--> get size here!!
209 off_t finSize = mx::ioutils::fileSize( fileno(fin) );
210
211 std::cerr << fname << "\n";
212
213 off_t totNrd = 0;
214
215 size_t buffSz = 0;
216 while(!feof(fin)) //<--This should be an exit condition controlled by loop logic, not feof.
217 {
218 int nrd;
219
220 ///\todo check for errors on all reads . . .
221
222 //Read next header
223 nrd = fread( head.get(), sizeof(char), logHeader::minHeadSize, fin);
224 if(nrd == 0)
225 {
226 //If we're following and on the last log file, wait for more to show up.
227 if( m_follow == true && i == logs.size()-1)
228 {
229 int check = 0;
230 firstRun = false; //from now on we show all logs
231 while(nrd == 0)
232 {
233 std::this_thread::sleep_for( std::chrono::duration<unsigned long, std::milli>(m_pauseTime));
234 clearerr(fin);
235 nrd = fread( head.get(), sizeof(char), logHeader::minHeadSize, fin);
236 if(nrd > 0) break;
237
238 ++check;
239 if(check >= m_fileCheckInterval)
240 {
241 //Check if a new file exists now.
242 size_t oldsz = logs.size();
243 logs = mx::ioutils::getFileNames( m_dir, m_prefixes[0], "", m_ext);
244 if(logs.size() > oldsz)
245 {
246 //new file(s) detected;
247 break;
248 }
249 check = 0;
250 }
251 }
252 }
253 else
254 {
255 break;
256 }
257 }
258
259 //We got here without any data, probably means time to get a new file.
260 if(nrd == 0) break;
261
262 totNrd += nrd;
263
265 {
266 //Intermediate size message, read two more bytes
267 nrd = fread( head.get() + logHeader::minHeadSize, sizeof(char), sizeof(msgLen1T), fin);
268 }
269 else if( logHeader::msgLen0(head) == logHeader::MAX_LEN0)
270 {
271 //Large size message: read 8 more bytes
272 nrd = fread( head.get() + logHeader::minHeadSize, sizeof(char), sizeof(msgLen2T), fin);
273 }
274
275 logPrioT lvl = logHeader::logLevel(head);
277 msgLenT len = logHeader::msgLen(head);
278
279 //Here: check if lvl, eventCode, etc, match what we want.
280 //If not, fseek and loop.
281 if(lvl > m_level)
282 {
283 fseek(fin, len, SEEK_CUR);
284 continue;
285 }
286
287
288 if(m_codes.size() > 0)
289 {
290 bool found = false;
291 for(size_t c = 0; c< m_codes.size(); ++c)
292 {
293 if( m_codes[c] == ec )
294 {
295 found = true;
296 break;
297 }
298 }
299
300 if(!found)
301 {
302 fseek(fin, len, SEEK_CUR);
303 continue;
304 }
305 }
306
307 size_t hSz = logHeader::headerSize(head);
308
309 if( (size_t) hSz + (size_t) len > buffSz )
310 {
311 logBuff = bufferPtrT(new char[hSz + len]);
312 }
313 memcpy( logBuff.get(), head.get(), hSz);
314
315 ///\todo what do we do if nrd not equal to expected size?
316 nrd = fread( logBuff.get() + hSz, sizeof(char), len, fin);
317 // If not following, exit loop without printing the incomplete log entry (go on to next file).cd
318 // If following, wait for it, but also be checking for new log file in case of crash
319
320 totNrd += nrd;
321
322 if(m_follow && firstRun && finSize > 512 && totNrd < finSize-512)
323 {
324 //firstRun = false;
325 continue;
326 }
327
328 if (!logVerify(ec, logBuff, len))
329 {
330 std::cerr << "Log " << fname << " failed verification on code=" << ec << " at byte=" << totNrd-len-hSz <<". File possibly corrupt. Exiting." << std::endl;
331 return -1;
332 }
333
334 if (m_jsonMode)
335 {
336 printLogJson(len, logBuff);
337 }
338 else
339 {
340 printLogBuff(lvl, ec, len, logBuff);
341 }
342
343 }
344
345 fclose(fin);
346 }
347
348 return 0;
349}
350
351inline
353 const eventCodeT & ec,
354 const msgLenT & len,
355 bufferPtrT & logBuff
356 )
357{
358 static_cast<void>(len); //be unused
359
360 if(ec == eventCodes::GIT_STATE)
361 {
362 if(git_state::repoName(logHeader::messageBuffer(logBuff)) == "MagAOX")
363 {
364 for(int i=0;i<80;++i) std::cout << '-';
365 std::cout << "\n\t\t\t\t SOFTWARE RESTART\n";
366 for(int i=0;i<80;++i) std::cout << '-';
367 std::cout << '\n';
368 }
369
370 }
371
372 if(lvl < logPrio::LOG_INFO)
373 {
374 if(lvl == logPrio::LOG_EMERGENCY)
375 {
376 std::cout << "\033[104m\033[91m\033[5m\033[1m";
377 }
378
379 if(lvl == logPrio::LOG_ALERT)
380 {
381 std::cout << "\033[101m\033[5m";
382 }
383
384 if(lvl == logPrio::LOG_CRITICAL)
385 {
386 std::cout << "\033[41m\033[1m";
387 }
388
389 if(lvl == logPrio::LOG_ERROR)
390 {
391 std::cout << "\033[91m\033[1m";
392 }
393
394 if(lvl == logPrio::LOG_WARNING)
395 {
396 std::cout << "\033[93m\033[1m";
397 }
398
399 if(lvl == logPrio::LOG_NOTICE)
400 {
401 std::cout << "\033[1m";
402 }
403
404 }
405
406 logStdFormat( std::cout, logBuff);
407
408 std::cout << "\033[0m";
409 std::cout << std::endl;
410}
411
412
413inline
415 bufferPtrT & logBuff
416 )
417{
418 static_cast<void>(len); //be unused
419 logJsonFormat(std::cout, logBuff);
420 std::cout << std::endl;
421
422}
423
424
425int logdump::gettimes(std::vector<std::string> & logs)
426{
427 for(size_t i=logs.size() - m_nfiles; i < logs.size(); ++i)
428 {
429 std::string fname = logs[i];
430 FILE * fin;
431
432 bufferPtrT head(new char[logHeader::maxHeadSize]);
433
434 fin = fopen(fname.c_str(), "rb");
435
436 //--> get size here!!
437 //off_t finSize = mx::ioutils::fileSize( fileno(fin) );
438
439
440 off_t totNrd = 0;
441
442 //size_t buffSz = 0;
443
444 //Read firs header
445
446 int nrd;
447
448 ///\todo check for errors on all reads . . .
449
450 //Read next header
451 nrd = fread( head.get(), sizeof(char), logHeader::minHeadSize, fin);
452 if(nrd == 0)
453 {
454 std::cerr << "got no header\n";
455 return 0;
456 }
457
459 {
460 //Intermediate size message, read two more bytes
461 nrd = fread( head.get() + logHeader::minHeadSize, sizeof(char), sizeof(msgLen1T), fin);
462 }
463 else if( logHeader::msgLen0(head) == logHeader::MAX_LEN0)
464 {
465 //Large size message: read 8 more bytes
466 nrd = fread( head.get() + logHeader::minHeadSize, sizeof(char), sizeof(msgLen2T), fin);
467 }
468
469 //logPrioT lvl = logHeader::logLevel(head);
470 //eventCodeT ec = logHeader::eventCode(head);
471 msgLenT len = logHeader::msgLen(head);
472 timespecX ts0 = logHeader::timespec(head);
473 //size_t hSz = logHeader::headerSize(head);
474
475 uint32_t nRecords = 1;
476 fseek(fin, len, SEEK_CUR);
477
478 timespecX ts;
479
480 while(!feof(fin)) //<--This should be an exit condition controlled by loop logic, not feof.
481 {
482 int nrd;
483
484 //Read next header
485 nrd = fread( head.get(), sizeof(char), logHeader::minHeadSize, fin);
486 if(nrd == 0)
487 {
488 break;
489 }
490 nRecords += 1;
491
492 //We got here without any data, probably means time to get a new file.
493 if(nrd == 0) break;
494
495 totNrd += nrd;
496
498 {
499 //Intermediate size message, read two more bytes
500 nrd = fread( head.get() + logHeader::minHeadSize, sizeof(char), sizeof(msgLen1T), fin);
501 }
502 else if( logHeader::msgLen0(head) == logHeader::MAX_LEN0)
503 {
504 //Large size message: read 8 more bytes
505 nrd = fread( head.get() + logHeader::minHeadSize, sizeof(char), sizeof(msgLen2T), fin);
506 }
507
508 //lvl = logHeader::logLevel(head);
509 //ec = logHeader::eventCode(head);
510 len = logHeader::msgLen(head);
511 ts = logHeader::timespec(head);
512 //hSz = logHeader::headerSize(head);
513
514 fseek(fin, len, SEEK_CUR);
515
516
517 }
518
519 fclose(fin);
520
521 double t0 = ts0.time_s + ts0.time_ns/1e9;
522 double t = ts.time_s + ts.time_ns/1e9;
523
524 std::cout << fname << " " << ts0.ISO8601DateTimeStrX() << "Z " << ts.ISO8601DateTimeStrX() << "Z " << t-t0 << " " << nRecords << std::endl;
525 }
526
527 return 0;
528}
529
530#endif //logdump_hpp
static constexpr int minHeadSize
The minimum header size.
Definition logHeader.hpp:84
static constexpr size_t MAX_LEN0
The max value in the msgLen0 field.
Definition logHeader.hpp:76
static constexpr int maxHeadSize
The maximum header size.
Definition logHeader.hpp:90
An application to dump MagAo-X binary logs to the terminal.
Definition logdump.hpp:40
bool m_follow
Number of files to dump. Default is 0, unless following then the default is 1.
Definition logdump.hpp:57
size_t m_nfiles
Definition logdump.hpp:55
logPrioT m_level
Definition logdump.hpp:59
std::string m_dir
Definition logdump.hpp:43
std::string m_file
Definition logdump.hpp:45
virtual int gettimes(std::vector< std::string > &logs)
Definition logdump.hpp:425
std::vector< eventCodeT > m_codes
Definition logdump.hpp:61
void printLogJson(const msgLenT &len, bufferPtrT &logBuff)
Definition logdump.hpp:414
virtual int execute()
Definition logdump.hpp:160
int m_fileCheckInterval
When following, pause time to check for new data. msec. Default is 250 msec.
Definition logdump.hpp:51
bool m_time
Definition logdump.hpp:47
virtual void setupConfig()
Definition logdump.hpp:83
virtual void loadConfig()
Definition logdump.hpp:101
unsigned long m_pauseTime
Definition logdump.hpp:50
void printLogBuff(const logPrioT &lvl, const eventCodeT &ec, const msgLenT &len, bufferPtrT &logBuff)
Definition logdump.hpp:352
std::string m_ext
Definition logdump.hpp:44
std::vector< std::string > m_prefixes
When following, number of loops to wait before checking for a new file. Default is 4.
Definition logdump.hpp:53
bool m_jsonMode
Definition logdump.hpp:48
#define MAGAOX_default_logExt
The extension for MagAO-X binary log files.
Definition defaults.hpp:22
#define MAGAOX_logRelPath
The relative path to the log directory.
Definition paths.hpp:50
#define MAGAOX_path
The path to the MagAO-X system files.
Definition paths.hpp:22
#define MAGAOX_env_path
Environment variable setting the MagAO-X path.
uint16_t msgLen1T
The type used for intermediate message length.
Definition logDefs.hpp:54
uint16_t eventCodeT
The type of an event code (16-bit unsigned int).
Definition logDefs.hpp:40
uint64_t msgLen2T
The type used for long message length.
Definition logDefs.hpp:62
msgLen2T msgLenT
The type used to refer to the message length, regardless of length.
Definition logDefs.hpp:69
int8_t logPrioT
The type of the log priority code.
Definition logDefs.hpp:21
static int eventCode(bufferPtrT &logBuffer, const eventCodeT &ec)
Set the event code of a log entry.
static msgLen0T msgLen0(bufferPtrT &logBuffer)
Extract the short message length of a log entry message.
static void * messageBuffer(bufferPtrT &logBuffer)
Get the message buffer address.
std::shared_ptr< char > bufferPtrT
The log entry buffer smart pointer.
Definition logHeader.hpp:58
static size_t headerSize(bufferPtrT &logBuffer)
Get the size of the header, including the variable size length field, for an existing logBuffer.
static int msgLen(bufferPtrT &logBuffer, const msgLenT &msgLen)
Set the message length of a log entry message.
static int logLevel(bufferPtrT &logBuffer, const logPrioT &lvl)
Set the level of a log entry in a logBuffer header.
static int timespec(bufferPtrT &logBuffer, const timespecX &ts)
Set the timespec of a log entry.
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_CRITICAL
The process can not continue and will shut down (fatal)
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
static constexpr logPrioT LOG_DEFAULT
Used to denote "use the default level for this log type".
static constexpr logPrioT LOG_EMERGENCY
Normal operations of the entire system should be shut down immediately.
static constexpr logPrioT LOG_ALERT
This should only be used if some action is required by operators to keep the system safe.
logPrioT logLevelFromString(const std::string &str)
Get the log priority from a string, which might have the number or the name.
static std::string repoName(void *msgBuffer)
Access the repo name field.
Definition git_state.hpp:85
A fixed-width timespec structure.
Definition timespecX.hpp:35
nanosecT time_ns
Nanoseconds.
Definition timespecX.hpp:37
secT time_s
Time since the Unix epoch.
Definition timespecX.hpp:36
std::string ISO8601DateTimeStrX()
Get a date-time string in ISO 8601 format for timespecX.