MythTV master
loggingserver.cpp
Go to the documentation of this file.
1#include <fstream>
2#include <thread>
3
4#include <QtGlobal>
5#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
6#include <QtSystemDetection>
7#endif
8#include <QAtomicInt>
9#include <QChar> // Fix Qt6 GCC SFINAE warning
10#include <QMutex>
11#include <QMutexLocker>
12#include <QWaitCondition>
13#include <QList>
14#include <QQueue>
15#include <QHash>
16#include <QCoreApplication>
17#include <QFileInfo>
18#include <QStringList>
19#include <QMap>
20#include <QSocketNotifier>
21#include <iostream>
22
23#include "mythconfig.h"
24#include "mythlogging.h"
25#include "logging.h"
26#include "loggingserver.h"
27#include "mythdb.h"
28#include "dbutil.h"
29#include "exitcodes.h"
30#include "compat.h"
31
32#include <cstdlib>
33#ifndef Q_OS_WINDOWS
34#include "mythsyslog.h"
35#if CONFIG_SYSTEMD_JOURNAL
36#define SD_JOURNAL_SUPPRESS_LOCATION 1 // NOLINT(cppcoreguidelines-macro-usage)
37#include <systemd/sd-journal.h>
38#endif
39#endif
40#include <cstdarg>
41#include <cstring>
42#include <sys/stat.h>
43#include <sys/types.h>
44#include <fcntl.h>
45#include <cstdio>
46#include <unistd.h>
47#if HAVE_GETTIMEOFDAY
48#include <sys/time.h>
49#endif
50#include <csignal>
51
52static QMutex loggerMapMutex;
53static QMap<QString, LoggerBase *> loggerMap;
54
56
57using LoggerList = QList<LoggerBase *>;
58
61 std::chrono::seconds m_itemEpoch;
62};
63
64// A list of logging objects that process messages.
65static QMutex gLoggerListMutex;
66static LoggerListItem *gLoggerList {nullptr};
67
68// This is a FIFO queue containing the incoming messages "sent" from a
69// client to this server. As each message arrives, it is queued here
70// for later retrieval by a different thread. This used to be
71// populated by a thread that received messages from the network, and
72// drained by a different thread that logged the messages. It is now
73// populated by the thread that generated the message, and drained by
74// a different thread that logs the messages.
75static QMutex gLogItemListMutex;
77static QWaitCondition gLogItemListNotEmpty;
78
82LoggerBase::LoggerBase(const char *string) :
83 m_handle(string)
84{
85 QMutexLocker locker(&loggerMapMutex);
86 loggerMap.insert(m_handle, this);
87}
88
89
93{
94 QMutexLocker locker(&loggerMapMutex);
95 loggerMap.remove(m_handle);
96}
97
98
103 m_ofstream(filename, std::ios::app)
104{
105 LOG(VB_GENERAL, LOG_INFO, QString("Added logging to %1")
106 .arg(filename));
107}
108
109
112{
113 if(m_ofstream.is_open())
114 {
115 LOG(VB_GENERAL, LOG_INFO, QString("Removed logging to %1")
116 .arg(m_handle));
117 m_ofstream.close();
118 }
119}
120
121FileLogger *FileLogger::create(const QString& filename, QMutex *mutex)
122{
123 QByteArray ba = filename.toLocal8Bit();
124 const char *file = ba.constData();
125 auto *logger =
126 dynamic_cast<FileLogger *>(loggerMap.value(filename, nullptr));
127
128 if (logger)
129 return logger;
130
131 // Need to add a new FileLogger
132 mutex->unlock();
133 // inserts into loggerMap
134 logger = new FileLogger(file);
135 mutex->lock();
136
137 return logger;
138}
139
143{
144 m_ofstream.close();
145
146 m_ofstream.open(qPrintable(m_handle), std::ios::app);
147 LOG(VB_GENERAL, LOG_INFO, QString("Rolled logging on %1") .arg(m_handle));
148}
149
153{
154 if (!m_ofstream.is_open())
155 return false;
156
157 std::string line = item->toString();
158
159 m_ofstream << line << std::flush;
160
161 if (m_ofstream.bad())
162 {
163 LOG(VB_GENERAL, LOG_ERR,
164 QString("Closed Log output to %1 due to unrecoverable error(s).").arg(m_handle));
165 m_ofstream.close();
166 return false;
167 }
168 return true;
169}
170
171#ifndef Q_OS_WINDOWS
174SyslogLogger::SyslogLogger(bool open) :
175 LoggerBase(nullptr)
176{
177 if (open)
178 {
179 openlog(nullptr, LOG_NDELAY, 0 );
180 m_opened = true;
181 }
182
183 LOG(VB_GENERAL, LOG_INFO, "Added syslogging");
184}
185
187SyslogLogger::~SyslogLogger()
188{
189 LOG(VB_GENERAL, LOG_INFO, "Removing syslogging");
190 if (m_opened)
191 closelog();
192}
193
194SyslogLogger *SyslogLogger::create(QMutex *mutex, bool open)
195{
196 auto *logger = dynamic_cast<SyslogLogger *>(loggerMap.value("", nullptr));
197 if (logger)
198 return logger;
199
200 // Need to add a new FileLogger
201 mutex->unlock();
202 // inserts into loggerMap
203 logger = new SyslogLogger(open);
204 mutex->lock();
205
206 return logger;
207}
208
209
212bool SyslogLogger::logmsg(LoggingItem *item)
213{
214 if (!m_opened || item->facility() <= 0)
215 return false;
216
217 char shortname = item->getLevelChar();
218 syslog(item->level() | item->facility(), "%s[%d]: %c %s %s:%d (%s) %s",
219 qPrintable(item->appName()), item->pid(), shortname,
220 qPrintable(item->threadName()), qPrintable(item->file()), item->line(),
221 qPrintable(item->function()), qPrintable(item->message()));
222
223 return true;
224}
225
226#if CONFIG_SYSTEMD_JOURNAL
228JournalLogger::JournalLogger() :
229 LoggerBase(nullptr)
230{
231 LOG(VB_GENERAL, LOG_INFO, "Added journal logging");
232}
233
235JournalLogger::~JournalLogger()
236{
237 LOG(VB_GENERAL, LOG_INFO, "Removing journal logging");
238}
239
240JournalLogger *JournalLogger::create(QMutex *mutex)
241{
242 auto *logger = dynamic_cast<JournalLogger *>(loggerMap.value("", nullptr));
243 if (logger)
244 return logger;
245
246 // Need to add a new FileLogger
247 mutex->unlock();
248 // inserts into loggerMap
249 logger = new JournalLogger();
250 mutex->lock();
251
252 return logger;
253}
254
255
258bool JournalLogger::logmsg(LoggingItem *item)
259{
260 sd_journal_send(
261 "MESSAGE=%s", qUtf8Printable(item->message()),
262 "PRIORITY=%d", item->level(),
263 "CODE_FILE=%s", qUtf8Printable(item->file()),
264 "CODE_LINE=%d", item->line(),
265 "CODE_FUNC=%s", qUtf8Printable(item->function()),
266 "SYSLOG_IDENTIFIER=%s", qUtf8Printable(item->appName()),
267 "SYSLOG_PID=%d", item->pid(),
268 "MYTH_THREAD=%s", qUtf8Printable(item->threadName()),
269 NULL
270 );
271 return true;
272}
273#endif
274#endif
275
276#ifndef Q_OS_WINDOWS
279
280void logSigHup(void)
281{
282 if (!logForwardThread)
283 return;
284
285 // This will be running in the thread that's used by SignalHandler
286 // Emit the signal which is connected to a slot that runs in the actual
287 // handling thread.
289}
290#endif
291
292
295 MThread("LogForward")
296{
297 moveToThread(qthread());
298}
299
302{
303 stop();
304 wait();
305}
306
311{
312 RunProlog();
313
316 Qt::QueuedConnection);
317
318 qRegisterMetaType<QList<QByteArray> >("QList<QByteArray>");
319
320 while (!m_aborted)
321 {
322 qApp->processEvents(QEventLoop::AllEvents, 10);
323 qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
324
325 {
326 QMutexLocker lock(&gLogItemListMutex);
327 if (gLogItemList.isEmpty() &&
328 !gLogItemListNotEmpty.wait(lock.mutex(), 90))
329 {
330 continue;
331 }
332
333 int processed = 0;
334 while (!gLogItemList.isEmpty())
335 {
336 processed++;
337 LoggingItem *item = gLogItemList.takeFirst();
338 lock.unlock();
339 forwardMessage(item);
340 item->DecrRef();
341
342 // Force a processEvents every 128 messages so a busy queue
343 // doesn't preclude timer notifications, etc.
344 if ((processed & 127) == 0)
345 {
346 qApp->processEvents(QEventLoop::AllEvents, 10);
347 qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
348 }
349
350 lock.relock();
351 }
352 }
353 }
354
355 LoggerList loggers;
356
357 {
358 QMutexLocker lock(&loggerMapMutex);
359 loggers = loggerMap.values();
360 }
361
362 while (!loggers.isEmpty())
363 {
364 LoggerBase *logger = loggers.takeFirst();
365 delete logger;
366 }
367
368 RunEpilog();
369}
370
371
374{
375#ifndef Q_OS_WINDOWS
376 LOG(VB_GENERAL, LOG_INFO, "SIGHUP received, rolling log files.");
377
378 /* SIGHUP was sent. Close and reopen debug logfiles */
379 QMutexLocker locker(&loggerMapMutex);
380 QMap<QString, LoggerBase *>::iterator it;
381 for (it = loggerMap.begin(); it != loggerMap.end(); ++it)
382 {
383 it.value()->reopen();
384 }
385#endif
386}
387
389{
390 QMutexLocker lock(&gLoggerListMutex);
391 LoggerListItem *logItem = gLoggerList;
392
393 if (logItem)
394 {
395 logItem->m_itemEpoch = nowAsDuration<std::chrono::seconds>();
396 }
397 else
398 {
399 QMutexLocker lock2(&loggerMapMutex);
400
401 // Need to find or create the loggers
402 auto *loggers = new LoggerList;
403
404 // FileLogger from logFile
405 QString logfile = item->logFile();
406 if (!logfile.isEmpty())
407 {
408 LoggerBase *logger = FileLogger::create(logfile, lock2.mutex());
409
410 if (logger && loggers)
411 loggers->insert(0, logger);
412 }
413
414#ifndef Q_OS_WINDOWS
415 // SyslogLogger from facility
416 int facility = item->facility();
417 if (facility > 0)
418 {
419 LoggerBase *logger = SyslogLogger::create(lock2.mutex());
420
421 if (logger && loggers)
422 loggers->insert(0, logger);
423 }
424
425#if CONFIG_SYSTEMD_JOURNAL
426 // Journal Logger
427 if (facility == SYSTEMD_JOURNAL_FACILITY)
428 {
429 LoggerBase *logger = JournalLogger::create(lock2.mutex());
430
431 if (logger && loggers)
432 loggers->insert(0, logger);
433 }
434#endif
435#endif
436
437 // Add the list of loggers for this client into the map.
438 logItem = new LoggerListItem;
439 logItem->m_itemEpoch = nowAsDuration<std::chrono::seconds>();
440 logItem->m_itemList = loggers;
441 gLoggerList = logItem;
442 }
443
444 // Does this client have an entry in the loggers map, does that
445 // entry have a list of loggers, and does that list have anything
446 // in it. I.E. is there anywhere to log this item.
447 if (logItem->m_itemList && !logItem->m_itemList->isEmpty())
448 {
449 // Log this item on each of the loggers.
450 for (auto *it : std::as_const(*logItem->m_itemList))
451 it->logmsg(item);
452 }
453}
454
457{
458 m_aborted = true;
459}
460
462{
465
466 std::this_thread::sleep_for(10ms);
468}
469
471{
472 if (!logForwardThread)
473 return;
474
476 delete logForwardThread;
477 logForwardThread = nullptr;
478}
479
480// Take a logging item and queue it for the logging server thread to
481// process.
483{
484 QMutexLocker lock(&gLogItemListMutex);
485
486 bool wasEmpty = gLogItemList.isEmpty();
487 item->IncrRef();
488 gLogItemList.append(item);
489
490 if (wasEmpty)
491 gLogItemListNotEmpty.wakeAll();
492}
493
494#include "moc_loggingserver.cpp"
static void logger(cdio_log_level_t level, const char *message)
Definition: cddecoder.cpp:38
File-based logger - used for logfiles and console.
Definition: loggingserver.h:46
static FileLogger * create(const QString &filename, QMutex *mutex)
std::ofstream m_ofstream
Output file stream for the log file.
Definition: loggingserver.h:54
bool logmsg(LoggingItem *item) override
Process a log message, writing to the logfile.
~FileLogger() override
FileLogger deconstructor - close the logfile.
void reopen(void) override
Reopen the logfile after a SIGHUP.
FileLogger(const char *filename)
FileLogger constructor.
The logging thread that forwards received messages to the consuming loggers via ZeroMQ.
Definition: loggingserver.h:91
void run(void) override
Run the log forwarding thread.
static void handleSigHup(void)
SIGHUP handler - reopen all open logfiles for logrollers.
static void forwardMessage(LoggingItem *item)
LogForwardThread()
LogForwardThread constructor.
void stop(void)
Stop the thread by setting the abort flag.
~LogForwardThread() override
LogForwardThread destructor.
bool m_aborted
Flag to abort the thread.
void incomingSigHup(void)
Base class for the various logging mechanisms.
Definition: loggingserver.h:29
virtual ~LoggerBase()
LoggerBase Deconstructor.
QString m_handle
semi-opaque handle for identifying instance
Definition: loggingserver.h:41
LoggerBase(const char *string)
LoggerBase Constructor.
The logging items that are generated by LOG() and are sent to the console.
Definition: logging.h:53
int level
Definition: logging.h:61
QString appName
Definition: logging.h:67
int pid
Definition: logging.h:56
char getLevelChar(void)
Get the message log level as a single character.
Definition: logging.cpp:211
QString logFile
Definition: logging.h:68
std::string toString()
Long format to string.
Definition: logging.cpp:220
QString file
Definition: logging.h:64
QString message
Definition: logging.h:69
int line
Definition: logging.h:59
QString function
Definition: logging.h:65
QString threadName
Definition: logging.h:66
int facility
Definition: logging.h:62
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:247
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:217
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
virtual int IncrRef(void)
Increments reference count.
void logForwardMessage(LoggingItem *item)
QList< LoggerBase * > LoggerList
static LoggerListItem * gLoggerList
bool logForwardStart(void)
static QMap< QString, LoggerBase * > loggerMap
static QMutex loggerMapMutex
static QMutex gLoggerListMutex
static LoggingItemList gLogItemList
static QWaitCondition gLogItemListNotEmpty
static QMutex gLogItemListMutex
void logForwardStop(void)
LogForwardThread * logForwardThread
QList< LoggingItem * > LoggingItemList
Definition: loggingserver.h:86
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
std::chrono::seconds m_itemEpoch
LoggerList * m_itemList