From 6f69e85d50d92b539273ff8163b20f536d34505f Mon Sep 17 00:00:00 2001 From: Gavin Hurlbut Date: Tue, 24 Apr 2012 19:01:23 -0700 Subject: [PATCH] Added mythlogserver Still needs more testing before this is complete. And more plumbing. --- mythtv/external/nzmqt/src/nzmqt.pro | 16 +- mythtv/libs/libmythbase/libmythbase.pro | 3 +- mythtv/libs/libmythbase/logging.cpp | 207 ++-- mythtv/libs/libmythbase/logging.h | 118 ++- mythtv/libs/libmythbase/loggingserver.cpp | 886 ++---------------- mythtv/libs/libmythbase/loggingserver.h | 55 +- mythtv/libs/libmythbase/mythcorecontext.h | 1 + mythtv/libs/libmythbase/verbosedefs.h | 30 + mythtv/programs/mythbackend/main.cpp | 3 +- mythtv/programs/mythlogserver/.gitignore | 1 + .../mythlogserver/commandlineparser.cpp | 23 + .../mythlogserver/commandlineparser.h | 19 + mythtv/programs/mythlogserver/main.cpp | 116 +++ .../programs/mythlogserver/mythlogserver.pro | 16 + .../programs/mythtranscode/replex/replex.pro | 3 + mythtv/programs/programs.pro | 2 +- mythtv/settings.pro | 3 + 17 files changed, 536 insertions(+), 966 deletions(-) create mode 100644 mythtv/programs/mythlogserver/.gitignore create mode 100644 mythtv/programs/mythlogserver/commandlineparser.cpp create mode 100644 mythtv/programs/mythlogserver/commandlineparser.h create mode 100644 mythtv/programs/mythlogserver/main.cpp create mode 100644 mythtv/programs/mythlogserver/mythlogserver.pro diff --git a/mythtv/external/nzmqt/src/nzmqt.pro b/mythtv/external/nzmqt/src/nzmqt.pro index 5bbcdab133f..84ebe885a5e 100644 --- a/mythtv/external/nzmqt/src/nzmqt.pro +++ b/mythtv/external/nzmqt/src/nzmqt.pro @@ -4,7 +4,7 @@ QT += core QT -= gui -TARGET = mythnzmqt-$$LIBVERSION +TARGET = mythnzmqt target.path = $${LIBDIR} #CONFIG += console #CONFIG -= app_bundle @@ -20,13 +20,13 @@ SOURCES += \ HEADERS += \ ../include/nzmqt/nzmqt.hpp \ -# pubsub/PubSubServer.h \ -# pubsub/PubSubClient.h \ -# reqrep/ReqRepServer.h \ -# reqrep/ReqRepClient.h \ -# pushpull/PushPullWorker.h \ -# pushpull/PushPullVentilator.h \ -# pushpull/PushPullSink.h \ + pubsub/PubSubServer.h \ + pubsub/PubSubClient.h \ + reqrep/ReqRepServer.h \ + reqrep/ReqRepClient.h \ + pushpull/PushPullWorker.h \ + pushpull/PushPullVentilator.h \ + pushpull/PushPullSink.h \ NzmqtApp.h \ common/Tools.h diff --git a/mythtv/libs/libmythbase/libmythbase.pro b/mythtv/libs/libmythbase/libmythbase.pro index 4aba2c80f8a..fa6d9bf7944 100644 --- a/mythtv/libs/libmythbase/libmythbase.pro +++ b/mythtv/libs/libmythbase/libmythbase.pro @@ -7,6 +7,7 @@ CONFIG += thread dll target.path = $${LIBDIR} INSTALLS = target INCLUDEPATH += $$PREFIX/include +INCLUDEPATH += $$PREFIX/include/qjson QMAKE_CLEAN += $(TARGET) $(TARGETA) $(TARGETD) $(TARGET0) $(TARGET1) $(TARGET2) @@ -124,4 +125,4 @@ QT += xml sql network include ( ../libs-targetfix.pro ) -LIBS += $$EXTRA_LIBS $$LATE_LIBS -lzmq +LIBS += $$EXTRA_LIBS $$LATE_LIBS -lzmq -lmythqjson diff --git a/mythtv/libs/libmythbase/logging.cpp b/mythtv/libs/libmythbase/logging.cpp index 6413df8f322..8a5e3c3d02b 100644 --- a/mythtv/libs/libmythbase/logging.cpp +++ b/mythtv/libs/libmythbase/logging.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include using namespace std; @@ -54,19 +55,23 @@ extern "C" { #endif #include "nzmqt.hpp" +#include "qjson/qobjecthelper.h" +#include "qjson/serializer.h" +#include "qjson/parser.h" -QMutex logQueueMutex; -QQueue logQueue; -QRegExp logRegExp = QRegExp("[%]{1,2}"); +static QMutex logQueueMutex; +static QQueue logQueue; +static QRegExp logRegExp = QRegExp("[%]{1,2}"); -QMutex logThreadMutex; -QHash logThreadHash; +static LoggerThread *logThread = NULL; +static QMutex logThreadMutex; +static QHash logThreadHash; -QMutex logThreadTidMutex; -QHash logThreadTidHash; +static QMutex logThreadTidMutex; +static QHash logThreadTidHash; -bool logThreadFinished = false; -bool debugRegistration = false; +static bool logThreadFinished = false; +static bool debugRegistration = false; typedef struct { bool propagate; @@ -84,21 +89,6 @@ QString logPropagateArgs; LogLevel_t logLevel = (LogLevel_t)LOG_INFO; -typedef struct { - uint64_t mask; - QString name; - bool additive; - QString helpText; -} VerboseDef; -typedef QMap VerboseMap; - -typedef struct { - int value; - QString name; - char shortname; -} LoglevelDef; -typedef QMap LoglevelMap; - bool verboseInitialized = false; VerboseMap verboseMap; QMutex verboseMapMutex; @@ -121,32 +111,31 @@ void loglevelAdd(int value, QString name, char shortname); void verboseInit(void); void verboseHelp(void); +LoggingItem::LoggingItem() +{ +} LoggingItem::LoggingItem(const char *_file, const char *_function, int _line, LogLevel_t _level, LoggingType _type) : - threadId((uint64_t)(QThread::currentThreadId())), - line(_line), type(_type), level(_level), file(_file), - function(_function), threadName(NULL) + m_threadId((uint64_t)(QThread::currentThreadId())), + m_line(_line), m_type(_type), m_level(_level), m_file(_file), + m_function(_function), m_threadName(NULL) { - time_t epoch; - #if HAVE_GETTIMEOFDAY struct timeval tv; gettimeofday(&tv, NULL); - epoch = tv.tv_sec; - usec = tv.tv_usec; + m_epoch = tv.tv_sec; + m_usec = tv.tv_usec; #else /* Stupid system has no gettimeofday, use less precise QDateTime */ QDateTime date = QDateTime::currentDateTime(); QTime time = date.time(); - epoch = date.toTime_t(); - usec = time.msec() * 1000; + m_epoch = date.toTime_t(); + m_usec = time.msec() * 1000; #endif - localtime_r(&epoch, &tm); - - message[0]='\0'; - message[LOGLINE_MAX]='\0'; + m_message[0]='\0'; + m_message[LOGLINE_MAX]='\0'; setThreadTid(); refcount.ref(); } @@ -154,9 +143,13 @@ LoggingItem::LoggingItem(const char *_file, const char *_function, QByteArray LoggingItem::toByteArray(void) { - QByteArray out(""); + QVariantMap variant = QJson::QObjectHelper::qobject2qvariant(this); + QJson::Serializer serializer; + QByteArray json = serializer.serialize(variant); + +cout << json.constData() << endl; - return out; + return json; } /// \brief Get the name of the thread that produced the LoggingItem @@ -165,11 +158,12 @@ char *LoggingItem::getThreadName(void) { static const char *unknown = "thread_unknown"; - if( threadName ) - return threadName; + if( m_threadName ) + return m_threadName; QMutexLocker locker(&logThreadMutex); - char *name = logThreadHash.value(threadId, (char *)unknown); + char *name = logThreadHash.value(m_threadId, (char *)unknown); + m_threadName = name; return name; } @@ -181,8 +175,8 @@ char *LoggingItem::getThreadName(void) int64_t LoggingItem::getThreadTid(void) { QMutexLocker locker(&logThreadTidMutex); - int64_t tid = logThreadTidHash.value(threadId, 0); - return tid; + m_tid = logThreadTidHash.value(m_threadId, 0); + return m_tid; } /// \brief Set the thread ID of the thread that produced the LoggingItem. This @@ -195,33 +189,36 @@ void LoggingItem::setThreadTid(void) { QMutexLocker locker(&logThreadTidMutex); - if( !logThreadTidHash.contains(threadId) ) + m_tid = logThreadTidHash.value(m_threadId, -1); + if (m_tid == -1) { - int64_t tid = 0; + m_tid = 0; #if defined(linux) - tid = (int64_t)syscall(SYS_gettid); + m_tid = (int64_t)syscall(SYS_gettid); #elif defined(__FreeBSD__) long lwpid; int dummy = thr_self( &lwpid ); (void)dummy; - tid = (int64_t)lwpid; + m_tid = (int64_t)lwpid; #elif CONFIG_DARWIN - tid = (int64_t)mach_thread_self(); + m_tid = (int64_t)mach_thread_self(); #endif - logThreadTidHash[threadId] = tid; + logThreadTidHash[m_threadId] = m_tid; } } /// \brief LoggerThread constructor. Enables debugging of thread registration /// and deregistration if the VERBOSE_THREADS environment variable is /// set. -LoggerThread::LoggerThread(QString filename, bool progress, bool quiet) : +LoggerThread::LoggerThread(QString filename, bool progress, bool quiet, + QString table, int facility) : MThread("Logger"), m_waitNotEmpty(new QWaitCondition()), m_waitEmpty(new QWaitCondition()), m_aborted(false), m_filename(filename), m_progress(progress), - m_quiet(quiet) + m_quiet(quiet), m_appname(QCoreApplication::applicationName()), + m_tablename(table), m_facility(facility) { char *debug = getenv("VERBOSE_THREADS"); if (debug != NULL) @@ -230,6 +227,8 @@ LoggerThread::LoggerThread(QString filename, bool progress, bool quiet) : "Logging thread registration/deregistration enabled!"); debugRegistration = true; } + + moveToThread(qthread()); } /// \brief LoggerThread destructor. Triggers the deletion of all loggers. @@ -269,12 +268,14 @@ void LoggerThread::run(void) kInitializing); if (item) { + fillItem(item); m_zmqSocket->sendMessage(item->toByteArray()); item->deleteItem(); } msleep(100); // wait up to 100ms for mythlogserver to respond - if (m_initialWaiting) + if (m_initialWaiting && + QCoreApplication::applicationName() != MYTH_APPNAME_MYTHLOGSERVER) { // Got no response from mythlogserver, let's assume it's dead and start // it up @@ -307,6 +308,7 @@ void LoggerThread::run(void) LoggingItem *item = logQueue.dequeue(); qLock.unlock(); + fillItem(item); handleItem(item); logConsole(item); item->deleteItem(); @@ -350,54 +352,54 @@ void LoggerThread::messageReceived(const QList &msg) /// \param item The LoggingItem to be handled void LoggerThread::handleItem(LoggingItem *item) { - if (item->type & kRegistering) + if (item->m_type & kRegistering) { - int64_t tid = item->getThreadTid(); + item->m_tid = item->getThreadTid(); QMutexLocker locker(&logThreadMutex); - logThreadHash[item->threadId] = strdup(item->threadName); + logThreadHash[item->m_threadId] = strdup(item->m_threadName); if (debugRegistration) { - snprintf(item->message, LOGLINE_MAX, + snprintf(item->m_message, LOGLINE_MAX, "Thread 0x%" PREFIX64 "X (%" PREFIX64 "d) registered as \'%s\'", - (long long unsigned int)item->threadId, - (long long int)tid, - logThreadHash[item->threadId]); + (long long unsigned int)item->m_threadId, + (long long int)item->m_tid, + logThreadHash[item->m_threadId]); } } - else if (item->type & kDeregistering) + else if (item->m_type & kDeregistering) { int64_t tid = 0; { QMutexLocker locker(&logThreadTidMutex); - if( logThreadTidHash.contains(item->threadId) ) + if( logThreadTidHash.contains(item->m_threadId) ) { - tid = logThreadTidHash[item->threadId]; - logThreadTidHash.remove(item->threadId); + tid = logThreadTidHash[item->m_threadId]; + logThreadTidHash.remove(item->m_threadId); } } QMutexLocker locker(&logThreadMutex); - if (logThreadHash.contains(item->threadId)) + if (logThreadHash.contains(item->m_threadId)) { if (debugRegistration) { - snprintf(item->message, LOGLINE_MAX, + snprintf(item->m_message, LOGLINE_MAX, "Thread 0x%" PREFIX64 "X (%" PREFIX64 "d) deregistered as \'%s\'", - (long long unsigned int)item->threadId, + (long long unsigned int)item->m_threadId, (long long int)tid, - logThreadHash[item->threadId]); + logThreadHash[item->m_threadId]); } - item->threadName = logThreadHash[item->threadId]; - logThreadHash.remove(item->threadId); + item->m_threadName = logThreadHash[item->m_threadId]; + logThreadHash.remove(item->m_threadId); } } - if (item->message[0] != '\0') + if (item->m_message[0] != '\0') { // Send it to mythlogserver m_zmqSocket->sendMessage(item->toByteArray()); @@ -412,24 +414,31 @@ bool LoggerThread::logConsole(LoggingItem *item) char usPart[9]; char timestamp[TIMESTAMP_MAX]; - if (m_quiet || (m_progress && item->level > LOG_ERR)) + if (m_quiet || (m_progress && item->m_level > LOG_ERR)) + return false; + + if (!(item->m_type & kMessage)) return false; item->refcount.ref(); - if (item->type & kStandardIO) - snprintf( line, MAX_STRING_LENGTH, "%s", item->message ); + if (item->m_type & kStandardIO) + snprintf( line, MAX_STRING_LENGTH, "%s", item->m_message ); else { - strftime( timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S", - (const struct tm *)&item->tm ); - snprintf( usPart, 9, ".%06d", (int)(item->usec) ); + time_t epoch = item->epoch(); + struct tm tm; + localtime_r(&epoch, &tm); + + strftime( timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S", + (const struct tm *)&tm ); + snprintf( usPart, 9, ".%06d", (int)(item->m_usec) ); strcat( timestamp, usPart ); char shortname; { QMutexLocker locker(&loglevelMapMutex); - LoglevelDef *lev = loglevelMap.value(item->level, NULL); + LoglevelDef *lev = loglevelMap.value(item->m_level, NULL); if (!lev) shortname = '-'; else @@ -437,7 +446,7 @@ bool LoggerThread::logConsole(LoggingItem *item) } snprintf( line, MAX_STRING_LENGTH, "%s %c %s\n", timestamp, - shortname, item->message ); + shortname, item->m_message ); } int result = write( 1, line, strlen(line) ); @@ -476,6 +485,17 @@ bool LoggerThread::flush(int timeoutMS) return logQueue.isEmpty(); } +void LoggerThread::fillItem(LoggingItem *item) +{ + if (!item) + return; + + item->setAppName(m_appname); + item->setTable(m_tablename); + item->setLogFile(m_filename); + item->setFacility(m_facility); +} + static QAtomicInt item_count; static QAtomicInt malloc_count; @@ -494,7 +514,8 @@ static QTime memory_time; /// \return LoggingItem that was created LoggingItem *LoggingItem::create(const char *_file, const char *_function, - int _line, LogLevel_t _level, int _type) + int _line, LogLevel_t _level, + LoggingType _type) { LoggingItem *item = new LoggingItem(_file, _function, _line, _level, _type); @@ -524,14 +545,11 @@ LoggingItem *LoggingItem::create(const char *_file, LoggingItem *LoggingItem::create(QByteArray &buf) { // Deserialize buffer + QJson::Parser parser; + QVariant variant = parser.parse(buf); - const char *_file = ""; - const char *_function = ""; - int _line = 0; - LogLevel_t _level = (LogLevel_t)LOG_DEBUG; - LoggingType _type = kMessage; - - LoggingItem *item = new LoggingItem(_file, _function, _line, _level, _type); + LoggingItem *item = new LoggingItem; + QJson::QObjectHelper::qvariant2qobject(variant.toMap(), item); malloc_count.ref(); @@ -562,8 +580,8 @@ void LoggingItem::deleteItem(void) { if (!refcount.deref()) { - if (threadName) - free(threadName); + if (m_threadName) + free(m_threadName); item_count.deref(); this->deleteLater(); } @@ -592,7 +610,8 @@ void LogPrintLine( uint64_t mask, LogLevel_t level, const char *file, int line, int type = kMessage; type |= (mask & VB_FLUSH) ? kFlush : 0; type |= (mask & VB_STDIO) ? kStandardIO : 0; - LoggingItem *item = LoggingItem::create(file, function, line, level, type); + LoggingItem *item = LoggingItem::create(file, function, line, level, + (LoggingType)type); if (!item) return; @@ -606,7 +625,7 @@ void LogPrintLine( uint64_t mask, LogLevel_t level, const char *file, int line, } va_start(arguments, format); - vsnprintf(item->message, LOGLINE_MAX, format, arguments); + vsnprintf(item->m_message, LOGLINE_MAX, format, arguments); va_end(arguments); if (formatcopy) @@ -711,9 +730,11 @@ void logStart(QString logfile, int progress, int quiet, int facility, logPropagateCalc(); + QString table = dblog ? QString("logging") : QString(""); + QMutexLocker qLock(&logQueueMutex); if (!logThread) - logThread = new LoggerThread(logfile, progress, quiet); + logThread = new LoggerThread(logfile, progress, quiet, table, facility); logThread->start(); } @@ -744,7 +765,7 @@ void loggingRegisterThread(const QString &name) kRegistering); if (item) { - item->threadName = strdup((char *)name.toLocal8Bit().constData()); + item->setThreadName((char *)name.toLocal8Bit().constData()); logQueue.enqueue(item); } } diff --git a/mythtv/libs/libmythbase/logging.h b/mythtv/libs/libmythbase/logging.h index 37190fcfb8f..5df743080d1 100644 --- a/mythtv/libs/libmythbase/logging.h +++ b/mythtv/libs/libmythbase/logging.h @@ -37,16 +37,36 @@ typedef enum { class LoggerThread; +typedef struct tm tmType; + /// \brief The logging items that are generated by LOG() and are sent to the /// console and to mythlogserver via ZeroMQ class LoggingItem: public QObject { - Q_OBJECT; + Q_OBJECT + + Q_PROPERTY(int pid READ pid WRITE setPid) + Q_PROPERTY(qlonglong tid READ tid WRITE setTid) + Q_PROPERTY(qulonglong threadId READ threadId WRITE setThreadId) + Q_PROPERTY(uint usec READ usec WRITE setUsec) + Q_PROPERTY(int line READ line WRITE setLine) + Q_PROPERTY(int type READ type WRITE setType) + Q_PROPERTY(int level READ level WRITE setLevel) + Q_PROPERTY(int facility READ facility WRITE setFacility) + Q_PROPERTY(qlonglong epoch READ epoch WRITE setEpoch) + Q_PROPERTY(QString file READ file WRITE setFile) + Q_PROPERTY(QString function READ function WRITE setFunction) + Q_PROPERTY(QString threadName READ threadName WRITE setThreadName) + Q_PROPERTY(QString appName READ appName WRITE setAppName) + Q_PROPERTY(QString table READ table WRITE setTable) + Q_PROPERTY(QString logFile READ logFile WRITE setLogFile) + Q_PROPERTY(QString message READ message WRITE setMessage) + Q_ENUMS(LoggingType) + Q_ENUMS(LogLevel_t) friend class LoggerThread; friend void LogPrintLine(uint64_t, LogLevel_t, const char *, int, - const char *, int, const char *, ... ); - friend void loggingRegisterThread(const QString &); + const char *, int, const char *, ... ); public: char *getThreadName(void); @@ -58,22 +78,82 @@ class LoggingItem: public QObject void deleteItem(void); QByteArray toByteArray(void); - protected: QAtomicInt refcount; - uint64_t threadId; - uint32_t usec; - int line; - int type; - LogLevel_t level; - struct tm tm; - const char *file; - const char *function; - char *threadName; - char message[LOGLINE_MAX+1]; + + int pid() const { return m_pid; }; + qlonglong tid() const { return m_tid; }; + qulonglong threadId() const { return m_threadId; }; + uint usec() const { return m_usec; }; + int line() const { return m_line; }; + int type() const { return (int)m_type; }; + int level() const { return (int)m_level; }; + int facility() const { return m_facility; }; + qlonglong epoch() const { return m_epoch; }; + QString file() const { return QString(m_file); }; + QString function() const { return QString(m_function); }; + QString threadName() const { return QString(m_threadName); }; + QString appName() const { return QString(m_appName); }; + QString table() const { return QString(m_table); }; + QString logFile() const { return QString(m_logFile); }; + QString message() const { return QString(m_message); }; + + void setPid(const int val) { m_pid = val; }; + void setTid(const qlonglong val) { m_tid = val; }; + void setThreadId(const qulonglong val) { m_threadId = val; }; + void setUsec(const uint val) { m_usec = val; }; + void setLine(const int val) { m_line = val; }; + void setType(const int val) { m_type = (LoggingType)val; }; + void setLevel(const int val) { m_level = (LogLevel_t)val; }; + void setFacility(const int val) { m_facility = val; }; + void setEpoch(const qlonglong val) { m_epoch = val; }; + void setFile(const QString val) + { m_file = strdup(val.toLocal8Bit().constData()); }; + void setFunction(const QString val) + { m_function = strdup(val.toLocal8Bit().constData()); }; + void setThreadName(const QString val) + { m_threadName = strdup(val.toLocal8Bit().constData()); }; + void setAppName(const QString val) + { m_appName = strdup(val.toLocal8Bit().constData()); }; + void setTable(const QString val) + { m_table = strdup(val.toLocal8Bit().constData()); }; + void setLogFile(const QString val) + { m_logFile = strdup(val.toLocal8Bit().constData()); }; + void setMessage(const QString val) + { + strncpy(m_message, val.toLocal8Bit().constData(), LOGLINE_MAX); + m_message[LOGLINE_MAX] = '\0'; + }; + + const char *rawFile() const { return m_file; }; + const char *rawFunction() const { return m_function; }; + const char *rawThreadName() const { return m_threadName; }; + const char *rawAppName() const { return m_appName; }; + const char *rawTable() const { return m_table; }; + const char *rawLogFile() const { return m_logFile; }; + const char *rawMessage() const { return m_message; }; + + protected: + int m_pid; + qlonglong m_tid; + qulonglong m_threadId; + uint m_usec; + int m_line; + LoggingType m_type; + LogLevel_t m_level; + int m_facility; + qlonglong m_epoch; + const char *m_file; + const char *m_function; + char *m_threadName; + const char *m_appName; + const char *m_table; + const char *m_logFile; + char m_message[LOGLINE_MAX+1]; private: + LoggingItem(); LoggingItem(const char *_file, const char *_function, - int _line, LogLevel_t _level, int _type); + int _line, LogLevel_t _level, LoggingType _type); }; /// \brief The logging thread that consumes the logging queue and dispatches @@ -82,12 +162,14 @@ class LoggerThread : public QObject, public MThread { Q_OBJECT public: - LoggerThread(QString filename, bool progress, bool quiet); + LoggerThread(QString filename, bool progress, bool quiet, QString table, + int facility); ~LoggerThread(); void run(void); void stop(void); bool flush(int timeoutMS = 200000); void handleItem(LoggingItem *item); + void fillItem(LoggingItem *item); private: bool logConsole(LoggingItem *item); QWaitCondition *m_waitNotEmpty; ///< Condition variable for waiting @@ -103,6 +185,10 @@ class LoggerThread : public QObject, public MThread QString m_filename; ///< Filename of debug logfile bool m_progress; ///< show only LOG_ERR and more important (console only) int m_quiet; ///< silence the console (console only) + QString m_appname; ///< Cached application name + QString m_tablename; ///< Cached table name for db logging + int m_facility; ///< Cached syslog facility (or -1 to disable) + nzmqt::PollingZMQContext *m_zmqContext; ///< ZeroMQ context to use in this logger nzmqt::ZMQSocket *m_zmqSocket; ///< ZeroMQ socket to talk to mythlogserver diff --git a/mythtv/libs/libmythbase/loggingserver.cpp b/mythtv/libs/libmythbase/loggingserver.cpp index bf9a715b23b..e2c882c5e8b 100644 --- a/mythtv/libs/libmythbase/loggingserver.cpp +++ b/mythtv/libs/libmythbase/loggingserver.cpp @@ -25,7 +25,6 @@ using namespace std; #include "compat.h" #include -#define SYSLOG_NAMES #ifndef _WIN32 #include #endif @@ -53,33 +52,11 @@ extern "C" { #include #endif -QMutex loggerListMutex; -QList loggerList; +static QMutex loggerListMutex; +static QList loggerList; -QMutex logQueueMutex; -QQueue logQueue; -QRegExp logRegExp = QRegExp("[%]{1,2}"); - -QMutex logThreadMutex; -QHash logThreadHash; - -QMutex logThreadTidMutex; -QHash logThreadTidHash; - -LogServerThread *logThread = NULL; -bool logThreadFinished = false; -bool debugRegistration = false; - -typedef struct { - bool propagate; - int quiet; - int facility; - bool dblog; - QString path; -} LogPropagateOpts; - -LogPropagateOpts logPropagateOpts; -QString logPropagateArgs; +static LogServerThread *logThread = NULL; +static bool logThreadFinished = false; #define TIMESTAMP_MAX 30 #define MAX_STRING_LENGTH (LOGLINE_MAX+120) @@ -92,20 +69,13 @@ void logSighup( int signum, siginfo_t *info, void *secret ); /// \brief LoggerBase class constructor. Adds the new logger instance to the /// loggerList. /// \param string a C-string of the handle for this instance (NULL if unused) -/// \param number an integer for the handle for this instance -LoggerBase::LoggerBase(char *string, int number) +LoggerBase::LoggerBase(char *string) { QMutexLocker locker(&loggerListMutex); if (string) - { - m_handle.string = strdup(string); - m_string = true; - } + m_handle = strdup(string); else - { - m_handle.number = number; - m_string = false; - } + m_handle = NULL; loggerList.append(this); } @@ -116,15 +86,15 @@ LoggerBase::~LoggerBase() QMutexLocker locker(&loggerListMutex); loggerList.removeAll(this); - if (m_string) - free(m_handle.string); + if (m_handle) + free(m_handle); } /// \brief FileLogger constructor /// \param filename Filename of the logfile. FileLogger::FileLogger(char *filename) : - LoggerBase(filename, 0), m_opened(false), m_fd(-1) + LoggerBase(filename), m_opened(false), m_fd(-1) { m_fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0664); m_opened = (m_fd != -1); @@ -145,14 +115,11 @@ FileLogger::~FileLogger() /// This allows for logrollers to be used. void FileLogger::reopen(void) { - char *filename = m_handle.string; - close(m_fd); - m_fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0664); + m_fd = open(m_handle, O_WRONLY|O_CREAT|O_APPEND, 0664); m_opened = (m_fd != -1); - LOG(VB_GENERAL, LOG_INFO, QString("Rolled logging on %1") - .arg(filename)); + LOG(VB_GENERAL, LOG_INFO, QString("Rolled logging on %1") .arg(m_handle)); } /// \brief Process a log message, writing to the logfile @@ -168,36 +135,41 @@ bool FileLogger::logmsg(LoggingItem *item) item->refcount.ref(); - strftime( timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S", - (const struct tm *)&item->tm ); - snprintf( usPart, 9, ".%06d", (int)(item->usec) ); + time_t epoch = item->epoch(); + struct tm tm; + localtime_r(&epoch, &tm); + + strftime(timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S", + (const struct tm *)&tm); + snprintf( usPart, 9, ".%06d", (int)(item->usec()) ); strcat( timestamp, usPart ); char shortname; { QMutexLocker locker(&loglevelMapMutex); - LoglevelMap::iterator it = loglevelMap.find(item->level); + LoglevelMap::iterator it = loglevelMap.find(item->level()); if (it == loglevelMap.end()) shortname = '-'; else shortname = (*it)->shortname; } - if( item->tid ) + if( item->tid() ) snprintf( line, MAX_STRING_LENGTH, - "%s %c [%d/%d] %s %s:%d (%s) - %s\n", - timestamp, shortname, item->pid, item->tid, item->threadName, - item->file, item->line, item->function, item->message ); + "%s %c [%d/%" PREFIX64 "d] %s %s:%d (%s) - %s\n", + timestamp, shortname, item->pid(), item->tid(), + item->rawThreadName(), item->rawFile(), item->line(), + item->rawFunction(), item->rawMessage() ); else snprintf( line, MAX_STRING_LENGTH, "%s %c [%d] %s %s:%d (%s) - %s\n", - timestamp, shortname, item->pid, item->threadName, item->file, - item->line, item->function, item->message ); - } + timestamp, shortname, item->pid(), item->rawThreadName(), + item->rawFile(), item->line(), item->rawFunction(), + item->rawMessage() ); - int result = write( m_fd, line, strlen(line) ); + int result = write(m_fd, line, strlen(line)); - deleteItem(item); + item->deleteItem(); if( result == -1 ) { @@ -214,10 +186,8 @@ bool FileLogger::logmsg(LoggingItem *item) #ifndef _WIN32 /// \brief SyslogLogger constructor /// \param facility Syslog facility to use in logging -SyslogLogger::SyslogLogger() : LoggerBase(NULL, 0), m_opened(false) +SyslogLogger::SyslogLogger() : LoggerBase(NULL), m_opened(false) { - CODE *name; - openlog(NULL, LOG_NDELAY, 0 ); m_opened = true; @@ -236,23 +206,24 @@ SyslogLogger::~SyslogLogger() /// \param item LoggingItem containing the log message to process bool SyslogLogger::logmsg(LoggingItem *item) { - if (!m_opened) + if (!m_opened || item->facility() <= 0) return false; char shortname; { QMutexLocker locker(&loglevelMapMutex); - LoglevelDef *lev = loglevelMap.value(item->level, NULL); + LoglevelDef *lev = loglevelMap.value(item->level(), NULL); if (!lev) shortname = '-'; else shortname = lev->shortname; } - syslog(item->level | item->facility, "%s[%d]: %c %s %s:%d (%s) %s", - item->appName, item->pid, shortname, item->threadName, item->file, - item->line, item->function, item->message); + syslog(item->level() | item->facility(), "%s[%d]: %c %s %s:%d (%s) %s", + item->rawAppName(), item->pid(), shortname, item->rawThreadName(), + item->rawFile(), item->line(), item->rawFunction(), + item->rawMessage()); return true; } @@ -262,7 +233,7 @@ const int DatabaseLogger::kMinDisabledTime = 1000; /// \brief DatabaseLogger constructor /// \param table C-string of the database table to log to -DatabaseLogger::DatabaseLogger(char *table) : LoggerBase(table, 0), +DatabaseLogger::DatabaseLogger(char *table) : LoggerBase(table), m_opened(false), m_loggingTableExists(false) { @@ -272,10 +243,10 @@ DatabaseLogger::DatabaseLogger(char *table) : LoggerBase(table, 0), " line, function, msgtime, level, message) " "VALUES (:HOST, :APP, :PID, :TID, :THREAD, :FILENAME, " " :LINE, :FUNCTION, :MSGTIME, :LEVEL, :MESSAGE)") - .arg(m_handle.string); + .arg(m_handle); LOG(VB_GENERAL, LOG_INFO, QString("Added database logging to table %1") - .arg(m_handle.string)); + .arg(m_handle)); m_thread = new DBLoggerThread(this); m_thread->start(); @@ -349,20 +320,22 @@ bool DatabaseLogger::logmsg(LoggingItem *item) bool DatabaseLogger::logqmsg(MSqlQuery &query, LoggingItem *item) { char timestamp[TIMESTAMP_MAX]; - char *threadName = getThreadName(item); - pid_t tid = getThreadTid(item); - strftime( timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S", - (const struct tm *)&item->tm ); + time_t epoch = item->epoch(); + struct tm tm; + localtime_r(&epoch, &tm); - query.bindValue(":TID", tid); - query.bindValue(":THREAD", threadName); - query.bindValue(":FILENAME", item->file); - query.bindValue(":LINE", item->line); - query.bindValue(":FUNCTION", item->function); + strftime(timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S", + (const struct tm *)&tm); + + query.bindValue(":TID", item->tid()); + query.bindValue(":THREAD", item->threadName()); + query.bindValue(":FILENAME", item->file()); + query.bindValue(":LINE", item->line()); + query.bindValue(":FUNCTION", item->function()); query.bindValue(":MSGTIME", timestamp); - query.bindValue(":LEVEL", item->level); - query.bindValue(":MESSAGE", item->message); + query.bindValue(":LEVEL", item->level()); + query.bindValue(":MESSAGE", item->message()); if (!query.exec()) { @@ -380,7 +353,7 @@ bool DatabaseLogger::logqmsg(MSqlQuery &query, LoggingItem *item) return false; } - deleteItem(item); + item->deleteItem(); return true; } @@ -405,7 +378,7 @@ bool DatabaseLogger::isDatabaseReady(void) if ((db) && db->HaveValidDatabase()) { if ( !m_loggingTableExists ) - m_loggingTableExists = tableExists(m_handle.string); + m_loggingTableExists = tableExists(m_handle); if ( m_loggingTableExists ) ready = true; @@ -449,7 +422,7 @@ DBLoggerThread::DBLoggerThread(DatabaseLogger *logger) : MThread("DBLogger"), m_logger(logger), m_queue(new QQueue), - m_wait(new QWaitCondition()), aborted(false) + m_wait(new QWaitCondition()), m_aborted(false) { } @@ -462,7 +435,7 @@ DBLoggerThread::~DBLoggerThread() QMutexLocker qLock(&m_queueMutex); while (!m_queue->empty()) - deleteItem(m_queue->dequeue()); + m_queue->dequeue()->deleteItem(); delete m_queue; delete m_wait; m_queue = NULL; @@ -479,14 +452,14 @@ void DBLoggerThread::run(void) // at all, and that's undesirable. while (true) { - if ((aborted || (gCoreContext && m_logger->isDatabaseReady()))) + if ((m_aborted || (gCoreContext && m_logger->isDatabaseReady()))) break; QMutexLocker locker(&m_queueMutex); m_wait->wait(locker.mutex(), 100); } - if (!aborted) + if (!m_aborted) { // We want the query to be out of scope before the RunEpilog() so // shutdown occurs correctly as otherwise the connection appears still @@ -495,7 +468,7 @@ void DBLoggerThread::run(void) m_logger->prepare(*query); QMutexLocker qLock(&m_queueMutex); - while (!aborted || !m_queue->isEmpty()) + while (!m_aborted || !m_queue->isEmpty()) { if (m_queue->isEmpty()) { @@ -509,7 +482,7 @@ void DBLoggerThread::run(void) qLock.unlock(); - if (item->message[0] != '\0') + if (item->message()[0] != '\0') { if (!m_logger->logqmsg(*query, item)) { @@ -524,7 +497,7 @@ void DBLoggerThread::run(void) } else { - deleteItem(item); + item->deleteItem(); } qLock.relock(); @@ -538,94 +511,19 @@ void DBLoggerThread::run(void) RunEpilog(); } -/// \brief Tell the thread to stop by setting the aborted flag. +/// \brief Tell the thread to stop by setting the m_aborted flag. void DBLoggerThread::stop(void) { QMutexLocker qLock(&m_queueMutex); - aborted = true; + m_aborted = true; m_wait->wakeAll(); } -/// \brief Get the name of the thread that produced the LoggingItem -/// \param item the LoggingItem in question -/// \return C-string of the thread name -char *getThreadName( LoggingItem *item ) -{ - static const char *unknown = "thread_unknown"; - char *threadName; - - if( !item ) - return( (char *)unknown ); - - if( !item->threadName ) - { - QMutexLocker locker(&logThreadMutex); - if( logThreadHash.contains(item->threadId) ) - threadName = logThreadHash[item->threadId]; - else - threadName = (char *)unknown; - } - else - { - threadName = item->threadName; - } - - return( threadName ); -} - -/// \brief Get the thread ID of the thread that produced the LoggingItem -/// \param item the LoggingItem in question -/// \return Thread ID of the producing thread, cast to a 64-bit signed integer -/// \notes In different platforms, the actual value returned here will vary. -/// The intention is to get a thread ID that will map well to what is -/// shown in gdb. -int64_t getThreadTid( LoggingItem *item ) -{ - pid_t tid = 0; - - if( !item ) - return( 0 ); - - QMutexLocker locker(&logThreadTidMutex); - if( logThreadTidHash.contains(item->threadId) ) - tid = logThreadTidHash[item->threadId]; - - return( tid ); -} - -/// \brief Set the thread ID of the thread that produced the LoggingItem. This -/// code is actually run in the thread in question as part of the call -/// to LOG() -/// \param item the LoggingItem in question -/// \notes In different platforms, the actual value returned here will vary. -/// The intention is to get a thread ID that will map well to what is -/// shown in gdb. -void setThreadTid( LoggingItem *item ) -{ - QMutexLocker locker(&logThreadTidMutex); - - if( ! logThreadTidHash.contains(item->threadId) ) - { - int64_t tid = 0; - -#if defined(linux) - tid = (int64_t)syscall(SYS_gettid); -#elif defined(__FreeBSD__) - long lwpid; - int dummy = thr_self( &lwpid ); - (void)dummy; - tid = (int64_t)lwpid; -#elif CONFIG_DARWIN - tid = (int64_t)mach_thread_self(); -#endif - logThreadTidHash[item->threadId] = tid; - } -} - /// \brief LogServerThread constructor. LogServerThread::LogServerThread() : MThread("LogServer"), m_aborted(false) { + moveToThread(qthread()); } /// \brief LogServerThread destructor. @@ -648,7 +546,7 @@ void LogServerThread::run(void) m_zmqContext->start(); m_zmqInSock = m_zmqContext->createSocket(nzmqt::ZMQSocket::TYP_ROUTER); - connect(m_zmqInSocket, SIGNAL(messageReceived(const QList&)), + connect(m_zmqInSock, SIGNAL(messageReceived(const QList&)), this, SLOT(messageReceived(const QList&))); m_zmqInSock->bindTo("tcp://127.0.0.1:35327"); @@ -672,106 +570,27 @@ void LogServerThread::run(void) /// \brief Handles messages received from logging clients /// \param msg The message received (can be multi-part) -void LoggerThread::messageReceived(const QList &msg) +void LogServerThread::messageReceived(const QList &msg) { - m_zmqPubSock->sendMessage(msg); -} - - - -/// \brief Handles each LoggingItem, generally by handing it off to the -/// various running logger instances. There is a special case for -/// thread registration and deregistration which are also included in -/// the logging queue to keep the thread names in sync with the log -/// messages. -/// \param item The LoggingItem to be handled -void LoggerThread::handleItem(LoggingItem *item) -{ - if (item->type & kRegistering) + QList::const_iterator it = msg.begin(); + int i = 0; + for (; it != msg.end(); ++it, i++) { - int64_t tid = getThreadTid(item); - - QMutexLocker locker(&logThreadMutex); - logThreadHash[item->threadId] = strdup(item->threadName); - - if (debugRegistration) - { - snprintf(item->message, LOGLINE_MAX, - "Thread 0x%" PREFIX64 "X (%" PREFIX64 - "d) registered as \'%s\'", - (long long unsigned int)item->threadId, - (long long int)tid, - logThreadHash[item->threadId]); - } - } - else if (item->type & kDeregistering) - { - int64_t tid = 0; - - { - QMutexLocker locker(&logThreadTidMutex); - if( logThreadTidHash.contains(item->threadId) ) - { - tid = logThreadTidHash[item->threadId]; - logThreadTidHash.remove(item->threadId); - } - } - - QMutexLocker locker(&logThreadMutex); - if (logThreadHash.contains(item->threadId)) - { - if (debugRegistration) - { - snprintf(item->message, LOGLINE_MAX, - "Thread 0x%" PREFIX64 "X (%" PREFIX64 - "d) deregistered as \'%s\'", - (long long unsigned int)item->threadId, - (long long int)tid, - logThreadHash[item->threadId]); - } - item->threadName = logThreadHash[item->threadId]; - logThreadHash.remove(item->threadId); - } + QByteArray buf = *it; + cout << i << ":\t" << buf.toHex().constData() << endl << "\t" << buf.constData() << endl; } + m_zmqPubSock->sendMessage(msg); +} - if (item->message[0] != '\0') - { - QMutexLocker locker(&loggerListMutex); - QList::iterator it; - for (it = loggerList.begin(); it != loggerList.end(); ++it) - (*it)->logmsg(item); - } -} /// \brief Stop the thread by setting the abort flag after waiting a second for /// the queue to be flushed. -void LoggerThread::stop(void) -{ - QMutexLocker qLock(&logQueueMutex); - flush(1000); - aborted = true; - m_waitNotEmpty->wakeAll(); -} - -/// \brief Wait for the queue to be flushed (up to a timeout) -/// \param timeoutMS The number of ms to wait for the queue to flush -/// \return true if the queue is empty, false otherwise -bool LoggerThread::flush(int timeoutMS) +void LogServerThread::stop(void) { - QTime t; - t.start(); - while (!aborted && logQueue.isEmpty() && t.elapsed() < timeoutMS) - { - m_waitNotEmpty->wakeAll(); - int left = timeoutMS - t.elapsed(); - if (left > 0) - m_waitEmpty->wait(&logQueueMutex, left); - } - return logQueue.isEmpty(); + m_aborted = true; } -static QList item_recycler; static QAtomicInt item_count; static QAtomicInt malloc_count; @@ -781,144 +600,6 @@ static int max_count = 0; static QTime memory_time; #endif -/// \brief Create a new LoggingItem -/// \param _file filename of the source file where the log message is from -/// \param _function source function where the log message is from -/// \param _line line number in the source where the log message is from -/// \param _level logging level of the message (LogLevel_t) -/// \param _type type of logging message -/// \return LoggingItem that was created -static LoggingItem *createItem(const char *_file, const char *_function, - int _line, LogLevel_t _level, int _type) -{ - LoggingItem *item = new LoggingItem(_file, _function, _line, _level, _type); - - malloc_count.ref(); - -#if DEBUG_MEMORY - int val = item_count.fetchAndAddRelaxed(1) + 1; - if (val == 0) - memory_time.start(); - max_count = (val > max_count) ? val : max_count; - if (memory_time.elapsed() > 1000) - { - cout<<"current memory usage: " - <refcount.deref()) - { - if (item->threadName) - free(item->threadName); - item_count.deref(); - delete item; - } -} - -/// \brief Fill in the time structure from the current time to make a timestamp -/// for the log message. This is run as part of the LOG() call. -/// \param tm pointer to the time structure to fill in -/// \param usec pointer to a 32bit unsigned int to return the number of us -void LogTimeStamp( struct tm *tm, uint32_t *usec ) -{ - if( !usec || !tm ) - return; - - time_t epoch; - -#if HAVE_GETTIMEOFDAY - struct timeval tv; - gettimeofday(&tv, NULL); - epoch = tv.tv_sec; - *usec = tv.tv_usec; -#else - /* Stupid system has no gettimeofday, use less precise QDateTime */ - QDateTime date = QDateTime::currentDateTime(); - QTime time = date.time(); - epoch = date.toTime_t(); - *usec = time.msec() * 1000; -#endif - - localtime_r(&epoch, tm); -} - -/// \brief Format and send a log message into the queue. This is called from -/// the LOG() macro. The intention is minimal blocking of the caller. -/// \param mask Verbosity mask of the message (VB_*) -/// \param level Log level of this message (LOG_* - matching syslog levels) -/// \param file Filename of source code logging the message -/// \param line Line number within the source of log message source -/// \param function Function name of the log message source -/// \param fromQString true if this message originated from QString -/// \param format printf format string (when not from QString), log message -/// (when from QString) -/// \param ... printf arguments (when not from QString) -void LogPrintLine( uint64_t mask, LogLevel_t level, const char *file, int line, - const char *function, int fromQString, - const char *format, ... ) -{ - va_list arguments; - - QMutexLocker qLock(&logQueueMutex); - - int type = kMessage; - type |= (mask & VB_FLUSH) ? kFlush : 0; - type |= (mask & VB_STDIO) ? kStandardIO : 0; - LoggingItem *item = createItem(file, function, line, level, type); - if (!item) - return; - - char *formatcopy = NULL; - if( fromQString && strchr(format, '%') ) - { - QString string(format); - format = strdup(string.replace(logRegExp, "%%").toLocal8Bit() - .constData()); - formatcopy = (char *)format; - } - - va_start(arguments, format); - vsnprintf(item->message, LOGLINE_MAX, format, arguments); - va_end(arguments); - - if (formatcopy) - free(formatcopy); - - logQueue.enqueue(item); - - if (logThread && logThreadFinished && !logThread->isRunning()) - { - while (!logQueue.isEmpty()) - { - item = logQueue.dequeue(); - qLock.unlock(); - logThread->handleItem(item); - deleteItem(item); - qLock.relock(); - } - } - else if (logThread && !logThreadFinished && (type & kFlush)) - { - logThread->flush(); - } -} #ifndef _WIN32 /// \brief SIGHUP handler - reopen all open logfiles for logrollers @@ -937,48 +618,6 @@ void logSighup( int signum, siginfo_t *info, void *secret ) } #endif -/// \brief Generate the logPropagateArgs global with the latest logging -/// level, mask, etc to propagate to all of the mythtv programs -/// spawned from this one. -void logPropagateCalc(void) -{ - QString mask = verboseString.trimmed(); - mask.replace(QRegExp(" "), ","); - mask.remove(QRegExp("^,")); - logPropagateArgs = " --verbose " + mask; - - if (logPropagateOpts.propagate) - logPropagateArgs += " --logpath " + logPropagateOpts.path; - - QString name = logLevelGetName(logLevel); - logPropagateArgs += " --loglevel " + name; - - for (int i = 0; i < logPropagateOpts.quiet; i++) - logPropagateArgs += " --quiet"; - - if (!logPropagateOpts.dblog) - logPropagateArgs += " --nodblog"; - -#ifndef _WIN32 - if (logPropagateOpts.facility >= 0) - { - CODE *syslogname; - - for (syslogname = &facilitynames[0]; - (syslogname->c_name && - syslogname->c_val != logPropagateOpts.facility); syslogname++); - - logPropagateArgs += QString(" --syslog %1").arg(syslogname->c_name); - } -#endif -} - -/// \brief Check if we are propagating a "--quiet" -/// \return true if --quiet is being propagated -bool logPropagateQuiet(void) -{ - return logPropagateOpts.quiet; -} /// \brief Entry point to start logging for the application. This will /// start up all of the threads needed. @@ -992,58 +631,13 @@ bool logPropagateQuiet(void) /// \param dblog true if database logging is requested /// \param propagate true if the logfile path needs to be propagated to child /// processes. -void logStart(QString logfile, int progress, int quiet, int facility, - LogLevel_t level, bool dblog, bool propagate) +void logServerStart(void) { - LoggerBase *logger; - - { - QMutexLocker qLock(&logQueueMutex); - if (!logThread) - logThread = new LoggerThread(); - } - - if (logThread->isRunning()) + if (logThread && logThread->isRunning()) return; - logLevel = level; - LOG(VB_GENERAL, LOG_NOTICE, QString("Setting Log Level to LOG_%1") - .arg(logLevelGetName(logLevel).toUpper())); - - logPropagateOpts.propagate = propagate; - logPropagateOpts.quiet = quiet; - logPropagateOpts.facility = facility; - logPropagateOpts.dblog = dblog; - - if (propagate) - { - QFileInfo finfo(logfile); - QString path = finfo.path(); - logPropagateOpts.path = path; - } - - logPropagateCalc(); - - /* log to the console */ - logger = new FileLogger((char *)"-", progress, quiet); - - /* Debug logfile */ - if( !logfile.isEmpty() ) - logger = new FileLogger((char *)logfile.toLocal8Bit().constData(), - false, false); - -#ifndef _WIN32 - /* Syslog */ - if( facility == -1 ) - LOG(VB_GENERAL, LOG_CRIT, - "Syslogging facility unknown, disabling syslog output"); - else if( facility >= 0 ) - logger = new SyslogLogger(facility); -#endif - - /* Database */ - if( dblog ) - logger = new DatabaseLogger((char *)"logging"); + if (!logThread) + logThread = new LogServerThread(); #ifndef _WIN32 /* Setup SIGHUP */ @@ -1055,13 +649,11 @@ void logStart(QString logfile, int progress, int quiet, int facility, sigaction( SIGHUP, &sa, NULL ); #endif - (void)logger; - logThread->start(); } /// \brief Entry point for stopping logging for an application -void logStop(void) +void logServerStop(void) { if (logThread) { @@ -1085,326 +677,16 @@ void logStop(void) } } -/// \brief Register the current thread with the given name. This is triggered -/// by the RunProlog() call in each thread. -/// \param name the name of the thread being registered. This is used for -/// indicating the thread each log message is coming from. -void loggingRegisterThread(const QString &name) +void FileLogger::messageReceived(const QList&) { - if (logThreadFinished) - return; - - QMutexLocker qLock(&logQueueMutex); - - LoggingItem *item = createItem(__FILE__, __FUNCTION__, __LINE__, - (LogLevel_t)LOG_DEBUG, kRegistering); - if (item) - { - item->threadName = strdup((char *)name.toLocal8Bit().constData()); - logQueue.enqueue(item); - } -} - -/// \brief Deregister the current thread's name. This is triggered by the -/// RunEpilog() call in each thread. -void loggingDeregisterThread(void) -{ - if (logThreadFinished) - return; - - QMutexLocker qLock(&logQueueMutex); - - LoggingItem *item = createItem(__FILE__, __FUNCTION__, __LINE__, - (LogLevel_t)LOG_DEBUG, kDeregistering); - if (item) - logQueue.enqueue(item); -} - -/// \brief Map a syslog facility name back to the enumerated value -/// \param facility QString containing the facility name -/// \return Syslog facility as enumerated type. Negative if not found. -int syslogGetFacility(QString facility) -{ -#ifdef _WIN32 - LOG(VB_GENERAL, LOG_NOTICE, - "Windows does not support syslog, disabling" ); - return( -2 ); -#else - CODE *name; - int i; - QByteArray ba = facility.toLocal8Bit(); - char *string = (char *)ba.constData(); - - for (i = 0, name = &facilitynames[0]; - name->c_name && strcmp(name->c_name, string); i++, name++); - - return( name->c_val ); -#endif -} - -/// \brief Map a log level name back to the enumerated value -/// \param level QString containing the log level name -/// \return Log level as enumerated type. LOG_UNKNOWN if not found. -LogLevel_t logLevelGet(QString level) -{ - QMutexLocker locker(&loglevelMapMutex); - if (!verboseInitialized) - { - locker.unlock(); - verboseInit(); - locker.relock(); - } - - for (LoglevelMap::iterator it = loglevelMap.begin(); - it != loglevelMap.end(); ++it) - { - LoglevelDef *item = (*it); - if ( item->name == level.toLower() ) - return (LogLevel_t)item->value; - } - - return LOG_UNKNOWN; -} - -/// \brief Map a log level enumerated value back to the name -/// \param level Enumerated value of the log level -/// \return Log level name. "unknown" if not found. -QString logLevelGetName(LogLevel_t level) -{ - QMutexLocker locker(&loglevelMapMutex); - if (!verboseInitialized) - { - locker.unlock(); - verboseInit(); - locker.relock(); - } - LoglevelMap::iterator it = loglevelMap.find((int)level); - - if ( it == loglevelMap.end() ) - return QString("unknown"); - - return (*it)->name; } -/// \brief Add a verbose level to the verboseMap. Done at initialization. -/// \param mask verbose mask (VB_*) -/// \param name name of the verbosity level -/// \param additive true if this is to be ORed with other masks. false if -/// is will clear the other bits. -/// \param helptext Descriptive text for --verbose help output -void verboseAdd(uint64_t mask, QString name, bool additive, QString helptext) +void SyslogLogger::messageReceived(const QList&) { - VerboseDef *item = new VerboseDef; - - item->mask = mask; - name.detach(); - // VB_GENERAL -> general - name.remove(0, 3); - name = name.toLower(); - item->name = name; - item->additive = additive; - helptext.detach(); - item->helpText = helptext; - - verboseMap.insert(name, item); -} - -/// \brief Add a log level to the logLevelMap. Done at initialization. -/// \param value log level enumerated value (LOG_*) - matches syslog -/// levels -/// \param name name of the log level -/// \param shortname one-letter short name for output into logs -void loglevelAdd(int value, QString name, char shortname) -{ - LoglevelDef *item = new LoglevelDef; - - item->value = value; - name.detach(); - // LOG_CRIT -> crit - name.remove(0, 4); - name = name.toLower(); - item->name = name; - item->shortname = shortname; - - loglevelMap.insert(value, item); -} - -/// \brief Initialize the logging levels and verbose levels. -void verboseInit(void) -{ - QMutexLocker locker(&verboseMapMutex); - QMutexLocker locker2(&loglevelMapMutex); - verboseMap.clear(); - loglevelMap.clear(); - - // This looks funky, so I'll put some explanation here. The verbosedefs.h - // file gets included as part of the mythlogging.h include, and at that - // time, the normal (without _IMPLEMENT_VERBOSE defined) code case will - // define the VerboseMask enum. At this point, we force it to allow us - // to include the file again, but with _IMPLEMENT_VERBOSE set so that the - // single definition of the VB_* values can be shared to define also the - // contents of verboseMap, via repeated calls to verboseAdd() - -#undef VERBOSEDEFS_H_ -#define _IMPLEMENT_VERBOSE -#include "verbosedefs.h" - - verboseInitialized = true; -} - - -/// \brief Outputs the Verbose levels and their descriptions -/// (for --verbose help) -void verboseHelp(void) -{ - QString m_verbose = verboseString.trimmed(); - m_verbose.replace(QRegExp(" "), ","); - m_verbose.remove(QRegExp("^,")); - - cerr << "Verbose debug levels.\n" - "Accepts any combination (separated by comma) of:\n\n"; - - for (VerboseMap::Iterator vit = verboseMap.begin(); - vit != verboseMap.end(); ++vit ) - { - VerboseDef *item = vit.value(); - QString name = QString(" %1").arg(item->name, -15, ' '); - if (item->helpText.isEmpty()) - continue; - cerr << name.toLocal8Bit().constData() << " - " << - item->helpText.toLocal8Bit().constData() << endl; - } - - cerr << endl << - "The default for this program appears to be: '-v " << - m_verbose.toLocal8Bit().constData() << "'\n\n" - "Most options are additive except for 'none' and 'all'.\n" - "These two are semi-exclusive and take precedence over any\n" - "other options. However, you may use something like\n" - "'-v none,jobqueue' to receive only JobQueue related messages\n" - "and override the default verbosity level.\n\n" - "Additive options may also be subtracted from 'all' by\n" - "prefixing them with 'no', so you may use '-v all,nodatabase'\n" - "to view all but database debug messages.\n\n" - "Some debug levels may not apply to this program.\n\n"; -} - -/// \brief Parse the --verbose commandline argument and set the verbose level -/// \param arg the commandline argument following "--verbose" -/// \return an exit code. GENERIC_EXIT_OK if all is well. -int verboseArgParse(QString arg) -{ - QString option; - - if (!verboseInitialized) - verboseInit(); - - QMutexLocker locker(&verboseMapMutex); - - verboseMask = verboseDefaultInt; - verboseString = QString(verboseDefaultStr); - - if (arg.startsWith('-')) - { - cerr << "Invalid or missing argument to -v/--verbose option\n"; - return GENERIC_EXIT_INVALID_CMDLINE; - } - - QStringList verboseOpts = arg.split(QRegExp("\\W+")); - for (QStringList::Iterator it = verboseOpts.begin(); - it != verboseOpts.end(); ++it ) - { - option = (*it).toLower(); - bool reverseOption = false; - - if (option != "none" && option.left(2) == "no") - { - reverseOption = true; - option = option.right(option.length() - 2); - } - - if (option == "help") - { - verboseHelp(); - return GENERIC_EXIT_INVALID_CMDLINE; - } - else if (option == "important") - { - cerr << "The \"important\" log mask is no longer valid.\n"; - } - else if (option == "extra") - { - cerr << "The \"extra\" log mask is no longer valid. Please try " - "--loglevel debug instead.\n"; - } - else if (option == "default") - { - if (haveUserDefaultValues) - { - verboseMask = userDefaultValueInt; - verboseString = userDefaultValueStr; - } - else - { - verboseMask = verboseDefaultInt; - verboseString = QString(verboseDefaultStr); - } - } - else - { - VerboseDef *item = verboseMap.value(option); - - if (item) - { - if (reverseOption) - { - verboseMask &= ~(item->mask); - verboseString = verboseString.remove(' ' + item->name); - verboseString += " no" + item->name; - } - else - { - if (item->additive) - { - if (!(verboseMask & item->mask)) - { - verboseMask |= item->mask; - verboseString += ' ' + item->name; - } - } - else - { - verboseMask = item->mask; - verboseString = item->name; - } - } - } - else - { - cerr << "Unknown argument for -v/--verbose: " << - option.toLocal8Bit().constData() << endl;; - return GENERIC_EXIT_INVALID_CMDLINE; - } - } - } - - if (!haveUserDefaultValues) - { - haveUserDefaultValues = true; - userDefaultValueInt = verboseMask; - userDefaultValueStr = verboseString; - } - - return GENERIC_EXIT_OK; } -/// \brief Verbose helper function for ENO macro. -/// \param errnum system errno value -/// \return QString containing the string version of the errno value, plus the -/// errno value itself. -QString logStrerror(int errnum) +void DatabaseLogger::messageReceived(const QList&) { - return QString("%1 (%2)").arg(strerror(errnum)).arg(errnum); } diff --git a/mythtv/libs/libmythbase/loggingserver.h b/mythtv/libs/libmythbase/loggingserver.h index a8827a5d08d..896d4f41e7a 100644 --- a/mythtv/libs/libmythbase/loggingserver.h +++ b/mythtv/libs/libmythbase/loggingserver.h @@ -21,13 +21,16 @@ class QString; class MSqlQuery; class LoggingItem; +MBASE_PUBLIC void logServerStart(void); +MBASE_PUBLIC void logServerStop(void); + /// \brief Base class for the various logging mechanisms class LoggerBase : public QObject { Q_OBJECT public: /// \brief LoggerBase Constructor - LoggerBase(char *string, int number); + LoggerBase(char *string); /// \brief LoggerBase Deconstructor virtual ~LoggerBase(); /// \brief Process a log message for the logger instance @@ -40,8 +43,7 @@ class LoggerBase : public QObject { /// \brief Deal with an incoming log message virtual void messageReceived(const QList&) = 0; protected: - LoggerHandle_t m_handle; ///< semi-opaque handle for identifying instance - bool m_string; ///< true if m_handle.string valid, false otherwise + char *m_handle; ///< semi-opaque handle for identifying instance }; /// \brief File-based logger - used for logfiles and console @@ -61,7 +63,7 @@ class FileLogger : public LoggerBase { /// \brief Syslog-based logger (not available in Windows) class SyslogLogger : public LoggerBase { public: - SyslogLogger(int facility, char *application); + SyslogLogger(); ~SyslogLogger(); bool logmsg(LoggingItem *item); /// \brief Unused for this logger. @@ -124,6 +126,9 @@ class LogServerThread : public QObject, public MThread void messageReceived(const QList&); }; +class QWaitCondition; +#define MAX_QUEUE_LEN 1000 + /// \brief Thread that manages the queueing of logging inserts for the database. /// The database logging gets throttled if it gets overwhelmed, and also /// during startup. Having a second queue allows the rest of the @@ -141,7 +146,7 @@ class DBLoggerThread : public MThread bool enqueue(LoggingItem *item) { QMutexLocker qLock(&m_queueMutex); - if (!aborted) + if (!m_aborted) m_queue->enqueue(item); return true; } @@ -160,45 +165,7 @@ class DBLoggerThread : public MThread QWaitCondition *m_wait; ///< Wait condition used for waiting /// for the queue to not be full. /// Protected by m_queueMutex - volatile bool aborted; ///< Used during shutdown to indicate - /// that the thread should stop ASAP. - /// Protected by m_queueMutex -}; - -/// \brief Thread that manages the queueing of logging inserts to mythlogserver, -/// and dealing with any messages sent back. -class ZeroMQLoggerThread : public MThread -{ - public: - ZeroMQLoggerThread(ZeroMQLogger *logger); - ~ZeroMQLoggerThread(); - void run(void); - void stop(void); - /// \brief Enqueues a LoggingItem onto the queue for the thread to - /// consume. - bool enqueue(LoggingItem *item) - { - QMutexLocker qLock(&m_queueMutex); - if (!aborted) - m_queue->enqueue(item); - return true; - } - - /// \brief Indicates when the queue is full - /// \return true when the queue is full - bool queueFull(void) - { - QMutexLocker qLock(&m_queueMutex); - return (m_queue->size() >= MAX_QUEUE_LEN); - } - private: - ZeroMQLogger *m_logger; ///< The associated logger instance - QMutex m_queueMutex; ///< Mutex for protecting the queue - QQueue *m_queue; ///< Queue of LoggingItems to insert - QWaitCondition *m_wait; ///< Wait condition used for waiting - /// for the queue to not be full. - /// Protected by m_queueMutex - volatile bool aborted; ///< Used during shutdown to indicate + volatile bool m_aborted; ///< Used during shutdown to indicate /// that the thread should stop ASAP. /// Protected by m_queueMutex }; diff --git a/mythtv/libs/libmythbase/mythcorecontext.h b/mythtv/libs/libmythbase/mythcorecontext.h index 0917313a455..c2a860d676f 100644 --- a/mythtv/libs/libmythbase/mythcorecontext.h +++ b/mythtv/libs/libmythbase/mythcorecontext.h @@ -28,6 +28,7 @@ #define MYTH_APPNAME_MYTHMEDIASERVER "mythmediaserver" #define MYTH_APPNAME_MYTHMETADATALOOKUP "mythmetadatalookup" #define MYTH_APPNAME_MYTHUTIL "mythutil" +#define MYTH_APPNAME_MYTHLOGSERVER "mythlogserver" class MDBManager; class MythCoreContextPrivate; diff --git a/mythtv/libs/libmythbase/verbosedefs.h b/mythtv/libs/libmythbase/verbosedefs.h index c267c8ada31..9d8ce8871c2 100644 --- a/mythtv/libs/libmythbase/verbosedefs.h +++ b/mythtv/libs/libmythbase/verbosedefs.h @@ -1,6 +1,11 @@ #ifndef VERBOSEDEFS_H_ #define VERBOSEDEFS_H_ +#ifdef __cplusplus +#include +#include +#include +#endif #include /// This file gets included in two different ways: @@ -180,4 +185,29 @@ LOGLEVEL_MAP(LOG_DEBUG, 7, 'D') LOGLEVEL_MAP(LOG_UNKNOWN, 8, '-') LOGLEVEL_POSTAMBLE +#ifndef _IMPLEMENT_VERBOSE +#ifdef __cplusplus +typedef struct { + uint64_t mask; + QString name; + bool additive; + QString helpText; +} VerboseDef; +typedef QMap VerboseMap; + +typedef struct { + int value; + QString name; + char shortname; +} LoglevelDef; +typedef QMap LoglevelMap; + +extern VerboseMap verboseMap; +extern QMutex verboseMapMutex; + +extern LoglevelMap loglevelMap; +extern QMutex loglevelMapMutex; +#endif +#endif + #endif diff --git a/mythtv/programs/mythbackend/main.cpp b/mythtv/programs/mythbackend/main.cpp index 68ecbcde5ce..2e1b214a277 100644 --- a/mythtv/programs/mythbackend/main.cpp +++ b/mythtv/programs/mythbackend/main.cpp @@ -49,7 +49,8 @@ static void qt_exit(int) { signal(SIGINT, SIG_DFL); - QCoreApplication::exit(0); + signal(SIGTERM, SIG_DFL); + QCoreApplication::exit(GENERIC_EXIT_OK); } int main(int argc, char **argv) diff --git a/mythtv/programs/mythlogserver/.gitignore b/mythtv/programs/mythlogserver/.gitignore new file mode 100644 index 00000000000..acdc39ab36b --- /dev/null +++ b/mythtv/programs/mythlogserver/.gitignore @@ -0,0 +1 @@ +mythlogserver diff --git a/mythtv/programs/mythlogserver/commandlineparser.cpp b/mythtv/programs/mythlogserver/commandlineparser.cpp new file mode 100644 index 00000000000..7adb56f4168 --- /dev/null +++ b/mythtv/programs/mythlogserver/commandlineparser.cpp @@ -0,0 +1,23 @@ +#include "commandlineparser.h" +#include "mythcorecontext.h" + +MythLogServerCommandLineParser::MythLogServerCommandLineParser() : + MythCommandLineParser(MYTH_APPNAME_MYTHLOGSERVER) +{ + LoadArguments(); +} + +void MythLogServerCommandLineParser::LoadArguments(void) +{ + addHelp(); + addVersion(); + addDaemon(); + addLogging(); +} + +QString MythLogServerCommandLineParser::GetHelpHeader(void) const +{ + return + "This is the common logserver that accepts logging messages from\n" + "all the clients via ZeroMQ."; +} diff --git a/mythtv/programs/mythlogserver/commandlineparser.h b/mythtv/programs/mythlogserver/commandlineparser.h new file mode 100644 index 00000000000..c9aed4d112b --- /dev/null +++ b/mythtv/programs/mythlogserver/commandlineparser.h @@ -0,0 +1,19 @@ +// -*- Mode: c++ -*- + +#ifndef _MYTH_LOGSERVER_COMMAND_LINE_PARSER_H_ +#define _MYTH_LOGSERVER_COMMAND_LINE_PARSER_H_ + +#include "mythcommandlineparser.h" + +class MythLogServerCommandLineParser : public MythCommandLineParser +{ + public: + MythLogServerCommandLineParser(); + void LoadArguments(void); + protected: + QString GetHelpHeader(void) const; +}; + +#endif // _MYTH_LOGSERVER_COMMAND_LINE_PARSER_H_ + +/* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/programs/mythlogserver/main.cpp b/mythtv/programs/mythlogserver/main.cpp new file mode 100644 index 00000000000..4305de430d5 --- /dev/null +++ b/mythtv/programs/mythlogserver/main.cpp @@ -0,0 +1,116 @@ +// -*- Mode: c++ -*- + +// C++ headers +#include +#include +using namespace std; + +// Qt headers +#include +#include +#include +#include +#include + +// MythTV headers +#include "mythccextractorplayer.h" +#include "commandlineparser.h" +#include "mythcontext.h" +#include "mythversion.h" +#include "programinfo.h" +#include "ringbuffer.h" +#include "exitcodes.h" +#include "loggingserver.h" + +namespace { + void cleanup() + { + delete gContext; + gContext = NULL; + } + + class CleanupGuard + { + public: + typedef void (*CleanupFunc)(); + + public: + CleanupGuard(CleanupFunc cleanFunction) : + m_cleanFunction(cleanFunction) {} + + ~CleanupGuard() + { + m_cleanFunction(); + } + + private: + CleanupFunc m_cleanFunction; + }; +} + +static void qt_exit(int) +{ + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + QCoreApplication::exit(GENERIC_EXIT_OK); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHLOGSERVER); + + MythLogServerCommandLineParser cmdline; + if (!cmdline.Parse(argc, argv)) + { + cmdline.PrintHelp(); + return GENERIC_EXIT_INVALID_CMDLINE; + } + + if (cmdline.toBool("showhelp")) + { + cmdline.PrintHelp(); + return GENERIC_EXIT_OK; + } + + if (cmdline.toBool("showversion")) + { + cmdline.PrintVersion(); + return GENERIC_EXIT_OK; + } + + //QString pidfile = cmdline.toString("pidfile"); + int retval = cmdline.Daemonize(); + if (retval != GENERIC_EXIT_OK) + return retval; + + bool daemonize = cmdline.toBool("daemon"); + QString mask("general"); + if ((retval = cmdline.ConfigureLogging(mask, daemonize)) != GENERIC_EXIT_OK) + return retval; + + if (daemonize) + // Don't listen to console input if daemonized + close(0); + + CleanupGuard callCleanup(cleanup); + signal(SIGINT, qt_exit); + signal(SIGTERM, qt_exit); + + gContext = new MythContext(MYTH_BINARY_VERSION); + if (!gContext->Init(false)) + { + cerr << "Failed to init MythContext, exiting." << endl; + return GENERIC_EXIT_NO_MYTHCONTEXT; + } + + logServerStart(); + + while (true) + usleep(100000); + + return GENERIC_EXIT_OK; +} + + +/* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/programs/mythlogserver/mythlogserver.pro b/mythtv/programs/mythlogserver/mythlogserver.pro new file mode 100644 index 00000000000..55dce0f9200 --- /dev/null +++ b/mythtv/programs/mythlogserver/mythlogserver.pro @@ -0,0 +1,16 @@ +include (../../settings.pro) +include ( ../programs-libs.pro ) + +QT += sql network + +TEMPLATE = app +CONFIG += thread +target.path = $${PREFIX}/bin +INSTALLS = target + +QMAKE_CLEAN += $(TARGET) + +# Input +HEADERS += commandlineparser.h +SOURCES += commandlineparser.cpp main.cpp + diff --git a/mythtv/programs/mythtranscode/replex/replex.pro b/mythtv/programs/mythtranscode/replex/replex.pro index 31bee905489..0bc79638f23 100644 --- a/mythtv/programs/mythtranscode/replex/replex.pro +++ b/mythtv/programs/mythtranscode/replex/replex.pro @@ -23,6 +23,9 @@ QMAKE_CFLAGS += -w LIBS += -L../../../external/FFmpeg/libavutil LIBS += -L../../../external/FFmpeg/libavcodec LIBS += -L../../../external/FFmpeg/libavformat +LIBS += -L../../../external/zeromq/src/.libs +LIBS += -L../../../external/nzmqt/src +LIBS += -L../../../external/qjson/lib LIBS += -L../../../libs/libmythbase LIBS += -lmythavformat LIBS += -lmythavcodec diff --git a/mythtv/programs/programs.pro b/mythtv/programs/programs.pro index 3bf98cf7678..358c0ae6da8 100644 --- a/mythtv/programs/programs.pro +++ b/mythtv/programs/programs.pro @@ -5,7 +5,7 @@ TEMPLATE = subdirs # Directories using_frontend { SUBDIRS += mythavtest mythfrontend mythcommflag - SUBDIRS += mythjobqueue mythlcdserver + SUBDIRS += mythjobqueue mythlcdserver mythlogserver SUBDIRS += mythwelcome mythshutdown mythutil SUBDIRS += mythpreviewgen mythmediaserver mythccextractor !mingw: SUBDIRS += mythtranscode/replex diff --git a/mythtv/settings.pro b/mythtv/settings.pro index e582691c117..5fe87ad3fce 100644 --- a/mythtv/settings.pro +++ b/mythtv/settings.pro @@ -84,6 +84,8 @@ contains(CONFIG_DARWIN, yes) { } INCLUDEPATH += $$unique(CONFIG_INCLUDEPATH) +INCLUDEPATH += $$PREFIX/include +INCLUDEPATH += $$PREFIX/include/qjson # remove warn_{on|off} from CONFIG since we set it in our CFLAGS CONFIG -= warn_on warn_off @@ -156,5 +158,6 @@ EXTRA_LIBS += $$CONFIG_FIREWIRE_LIBS EXTRA_LIBS += $$LOCAL_LIBDIR_OGL EXTRA_LIBS += $$LOCAL_LIBDIR_X11 EXTRA_LIBS += $$CONFIG_OPENGL_LIBS +EXTRA_LIBS += -lzmq -lmythnzmqt -lmythqjson macx:using_firewire:using_backend:EXTRA_LIBS += -F$${CONFIG_MAC_AVC} -framework AVCVideoServices