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