MythTV  0.26-pre
logging.cpp
Go to the documentation of this file.
00001 #include <QAtomicInt>
00002 #include <QMutex>
00003 #include <QMutexLocker>
00004 #include <QWaitCondition>
00005 #include <QList>
00006 #include <QQueue>
00007 #include <QHash>
00008 #include <QCoreApplication>
00009 #include <QFileInfo>
00010 #include <QStringList>
00011 #include <QMap>
00012 #include <QRegExp>
00013 #include <iostream>
00014 
00015 using namespace std;
00016 
00017 #include "mythlogging.h"
00018 #include "logging.h"
00019 #include "mythconfig.h"
00020 #include "mythdb.h"
00021 #include "mythcorecontext.h"
00022 #include "dbutil.h"
00023 #include "exitcodes.h"
00024 #include "compat.h"
00025 
00026 #include <stdlib.h>
00027 #define SYSLOG_NAMES
00028 #ifndef _WIN32
00029 #include <syslog.h>
00030 #endif
00031 #include <stdarg.h>
00032 #include <string.h>
00033 #include <sys/stat.h>
00034 #include <sys/types.h>
00035 #include <fcntl.h>
00036 #include <stdio.h>
00037 #include <unistd.h>
00038 #if HAVE_GETTIMEOFDAY
00039 #include <sys/time.h>
00040 #endif
00041 #include <signal.h>
00042 
00043 // Various ways to get to thread's tid
00044 #if defined(linux)
00045 #include <sys/syscall.h>
00046 #elif defined(__FreeBSD__)
00047 extern "C" {
00048 #include <sys/ucontext.h>
00049 #include <sys/thr.h>
00050 }
00051 #elif CONFIG_DARWIN
00052 #include <mach/mach.h>
00053 #endif
00054 
00055 QMutex                  loggerListMutex;
00056 QList<LoggerBase *>     loggerList;
00057 
00058 QMutex                  logQueueMutex;
00059 QQueue<LoggingItem *> logQueue;
00060 QRegExp                 logRegExp = QRegExp("[%]{1,2}");
00061 
00062 QMutex                  logThreadMutex;
00063 QHash<uint64_t, char *> logThreadHash;
00064 
00065 QMutex                   logThreadTidMutex;
00066 QHash<uint64_t, int64_t> logThreadTidHash;
00067 
00068 LoggerThread           *logThread = NULL;
00069 bool                    logThreadFinished = false;
00070 bool                    debugRegistration = false;
00071 
00072 typedef struct {
00073     bool    propagate;
00074     int     quiet;
00075     int     facility;
00076     bool    dblog;
00077     QString path;
00078 } LogPropagateOpts;
00079 
00080 LogPropagateOpts        logPropagateOpts;
00081 QString                 logPropagateArgs;
00082 
00083 #define TIMESTAMP_MAX 30
00084 #define MAX_STRING_LENGTH (LOGLINE_MAX+120)
00085 
00086 LogLevel_t logLevel = (LogLevel_t)LOG_INFO;
00087 
00088 typedef struct {
00089     uint64_t    mask;
00090     QString     name;
00091     bool        additive;
00092     QString     helpText;
00093 } VerboseDef;
00094 typedef QMap<QString, VerboseDef *> VerboseMap;
00095 
00096 typedef struct {
00097     int         value;
00098     QString     name;
00099     char        shortname;
00100 } LoglevelDef;
00101 typedef QMap<int, LoglevelDef *> LoglevelMap;
00102 
00103 bool verboseInitialized = false;
00104 VerboseMap verboseMap;
00105 QMutex verboseMapMutex;
00106 
00107 LoglevelMap loglevelMap;
00108 QMutex loglevelMapMutex;
00109 
00110 const uint64_t verboseDefaultInt = VB_GENERAL;
00111 const char    *verboseDefaultStr = " general";
00112 
00113 uint64_t verboseMask = verboseDefaultInt;
00114 QString verboseString = QString(verboseDefaultStr);
00115 
00116 uint64_t     userDefaultValueInt = verboseDefaultInt;
00117 QString      userDefaultValueStr = QString(verboseDefaultStr);
00118 bool         haveUserDefaultValues = false;
00119 
00120 void verboseAdd(uint64_t mask, QString name, bool additive, QString helptext);
00121 void loglevelAdd(int value, QString name, char shortname);
00122 void verboseInit(void);
00123 void verboseHelp(void);
00124 
00125 void LogTimeStamp( struct tm *tm, uint32_t *usec );
00126 char *getThreadName( LoggingItem *item );
00127 int64_t getThreadTid( LoggingItem *item );
00128 void setThreadTid( LoggingItem *item );
00129 static LoggingItem *createItem(
00130     const char *, const char *, int, LogLevel_t, int);
00131 static void deleteItem(LoggingItem *item);
00132 #ifndef _WIN32
00133 void logSighup( int signum, siginfo_t *info, void *secret );
00134 #endif
00135 
00136 typedef enum {
00137     kMessage       = 0x01,
00138     kRegistering   = 0x02,
00139     kDeregistering = 0x04,
00140     kFlush         = 0x08,
00141     kStandardIO    = 0x10,
00142 } LoggingType;
00143 
00144 class LoggingItem
00145 {
00146   public:
00147     LoggingItem(const char *_file, const char *_function,
00148                 int _line, LogLevel_t _level, int _type) :
00149         threadId((uint64_t)(QThread::currentThreadId())),
00150         line(_line), type(_type), level(_level), file(_file),
00151         function(_function), threadName(NULL)
00152     {
00153         LogTimeStamp(&tm, &usec);
00154         message[0]='\0';
00155         message[LOGLINE_MAX]='\0';
00156         setThreadTid(this);
00157         refcount.ref();
00158     }
00159 
00160     QAtomicInt          refcount;
00161     uint64_t            threadId;
00162     uint32_t            usec;
00163     int                 line;
00164     int                 type;
00165     LogLevel_t          level;
00166     struct tm           tm;
00167     const char         *file;
00168     const char         *function;
00169     char               *threadName;
00170     char                message[LOGLINE_MAX+1];
00171 };
00172 
00177 LoggerBase::LoggerBase(char *string, int number)
00178 {
00179     QMutexLocker locker(&loggerListMutex);
00180     if (string)
00181     {
00182         m_handle.string = strdup(string);
00183         m_string = true;
00184     }
00185     else
00186     {
00187         m_handle.number = number;
00188         m_string = false;
00189     }
00190     loggerList.append(this);
00191 }
00192 
00195 LoggerBase::~LoggerBase()
00196 {
00197     QMutexLocker locker(&loggerListMutex);
00198 
00199     QList<LoggerBase *>::iterator it;
00200 
00201     for (it = loggerList.begin(); it != loggerList.end(); ++it)
00202     {
00203         if( *it == this )
00204         {
00205             loggerList.erase(it);
00206             break;
00207         }
00208     }
00209 
00210     if (m_string)
00211         free(m_handle.string);
00212 }
00213 
00214 
00221 FileLogger::FileLogger(char *filename, bool progress, int quiet) :
00222         LoggerBase(filename, 0), m_opened(false), m_fd(-1),
00223         m_progress(progress), m_quiet(quiet)
00224 {
00225     if( !strcmp(filename, "-") )
00226     {
00227         m_opened = true;
00228         m_fd = 1;
00229         LOG(VB_GENERAL, LOG_INFO, "Added logging to the console");
00230     }
00231     else
00232     {
00233         m_progress = false;
00234         m_quiet = 0;
00235         m_fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0664);
00236         m_opened = (m_fd != -1);
00237         LOG(VB_GENERAL, LOG_INFO, QString("Added logging to %1")
00238                  .arg(filename));
00239     }
00240 }
00241 
00243 FileLogger::~FileLogger()
00244 {
00245     if( m_opened )
00246     {
00247         if( m_fd != 1 )
00248         {
00249             LOG(VB_GENERAL, LOG_INFO, QString("Removed logging to %1")
00250                      .arg(m_handle.string));
00251             close( m_fd );
00252         }
00253         else
00254             LOG(VB_GENERAL, LOG_INFO, "Removed logging to the console");
00255     }
00256 }
00257 
00260 void FileLogger::reopen(void)
00261 {
00262     char *filename = m_handle.string;
00263 
00264     // Skip console
00265     if( !strcmp(filename, "-") )
00266         return;
00267 
00268     close(m_fd);
00269 
00270     m_fd = open(filename, O_WRONLY|O_CREAT|O_APPEND, 0664);
00271     m_opened = (m_fd != -1);
00272     LOG(VB_GENERAL, LOG_INFO, QString("Rolled logging on %1")
00273              .arg(filename));
00274 }
00275 
00278 bool FileLogger::logmsg(LoggingItem *item)
00279 {
00280     char                line[MAX_STRING_LENGTH];
00281     char                usPart[9];
00282     char                timestamp[TIMESTAMP_MAX];
00283     char               *threadName = NULL;
00284     pid_t               pid = getpid();
00285 
00286     if (!m_opened || m_quiet || (m_progress && item->level > LOG_ERR))
00287         return false;
00288 
00289     item->refcount.ref();
00290 
00291     strftime( timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S",
00292               (const struct tm *)&item->tm );
00293     snprintf( usPart, 9, ".%06d", (int)(item->usec) );
00294     strcat( timestamp, usPart );
00295     char shortname;
00296 
00297     {
00298         QMutexLocker locker(&loglevelMapMutex);
00299         LoglevelMap::iterator it = loglevelMap.find(item->level);
00300         if (it == loglevelMap.end())
00301             shortname = '-';
00302         else
00303             shortname = (*it)->shortname;
00304     }
00305 
00306     if (item->type & kStandardIO)
00307     {
00308         snprintf( line, MAX_STRING_LENGTH, "%s", item->message );
00309     }
00310     else if (m_fd == 1)
00311     {
00312         // Stdout
00313         snprintf( line, MAX_STRING_LENGTH, "%s %c  %s\n", timestamp,
00314                   shortname, item->message );
00315     }
00316     else
00317     {
00318         threadName = getThreadName(item);
00319         pid_t tid = getThreadTid(item);
00320 
00321         if( tid )
00322             snprintf( line, MAX_STRING_LENGTH, 
00323                       "%s %c [%d/%d] %s %s:%d (%s) - %s\n",
00324                       timestamp, shortname, pid, tid, threadName, item->file,
00325                       item->line, item->function, item->message );
00326         else
00327             snprintf( line, MAX_STRING_LENGTH,
00328                       "%s %c [%d] %s %s:%d (%s) - %s\n",
00329                       timestamp, shortname, pid, threadName, item->file,
00330                       item->line, item->function, item->message );
00331     }
00332 
00333     int fd = (item->type & kStandardIO) ? 1 : m_fd;
00334     int result = write( fd, line, strlen(line) );
00335 
00336     deleteItem(item);
00337 
00338     if( result == -1 )
00339     {
00340         LOG(VB_GENERAL, LOG_ERR,
00341                  QString("Closed Log output on fd %1 due to errors").arg(m_fd));
00342         m_opened = false;
00343         if( m_fd != 1 )
00344             close( m_fd );
00345         return false;
00346     }
00347     return true;
00348 }
00349 
00350 
00351 #ifndef _WIN32
00352 
00353 
00354 SyslogLogger::SyslogLogger(int facility) : LoggerBase(NULL, facility),
00355                                            m_opened(false)
00356 {
00357     CODE *name;
00358 
00359     m_application = strdup((char *)QCoreApplication::applicationName()
00360                            .toLocal8Bit().constData());
00361 
00362     openlog( m_application, LOG_NDELAY | LOG_PID, facility );
00363     m_opened = true;
00364 
00365     for (name = &facilitynames[0];
00366          name->c_name && name->c_val != facility; name++);
00367 
00368     LOG(VB_GENERAL, LOG_INFO, QString("Added syslogging to facility %1")
00369              .arg(name->c_name));
00370 }
00371 
00373 SyslogLogger::~SyslogLogger()
00374 {
00375     LOG(VB_GENERAL, LOG_INFO, "Removing syslogging");
00376     free(m_application);
00377     closelog();
00378 }
00379 
00380 
00383 bool SyslogLogger::logmsg(LoggingItem *item)
00384 {
00385     if (!m_opened)
00386         return false;
00387 
00388     char shortname;
00389 
00390     {
00391         QMutexLocker locker(&loglevelMapMutex);
00392         LoglevelMap::iterator it = loglevelMap.find(item->level);
00393         if (it == loglevelMap.end())
00394             shortname = '-';
00395         else
00396             shortname = (*it)->shortname;
00397     }
00398 
00399     char *threadName = getThreadName(item);
00400 
00401     syslog( item->level, "%c %s %s:%d (%s) %s", shortname, threadName,
00402             item->file, item->line, item->function, item->message );
00403 
00404     return true;
00405 }
00406 #endif
00407 
00408 const int DatabaseLogger::kMinDisabledTime = 1000;
00409 
00412 DatabaseLogger::DatabaseLogger(char *table) : LoggerBase(table, 0),
00413                                               m_opened(false),
00414                                               m_loggingTableExists(false)
00415 {
00416     m_query = QString(
00417         "INSERT INTO %1 "
00418         "    (host, application, pid, tid, thread, filename, "
00419         "     line, function, msgtime, level, message) "
00420         "VALUES (:HOST, :APP, :PID, :TID, :THREAD, :FILENAME, "
00421         "        :LINE, :FUNCTION, :MSGTIME, :LEVEL, :MESSAGE)")
00422         .arg(m_handle.string);
00423 
00424     LOG(VB_GENERAL, LOG_INFO, QString("Added database logging to table %1")
00425         .arg(m_handle.string));
00426 
00427     m_thread = new DBLoggerThread(this);
00428     m_thread->start();
00429 
00430     m_opened = true;
00431     m_disabled = false;
00432 }
00433 
00435 DatabaseLogger::~DatabaseLogger()
00436 {
00437     LOG(VB_GENERAL, LOG_INFO, "Removing database logging");
00438 
00439     stopDatabaseAccess();
00440 }
00441 
00443 void DatabaseLogger::stopDatabaseAccess(void)
00444 {
00445     if( m_thread )
00446     {
00447         m_thread->stop();
00448         m_thread->wait();
00449         delete m_thread;
00450         m_thread = NULL;
00451     }
00452 }
00453 
00456 bool DatabaseLogger::logmsg(LoggingItem *item)
00457 {
00458     if (!m_thread)
00459         return false;
00460 
00461     if (!m_thread->isRunning())
00462     {
00463         m_disabled = true;
00464         m_disabledTime.start();
00465     }
00466 
00467     if (!m_disabled && m_thread->queueFull())
00468     {
00469         m_disabled = true;
00470         m_disabledTime.start();
00471         LOG(VB_GENERAL, LOG_CRIT,
00472             "Disabling DB Logging: too many messages queued");
00473         return false;
00474     }
00475 
00476     if (m_disabled && m_disabledTime.elapsed() > kMinDisabledTime)
00477     {
00478         if (isDatabaseReady() && !m_thread->queueFull())
00479         {
00480             m_disabled = false;
00481             LOG(VB_GENERAL, LOG_CRIT, "Reenabling DB Logging");
00482         }
00483     }
00484 
00485     if (m_disabled)
00486         return false;
00487 
00488     item->refcount.ref();
00489     m_thread->enqueue(item);
00490     return true;
00491 }
00492 
00496 bool DatabaseLogger::logqmsg(MSqlQuery &query, LoggingItem *item)
00497 {
00498     char        timestamp[TIMESTAMP_MAX];
00499     char       *threadName = getThreadName(item);
00500     pid_t       tid        = getThreadTid(item);
00501 
00502     strftime( timestamp, TIMESTAMP_MAX-8, "%Y-%m-%d %H:%M:%S",
00503               (const struct tm *)&item->tm );
00504 
00505     query.bindValue(":TID",         tid);
00506     query.bindValue(":THREAD",      threadName);
00507     query.bindValue(":FILENAME",    item->file);
00508     query.bindValue(":LINE",        item->line);
00509     query.bindValue(":FUNCTION",    item->function);
00510     query.bindValue(":MSGTIME",     timestamp);
00511     query.bindValue(":LEVEL",       item->level);
00512     query.bindValue(":MESSAGE",     item->message);
00513 
00514     if (!query.exec())
00515     {
00516         // Suppress Driver not loaded errors that occur at startup.
00517         // and suppress additional errors for one second after the
00518         // previous error (to avoid spamming the log).
00519         QSqlError err = query.lastError();
00520         if ((err.type() != 1 || err.number() != -1) &&
00521             (!m_errorLoggingTime.isValid() ||
00522              (m_errorLoggingTime.elapsed() > 1000)))
00523         {
00524             MythDB::DBError("DBLogging", query);
00525             m_errorLoggingTime.start();
00526         }
00527         return false;
00528     }
00529 
00530     deleteItem(item);
00531 
00532     return true;
00533 }
00534 
00537 void DatabaseLogger::prepare(MSqlQuery &query)
00538 {
00539     query.prepare(m_query);
00540     query.bindValue(":HOST", gCoreContext->GetHostName());
00541     query.bindValue(":APP", QCoreApplication::applicationName());
00542     query.bindValue(":PID", getpid());
00543 }
00544 
00547 bool DatabaseLogger::isDatabaseReady(void)
00548 {
00549     bool ready = false;
00550     MythDB *db = GetMythDB();
00551 
00552     if ((db) && db->HaveValidDatabase())
00553     {
00554         if ( !m_loggingTableExists )
00555             m_loggingTableExists = tableExists(m_handle.string);
00556 
00557         if ( m_loggingTableExists )
00558             ready = true;
00559     }
00560 
00561     return ready;
00562 }
00563 
00567 bool DatabaseLogger::tableExists(const QString &table)
00568 {
00569     bool result = false;
00570     MSqlQuery query(MSqlQuery::InitCon());
00571     if (query.isConnected())
00572     {
00573         QString sql = "SELECT INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME "
00574                       "  FROM INFORMATION_SCHEMA.COLUMNS "
00575                       " WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA = "
00576                       "       DATABASE() "
00577                       "   AND INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = "
00578                       "       :TABLENAME "
00579                       "   AND INFORMATION_SCHEMA.COLUMNS.COLUMN_NAME = "
00580                       "       :COLUMNNAME;";
00581         if (query.prepare(sql))
00582         {
00583             query.bindValue(":TABLENAME", table);
00584             query.bindValue(":COLUMNNAME", "function");
00585             if (query.exec() && query.next())
00586                 result = true;
00587         }
00588     }
00589     return result;
00590 }
00591 
00592 
00595 DBLoggerThread::DBLoggerThread(DatabaseLogger *logger) :
00596     MThread("DBLogger"),
00597     m_logger(logger),
00598     m_queue(new QQueue<LoggingItem *>),
00599     m_wait(new QWaitCondition()), aborted(false)
00600 {
00601 }
00602 
00605 DBLoggerThread::~DBLoggerThread()
00606 {
00607     stop();
00608     wait();
00609 
00610     QMutexLocker qLock(&m_queueMutex);
00611     while (!m_queue->empty())
00612         deleteItem(m_queue->dequeue());
00613     delete m_queue;
00614     delete m_wait;
00615     m_queue = NULL;
00616     m_wait = NULL;
00617 }
00618 
00620 void DBLoggerThread::run(void)
00621 {
00622     RunProlog();
00623 
00624     // Wait a bit before we start logging to the DB..  If we wait too long,
00625     // then short-running tasks (like mythpreviewgen) will not log to the db
00626     // at all, and that's undesirable.
00627     while (true)
00628     {
00629         if ((aborted || (gCoreContext && m_logger->isDatabaseReady())))
00630             break;
00631 
00632         QMutexLocker locker(&m_queueMutex);
00633         m_wait->wait(locker.mutex(), 100);
00634     }
00635 
00636     if (!aborted)
00637     {
00638         // We want the query to be out of scope before the RunEpilog() so
00639         // shutdown occurs correctly as otherwise the connection appears still
00640         // in use, and we get a qWarning on shutdown.
00641         MSqlQuery *query = new MSqlQuery(MSqlQuery::InitCon());
00642         m_logger->prepare(*query);
00643 
00644         QMutexLocker qLock(&m_queueMutex);
00645         while (!aborted || !m_queue->isEmpty())
00646         {
00647             if (m_queue->isEmpty())
00648             {
00649                 m_wait->wait(qLock.mutex(), 100);
00650                 continue;
00651             }
00652 
00653             LoggingItem *item = m_queue->dequeue();
00654             if (!item)
00655                 continue;
00656 
00657             qLock.unlock();
00658 
00659             if (item->message[0] != '\0')
00660             {
00661                 if (!m_logger->logqmsg(*query, item))
00662                 {
00663                     qLock.relock();
00664                     m_queue->prepend(item);
00665                     m_wait->wait(qLock.mutex(), 100);
00666                     delete query;
00667                     query = new MSqlQuery(MSqlQuery::InitCon());
00668                     m_logger->prepare(*query);
00669                     continue;
00670                 }
00671             }
00672             else
00673             {
00674                 deleteItem(item);
00675             }
00676 
00677             qLock.relock();
00678         }
00679 
00680         delete query;
00681 
00682         qLock.unlock();
00683     }
00684 
00685     RunEpilog();
00686 }
00687 
00689 void DBLoggerThread::stop(void)
00690 {
00691     QMutexLocker qLock(&m_queueMutex);
00692     aborted = true;
00693     m_wait->wakeAll();
00694 }
00695 
00699 char *getThreadName( LoggingItem *item )
00700 {
00701     static const char  *unknown = "thread_unknown";
00702     char *threadName;
00703 
00704     if( !item )
00705         return( (char *)unknown );
00706 
00707     if( !item->threadName )
00708     {
00709         QMutexLocker locker(&logThreadMutex);
00710         if( logThreadHash.contains(item->threadId) )
00711             threadName = logThreadHash[item->threadId];
00712         else
00713             threadName = (char *)unknown;
00714     }
00715     else
00716     {
00717         threadName = item->threadName;
00718     }
00719 
00720     return( threadName );
00721 }
00722 
00729 int64_t getThreadTid( LoggingItem *item )
00730 {
00731     pid_t tid = 0;
00732 
00733     if( !item )
00734         return( 0 );
00735 
00736     QMutexLocker locker(&logThreadTidMutex);
00737     if( logThreadTidHash.contains(item->threadId) )
00738         tid = logThreadTidHash[item->threadId];
00739 
00740     return( tid );
00741 }
00742 
00750 void setThreadTid( LoggingItem *item )
00751 {
00752     QMutexLocker locker(&logThreadTidMutex);
00753 
00754     if( ! logThreadTidHash.contains(item->threadId) )
00755     {
00756         int64_t tid = 0;
00757 
00758 #if defined(linux)
00759         tid = (int64_t)syscall(SYS_gettid);
00760 #elif defined(__FreeBSD__)
00761         long lwpid;
00762         int dummy = thr_self( &lwpid );
00763         (void)dummy;
00764         tid = (int64_t)lwpid;
00765 #elif CONFIG_DARWIN
00766         tid = (int64_t)mach_thread_self();
00767 #endif
00768         logThreadTidHash[item->threadId] = tid;
00769     }
00770 }
00771 
00775 LoggerThread::LoggerThread() :
00776     MThread("Logger"),
00777     m_waitNotEmpty(new QWaitCondition()),
00778     m_waitEmpty(new QWaitCondition()),
00779     aborted(false)
00780 {
00781     char *debug = getenv("VERBOSE_THREADS");
00782     if (debug != NULL)
00783     {
00784         LOG(VB_GENERAL, LOG_NOTICE,
00785             "Logging thread registration/deregistration enabled!");
00786         debugRegistration = true;
00787     }
00788 }
00789 
00791 LoggerThread::~LoggerThread()
00792 {
00793     stop();
00794     wait();
00795 
00796     QMutexLocker locker(&loggerListMutex);
00797 
00798     QList<LoggerBase *>::iterator it;
00799 
00800     for (it = loggerList.begin(); it != loggerList.end(); ++it)
00801     {
00802         (*it)->deleteLater();
00803     }
00804 
00805     delete m_waitNotEmpty;
00806     delete m_waitEmpty;
00807 }
00808 
00813 void LoggerThread::run(void)
00814 {
00815     RunProlog();
00816 
00817     logThreadFinished = false;
00818 
00819     QMutexLocker qLock(&logQueueMutex);
00820 
00821     while (!aborted || !logQueue.isEmpty())
00822     {
00823         if (logQueue.isEmpty())
00824         {
00825             m_waitEmpty->wakeAll();
00826             m_waitNotEmpty->wait(qLock.mutex(), 100);
00827             continue;
00828         }
00829 
00830         LoggingItem *item = logQueue.dequeue();
00831         qLock.unlock();
00832 
00833         handleItem(item);
00834         deleteItem(item);
00835 
00836         qLock.relock();
00837     }
00838 
00839     logThreadFinished = true;
00840 
00841     qLock.unlock();
00842 
00843     RunEpilog();
00844 }
00845 
00846 
00853 void LoggerThread::handleItem(LoggingItem *item)
00854 {
00855     if (item->type & kRegistering)
00856     {
00857         int64_t tid = getThreadTid(item);
00858 
00859         QMutexLocker locker(&logThreadMutex);
00860         logThreadHash[item->threadId] = strdup(item->threadName);
00861 
00862         if (debugRegistration)
00863         {
00864             snprintf(item->message, LOGLINE_MAX,
00865                      "Thread 0x%" PREFIX64 "X (%" PREFIX64 
00866                      "d) registered as \'%s\'",
00867                      (long long unsigned int)item->threadId,
00868                      (long long int)tid,
00869                      logThreadHash[item->threadId]);
00870         }
00871     }
00872     else if (item->type & kDeregistering)
00873     {
00874         int64_t tid = 0;
00875 
00876         {
00877             QMutexLocker locker(&logThreadTidMutex);
00878             if( logThreadTidHash.contains(item->threadId) )
00879             {
00880                 tid = logThreadTidHash[item->threadId];
00881                 logThreadTidHash.remove(item->threadId);
00882             }
00883         }
00884 
00885         QMutexLocker locker(&logThreadMutex);
00886         if (logThreadHash.contains(item->threadId))
00887         {
00888             if (debugRegistration)
00889             {
00890                 snprintf(item->message, LOGLINE_MAX,
00891                          "Thread 0x%" PREFIX64 "X (%" PREFIX64 
00892                          "d) deregistered as \'%s\'",
00893                          (long long unsigned int)item->threadId,
00894                          (long long int)tid,
00895                          logThreadHash[item->threadId]);
00896             }
00897             item->threadName = logThreadHash[item->threadId];
00898             logThreadHash.remove(item->threadId);
00899         }
00900     }
00901 
00902     if (item->message[0] != '\0')
00903     {
00904         QMutexLocker locker(&loggerListMutex);
00905 
00906         QList<LoggerBase *>::iterator it;
00907         for (it = loggerList.begin(); it != loggerList.end(); ++it)
00908             (*it)->logmsg(item);
00909     }
00910 }
00911 
00914 void LoggerThread::stop(void)
00915 {
00916     QMutexLocker qLock(&logQueueMutex);
00917     flush(1000);
00918     aborted = true;
00919     m_waitNotEmpty->wakeAll();
00920 }
00921 
00925 bool LoggerThread::flush(int timeoutMS)
00926 {
00927     QTime t;
00928     t.start();
00929     while (!aborted && logQueue.isEmpty() && t.elapsed() < timeoutMS)
00930     {
00931         m_waitNotEmpty->wakeAll();
00932         int left = timeoutMS - t.elapsed();
00933         if (left > 0)
00934             m_waitEmpty->wait(&logQueueMutex, left);
00935     }
00936     return logQueue.isEmpty();
00937 }
00938 
00939 static QList<LoggingItem*> item_recycler;
00940 static QAtomicInt item_count;
00941 static QAtomicInt malloc_count;
00942 
00943 #define DEBUG_MEMORY 0
00944 #if DEBUG_MEMORY
00945 static int max_count = 0;
00946 static QTime memory_time;
00947 #endif
00948 
00956 static LoggingItem *createItem(const char *_file, const char *_function,
00957                                int _line, LogLevel_t _level, int _type)
00958 {
00959     LoggingItem *item = new LoggingItem(_file, _function, _line, _level, _type);
00960 
00961     malloc_count.ref();
00962 
00963 #if DEBUG_MEMORY
00964     int val = item_count.fetchAndAddRelaxed(1) + 1;
00965     if (val == 0)
00966         memory_time.start();
00967     max_count = (val > max_count) ? val : max_count;
00968     if (memory_time.elapsed() > 1000)
00969     {
00970         cout<<"current memory usage: "
00971             <<val<<" * "<<sizeof(LoggingItem)<<endl;
00972         cout<<"max memory usage: "
00973             <<max_count<<" * "<<sizeof(LoggingItem)<<endl;
00974         cout<<"malloc count: "<<(int)malloc_count<<endl;
00975         memory_time.start();
00976     }
00977 #else
00978     item_count.ref();
00979 #endif
00980 
00981     return item;
00982 }
00983 
00986 static void deleteItem(LoggingItem *item)
00987 {
00988     if (!item)
00989         return;
00990 
00991     if (!item->refcount.deref())
00992     {
00993         if (item->threadName)
00994             free(item->threadName);
00995         item_count.deref();
00996         delete item;
00997     }
00998 }
00999 
01004 void LogTimeStamp( struct tm *tm, uint32_t *usec )
01005 {
01006     if( !usec || !tm )
01007         return;
01008 
01009     time_t epoch;
01010 
01011 #if HAVE_GETTIMEOFDAY
01012     struct timeval  tv;
01013     gettimeofday(&tv, NULL);
01014     epoch = tv.tv_sec;
01015     *usec  = tv.tv_usec;
01016 #else
01017     /* Stupid system has no gettimeofday, use less precise QDateTime */
01018     QDateTime date = QDateTime::currentDateTime();
01019     QTime     time = date.time();
01020     epoch = date.toTime_t();
01021     *usec = time.msec() * 1000;
01022 #endif
01023 
01024     localtime_r(&epoch, tm);
01025 }
01026 
01038 void LogPrintLine( uint64_t mask, LogLevel_t level, const char *file, int line,
01039                    const char *function, int fromQString,
01040                    const char *format, ... )
01041 {
01042     va_list         arguments;
01043 
01044     QMutexLocker qLock(&logQueueMutex);
01045 
01046     int type = kMessage;
01047     type |= (mask & VB_FLUSH) ? kFlush : 0;
01048     type |= (mask & VB_STDIO) ? kStandardIO : 0;
01049     LoggingItem *item = createItem(file, function, line, level, type);
01050     if (!item)
01051         return;
01052 
01053     char *formatcopy = NULL;
01054     if( fromQString && strchr(format, '%') )
01055     {
01056         QString string(format);
01057         format = strdup(string.replace(logRegExp, "%%").toLocal8Bit()
01058                               .constData());
01059         formatcopy = (char *)format;
01060     }
01061 
01062     va_start(arguments, format);
01063     vsnprintf(item->message, LOGLINE_MAX, format, arguments);
01064     va_end(arguments);
01065 
01066     if (formatcopy)
01067         free(formatcopy);
01068 
01069     logQueue.enqueue(item);
01070 
01071     if (logThread && logThreadFinished && !logThread->isRunning())
01072     {
01073         while (!logQueue.isEmpty())
01074         {
01075             item = logQueue.dequeue();
01076             qLock.unlock();
01077             logThread->handleItem(item);
01078             deleteItem(item);
01079             qLock.relock();
01080         }
01081     }
01082     else if (logThread && !logThreadFinished && (type & kFlush))
01083     {
01084         logThread->flush();
01085     }
01086 }
01087 
01088 #ifndef _WIN32
01089 
01090 void logSighup( int signum, siginfo_t *info, void *secret )
01091 {
01092     LOG(VB_GENERAL, LOG_INFO, "SIGHUP received, rolling log files.");
01093 
01094     /* SIGHUP was sent.  Close and reopen debug logfiles */
01095     QMutexLocker locker(&loggerListMutex);
01096 
01097     QList<LoggerBase *>::iterator it;
01098     for (it = loggerList.begin(); it != loggerList.end(); ++it)
01099     {
01100         (*it)->reopen();
01101     }
01102 }
01103 #endif
01104 
01108 void logPropagateCalc(void)
01109 {
01110     QString mask = verboseString.trimmed();
01111     mask.replace(QRegExp(" "), ",");
01112     mask.remove(QRegExp("^,"));
01113     logPropagateArgs = " --verbose " + mask;
01114 
01115     if (logPropagateOpts.propagate)
01116         logPropagateArgs += " --logpath " + logPropagateOpts.path;
01117 
01118     QString name = logLevelGetName(logLevel);
01119     logPropagateArgs += " --loglevel " + name;
01120 
01121     for (int i = 0; i < logPropagateOpts.quiet; i++)
01122         logPropagateArgs += " --quiet";
01123 
01124     if (!logPropagateOpts.dblog)
01125         logPropagateArgs += " --nodblog";
01126 
01127 #ifndef _WIN32
01128     if (logPropagateOpts.facility >= 0)
01129     {
01130         CODE *syslogname;
01131 
01132         for (syslogname = &facilitynames[0];
01133              (syslogname->c_name &&
01134               syslogname->c_val != logPropagateOpts.facility); syslogname++);
01135 
01136         logPropagateArgs += QString(" --syslog %1").arg(syslogname->c_name);
01137     }
01138 #endif
01139 }
01140 
01143 bool logPropagateQuiet(void)
01144 {
01145     return logPropagateOpts.quiet;
01146 }
01147 
01160 void logStart(QString logfile, int progress, int quiet, int facility,
01161               LogLevel_t level, bool dblog, bool propagate)
01162 {
01163     LoggerBase *logger;
01164 
01165     {
01166         QMutexLocker qLock(&logQueueMutex);
01167         if (!logThread)
01168             logThread = new LoggerThread();
01169     }
01170 
01171     if (logThread->isRunning())
01172         return;
01173 
01174     logLevel = level;
01175     LOG(VB_GENERAL, LOG_NOTICE, QString("Setting Log Level to LOG_%1")
01176              .arg(logLevelGetName(logLevel).toUpper()));
01177 
01178     logPropagateOpts.propagate = propagate;
01179     logPropagateOpts.quiet = quiet;
01180     logPropagateOpts.facility = facility;
01181     logPropagateOpts.dblog = dblog;
01182 
01183     if (propagate)
01184     {
01185         QFileInfo finfo(logfile);
01186         QString path = finfo.path();
01187         logPropagateOpts.path = path;
01188     }
01189 
01190     logPropagateCalc();
01191 
01192     /* log to the console */
01193     logger = new FileLogger((char *)"-", progress, quiet);
01194 
01195     /* Debug logfile */
01196     if( !logfile.isEmpty() )
01197         logger = new FileLogger((char *)logfile.toLocal8Bit().constData(),
01198                                 false, false);
01199 
01200 #ifndef _WIN32
01201     /* Syslog */
01202     if( facility == -1 )
01203         LOG(VB_GENERAL, LOG_CRIT,
01204                  "Syslogging facility unknown, disabling syslog output");
01205     else if( facility >= 0 )
01206         logger = new SyslogLogger(facility);
01207 #endif
01208 
01209     /* Database */
01210     if( dblog )
01211         logger = new DatabaseLogger((char *)"logging");
01212 
01213 #ifndef _WIN32
01214     /* Setup SIGHUP */
01215     LOG(VB_GENERAL, LOG_NOTICE, "Setting up SIGHUP handler");
01216     struct sigaction sa;
01217     sa.sa_sigaction = logSighup;
01218     sigemptyset( &sa.sa_mask );
01219     sa.sa_flags = SA_RESTART | SA_SIGINFO;
01220     sigaction( SIGHUP, &sa, NULL );
01221 #endif
01222 
01223     (void)logger;
01224 
01225     logThread->start();
01226 }
01227 
01229 void logStop(void)
01230 {
01231     if (logThread)
01232     {
01233         logThread->stop();
01234         logThread->wait();
01235     }
01236 
01237 #ifndef _WIN32
01238     /* Tear down SIGHUP */
01239     struct sigaction sa;
01240     sa.sa_handler = SIG_DFL;
01241     sigemptyset( &sa.sa_mask );
01242     sa.sa_flags = SA_RESTART;
01243     sigaction( SIGHUP, &sa, NULL );
01244 #endif
01245 
01246     QList<LoggerBase *>::iterator it;
01247     for (it = loggerList.begin(); it != loggerList.end(); ++it)
01248     {
01249         (*it)->stopDatabaseAccess();
01250     }
01251 }
01252 
01257 void loggingRegisterThread(const QString &name)
01258 {
01259     if (logThreadFinished)
01260         return;
01261 
01262     QMutexLocker qLock(&logQueueMutex);
01263 
01264     LoggingItem *item = createItem(__FILE__, __FUNCTION__, __LINE__,
01265                                    (LogLevel_t)LOG_DEBUG, kRegistering);
01266     if (item)
01267     {
01268         item->threadName = strdup((char *)name.toLocal8Bit().constData());
01269         logQueue.enqueue(item);
01270     }
01271 }
01272 
01275 void loggingDeregisterThread(void)
01276 {
01277     if (logThreadFinished)
01278         return;
01279 
01280     QMutexLocker qLock(&logQueueMutex);
01281 
01282     LoggingItem *item = createItem(__FILE__, __FUNCTION__, __LINE__,
01283                                    (LogLevel_t)LOG_DEBUG, kDeregistering);
01284     if (item)
01285         logQueue.enqueue(item);
01286 }
01287 
01291 int syslogGetFacility(QString facility)
01292 {
01293 #ifdef _WIN32
01294     LOG(VB_GENERAL, LOG_NOTICE,
01295         "Windows does not support syslog, disabling" );
01296     return( -2 );
01297 #else
01298     CODE *name;
01299     int i;
01300     QByteArray ba = facility.toLocal8Bit();
01301     char *string = (char *)ba.constData();
01302 
01303     for (i = 0, name = &facilitynames[0];
01304          name->c_name && strcmp(name->c_name, string); i++, name++);
01305 
01306     return( name->c_val );
01307 #endif
01308 }
01309 
01313 LogLevel_t logLevelGet(QString level)
01314 {
01315     QMutexLocker locker(&loglevelMapMutex);
01316     if (!verboseInitialized)
01317     {
01318         locker.unlock();
01319         verboseInit();
01320         locker.relock();
01321     }
01322 
01323     for (LoglevelMap::iterator it = loglevelMap.begin();
01324          it != loglevelMap.end(); ++it)
01325     {
01326         LoglevelDef *item = (*it);
01327         if ( item->name == level.toLower() )
01328             return (LogLevel_t)item->value;
01329     }
01330 
01331     return LOG_UNKNOWN;
01332 }
01333 
01337 QString logLevelGetName(LogLevel_t level)
01338 {
01339     QMutexLocker locker(&loglevelMapMutex);
01340     if (!verboseInitialized)
01341     {
01342         locker.unlock();
01343         verboseInit();
01344         locker.relock();
01345     }
01346     LoglevelMap::iterator it = loglevelMap.find((int)level);
01347 
01348     if ( it == loglevelMap.end() )
01349         return QString("unknown");
01350 
01351     return (*it)->name;
01352 }
01353 
01360 void verboseAdd(uint64_t mask, QString name, bool additive, QString helptext)
01361 {
01362     VerboseDef *item = new VerboseDef;
01363 
01364     item->mask = mask;
01365     name.detach();
01366     // VB_GENERAL -> general
01367     name.remove(0, 3);
01368     name = name.toLower();
01369     item->name = name;
01370     item->additive = additive;
01371     helptext.detach();
01372     item->helpText = helptext;
01373 
01374     verboseMap.insert(name, item);
01375 }
01376 
01382 void loglevelAdd(int value, QString name, char shortname)
01383 {
01384     LoglevelDef *item = new LoglevelDef;
01385 
01386     item->value = value;
01387     name.detach();
01388     // LOG_CRIT -> crit
01389     name.remove(0, 4);
01390     name = name.toLower();
01391     item->name = name;
01392     item->shortname = shortname;
01393 
01394     loglevelMap.insert(value, item);
01395 }
01396 
01398 void verboseInit(void)
01399 {
01400     QMutexLocker locker(&verboseMapMutex);
01401     QMutexLocker locker2(&loglevelMapMutex);
01402     verboseMap.clear();
01403     loglevelMap.clear();
01404 
01405     // This looks funky, so I'll put some explanation here.  The verbosedefs.h
01406     // file gets included as part of the mythlogging.h include, and at that
01407     // time, the normal (without _IMPLEMENT_VERBOSE defined) code case will
01408     // define the VerboseMask enum.  At this point, we force it to allow us
01409     // to include the file again, but with _IMPLEMENT_VERBOSE set so that the
01410     // single definition of the VB_* values can be shared to define also the
01411     // contents of verboseMap, via repeated calls to verboseAdd()
01412 
01413 #undef VERBOSEDEFS_H_
01414 #define _IMPLEMENT_VERBOSE
01415 #include "verbosedefs.h"
01416     
01417     verboseInitialized = true;
01418 }
01419 
01420 
01423 void verboseHelp(void)
01424 {
01425     QString m_verbose = verboseString.trimmed();
01426     m_verbose.replace(QRegExp(" "), ",");
01427     m_verbose.remove(QRegExp("^,"));
01428 
01429     cerr << "Verbose debug levels.\n"
01430             "Accepts any combination (separated by comma) of:\n\n";
01431 
01432     for (VerboseMap::Iterator vit = verboseMap.begin();
01433          vit != verboseMap.end(); ++vit )
01434     {
01435         VerboseDef *item = vit.value();
01436         QString name = QString("  %1").arg(item->name, -15, ' ');
01437         if (item->helpText.isEmpty())
01438             continue;
01439         cerr << name.toLocal8Bit().constData() << " - " << 
01440                 item->helpText.toLocal8Bit().constData() << endl;
01441     }
01442 
01443     cerr << endl <<
01444       "The default for this program appears to be: '-v " <<
01445       m_verbose.toLocal8Bit().constData() << "'\n\n"
01446       "Most options are additive except for 'none' and 'all'.\n"
01447       "These two are semi-exclusive and take precedence over any\n"
01448       "other options.  However, you may use something like\n"
01449       "'-v none,jobqueue' to receive only JobQueue related messages\n"
01450       "and override the default verbosity level.\n\n"
01451       "Additive options may also be subtracted from 'all' by\n"
01452       "prefixing them with 'no', so you may use '-v all,nodatabase'\n"
01453       "to view all but database debug messages.\n\n"
01454       "Some debug levels may not apply to this program.\n\n";
01455 }
01456 
01460 int verboseArgParse(QString arg)
01461 {
01462     QString option;
01463 
01464     if (!verboseInitialized)
01465         verboseInit();
01466 
01467     QMutexLocker locker(&verboseMapMutex);
01468 
01469     verboseMask = verboseDefaultInt;
01470     verboseString = QString(verboseDefaultStr);
01471 
01472     if (arg.startsWith('-'))
01473     {
01474         cerr << "Invalid or missing argument to -v/--verbose option\n";
01475         return GENERIC_EXIT_INVALID_CMDLINE;
01476     }
01477 
01478     QStringList verboseOpts = arg.split(QRegExp("\\W+"));
01479     for (QStringList::Iterator it = verboseOpts.begin();
01480          it != verboseOpts.end(); ++it )
01481     {
01482         option = (*it).toLower();
01483         bool reverseOption = false;
01484 
01485         if (option != "none" && option.left(2) == "no")
01486         {
01487             reverseOption = true;
01488             option = option.right(option.length() - 2);
01489         }
01490 
01491         if (option == "help")
01492         {
01493             verboseHelp();
01494             return GENERIC_EXIT_INVALID_CMDLINE;
01495         }
01496         else if (option == "important")
01497         {
01498             cerr << "The \"important\" log mask is no longer valid.\n";
01499         }
01500         else if (option == "extra")
01501         {
01502             cerr << "The \"extra\" log mask is no longer valid.  Please try "
01503                     "--loglevel debug instead.\n";
01504         }
01505         else if (option == "default")
01506         {
01507             if (haveUserDefaultValues)
01508             {
01509                 verboseMask = userDefaultValueInt;
01510                 verboseString = userDefaultValueStr;
01511             }
01512             else
01513             {
01514                 verboseMask = verboseDefaultInt;
01515                 verboseString = QString(verboseDefaultStr);
01516             }
01517         }
01518         else 
01519         {
01520             VerboseDef *item = verboseMap.value(option);
01521 
01522             if (item)
01523             {
01524                 if (reverseOption)
01525                 {
01526                     verboseMask &= ~(item->mask);
01527                     verboseString = verboseString.remove(' ' + item->name);
01528                     verboseString += " no" + item->name;
01529                 }
01530                 else
01531                 {
01532                     if (item->additive)
01533                     {
01534                         if (!(verboseMask & item->mask))
01535                         {
01536                             verboseMask |= item->mask;
01537                             verboseString += ' ' + item->name;
01538                         }
01539                     }
01540                     else
01541                     {
01542                         verboseMask = item->mask;
01543                         verboseString = item->name;
01544                     }
01545                 }
01546             }
01547             else
01548             {
01549                 cerr << "Unknown argument for -v/--verbose: " << 
01550                         option.toLocal8Bit().constData() << endl;;
01551                 return GENERIC_EXIT_INVALID_CMDLINE;
01552             }
01553         }
01554     }
01555 
01556     if (!haveUserDefaultValues)
01557     {
01558         haveUserDefaultValues = true;
01559         userDefaultValueInt = verboseMask;
01560         userDefaultValueStr = verboseString;
01561     }
01562 
01563     return GENERIC_EXIT_OK;
01564 }
01565 
01570 QString logStrerror(int errnum)
01571 {
01572     return QString("%1 (%2)").arg(strerror(errnum)).arg(errnum);
01573 }
01574 
01575 
01576 /*
01577  * vim:ts=4:sw=4:ai:et:si:sts=4
01578  */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends