|
MythTV
0.26-pre
|
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 */
1.7.6.1