From: Enar Väikene Date: Fri, 22 Apr 2011 12:36:35 +0000 (+0300) Subject: More work on the common library. The project builds not without errors, but we have... X-Git-Url: https://vaikene.ee/gitweb/overview.html?a=commitdiff_plain;h=720224734cb4f6ea2708c6b5ecf93cc666aad378;p=evaf More work on the common library. The project builds not without errors, but we have a little chicken-egg issue. --- diff --git a/src/libs/Common/CMakeLists.txt b/src/libs/Common/CMakeLists.txt index 1d16647..20eca77 100644 --- a/src/libs/Common/CMakeLists.txt +++ b/src/libs/Common/CMakeLists.txt @@ -19,6 +19,8 @@ set(SRCS env.cpp event.cpp eventqueue.cpp + globals.cpp + logger.cpp registry.cpp ) @@ -32,6 +34,7 @@ set(MOC_HDRS app.h env.h eventqueue.h + logger.h registry.h ) diff --git a/src/libs/Common/globals.cpp b/src/libs/Common/globals.cpp new file mode 100644 index 0000000..dfb1595 --- /dev/null +++ b/src/libs/Common/globals.cpp @@ -0,0 +1,65 @@ +/** + * @file Common/globals.cpp + * @brief Global constants and macros for eVaf + * @author Enar Vaikene + * + * Copyright (c) 2011 Enar Vaikene + * + * This file is part of the eVaf C++ cross-platform application development framework. + * + * This file can be used under the terms of the GNU General Public License + * version 3.0 as published by the Free Software Foundation and appearing in + * the file LICENSE included in the packaging of this file. Please review the + * the following information to ensure the GNU General Public License version + * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + * + * Alternatively, this file may be used in accordance with the Commercial License + * Agreement provided with the Software. + */ + +#include "globals.h" +#include "env.h" +#include "app.h" +#include "logger.h" +#include "version.h" +#include "ilogger.h" + +#include + + +//------------------------------------------------------------------- + +bool eVaf::Common::init() +{ + if (QCoreApplication::instance() == 0) { + EVAF_FATAL_ERROR("QApplication is not instantiated"); + return false; + } + + EVAF_INFO("Initializing %s.Globals", VER_MODULE_NAME_STR); + + // Initialize all the common interface implementations in the proper sequence + + eVaf::Common::Internal::Env * env = + qobject_cast(eVaf::Common::iEnv::instance()); + if (env) { + if (!env->init()) + return false; + } + eVaf::Common::Internal::Logger * logger = + qobject_cast(eVaf::Common::iLogger::instance()); + if (logger) { + if (!logger->init()) + return false; + } + eVaf::Common::Internal::App * app = + qobject_cast(eVaf::Common::iApp::instance()); + if (app) { + if (!app->init()) + return false; + } + + EVAF_INFO("%s.Globals initialized", VER_MODULE_NAME_STR); + + return true; +} diff --git a/src/libs/Common/ilogger.h b/src/libs/Common/ilogger.h index 81d8016..cf8e5c7 100644 --- a/src/libs/Common/ilogger.h +++ b/src/libs/Common/ilogger.h @@ -99,7 +99,7 @@ public: * Returns the current severity level * @param source Name of the source or default if omitted. */ - virtual Severity severity(QString const & source = 0) const = 0; + virtual Severity severity(QString const & source = 0) = 0; /** * Changes the current severity level. @@ -116,7 +116,7 @@ public: * Returns the current maximum size of log files in KiB. * @param source Name of the source or default if omitted. */ - virtual uint maxSize(QString const & source = 0) const = 0; + virtual uint maxSize(QString const & source = 0) = 0; /** * Changes the maximum size of log files for the given source @@ -136,7 +136,7 @@ public: * Returns the maximum number of log files. * @param source Name of the source or default if omitted. */ - virtual uint maxCount(QString const & source = 0) const = 0; + virtual uint maxCount(QString const & source = 0) = 0; /** * Changes the maximum number of log files @@ -226,6 +226,22 @@ public: */ virtual FatalMsgHandler installFatalMsgHandler(FatalMsgHandler newHandler) = 0; + +signals: + + /** + * Logger event signal + * @param severity Severity of the message + * @param text The message + * @param source Source of the message + * @param where Where the message was output + * + * This signal is emitted for every message output with the iLogger interface. Connect + * your receiver to this signal if you want to add your own message handling. For example, + * use this signal to show messages in a log window etc. + */ + void loggerEvent(Severity severity, QString const & text, QString const & source, QString const & where); + }; } // namespace eVaf::Common diff --git a/src/libs/Common/logger.cpp b/src/libs/Common/logger.cpp index 0909fad..98d3770 100644 --- a/src/libs/Common/logger.cpp +++ b/src/libs/Common/logger.cpp @@ -20,6 +20,7 @@ #include "logger.h" #include "iregistry.h" #include "ienv.h" +#include "version.h" #include @@ -36,20 +37,7 @@ //------------------------------------------------------------------- -using namespace eVaf::Common; - -iLogger * iLogger::instance() -{ - static Internal::Logger singleton; - return &singleton; -} - - -//------------------------------------------------------------------- - -using namespace eVaf::Common::Internal; - -void defFatalMsgHandler(QString const & msg, QString const & source, QString const & where) +void eVaf::Common::Internal::defFatalMsgHandler(QString const & msg, QString const & source, QString const & where) { Q_UNUSED(source); @@ -65,6 +53,19 @@ void defFatalMsgHandler(QString const & msg, QString const & source, QString con //------------------------------------------------------------------- +using namespace eVaf::Common; + +iLogger * iLogger::instance() +{ + static Internal::Logger singleton; + return &singleton; +} + + +//------------------------------------------------------------------- + +using namespace eVaf::Common::Internal; + LoggerSource::LoggerSource() : QSharedData() , severity(iLogger::Fatal) @@ -88,3 +89,370 @@ void LoggerSource::init(QString const & source, QString const & logDir, QString //------------------------------------------------------------------- + +/// Recursively renames backup files +void renameBackupFile(QDir & dir, QString const & baseName, int idx) +{ + QString f1 = QString("%1.%2").arg(baseName).arg(idx); + QString f2 = QString("%1.%2").arg(baseName).arg(idx + 1); + + if (dir.exists(f2)) + renameBackupFile(dir, baseName, idx + 1); + + dir.rename(f1, f2); +} + +void LoggerWorker::writeToLogFile(LoggerSource const & src, QString const & msg) +{ + QFile f(src.fileName); + QFile::OpenMode mode; +#ifdef Q_OS_LINUX + mode = QFile::Append | QFile::Text | QFile::Unbuffered; +#else + mode = QFile::Append | QFile::Text; +#endif + + // Open the log file + if (f.open(mode)) { + + // Write to the log file and then close the file + f.write(msg.toLocal8Bit()); + f.close(); + + // Check the file size + if (src.maxSize > 0 && f.size() > src.maxSize) { + + QDir dir(QFileInfo(src.fileName).dir()); + QString baseName = QFileInfo(src.fileName).fileName(); + + // Delete the oldest backup file if the number of files has reached the maximum + if (src.maxCount > 0 && dir.exists(QString("%1.%2").arg(baseName).arg(src.maxCount - 1))) + dir.remove(QString("%1.%2").arg(baseName).arg(src.maxCount - 1)); + + // Shift backup files (.0 -> .1, .1 -> .2 etc) + renameBackupFile(dir, baseName, 0); + + // Rename the current log file to the backup #0 file + dir.rename(baseName, baseName + ".0"); + + } + } +} + + +//------------------------------------------------------------------- + +Logger::Logger() + : iLogger() + , mFatalMsgHandler(defFatalMsgHandler) + , mConsoleSeverity(iLogger::Fatal) + , mDefaultSource("evaf") + , mThread(0) + , mWorker(0) +{ + setObjectName(QString("%1-iLogger").arg(VER_MODULE_NAME_STR)); + + qRegisterMetaType("LoggerSource"); + + write(Info, QString("%1 created").arg(objectName()), 0, printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__)); +} + +Logger::~Logger() +{ + // Disconnect any potential receivers from this object + disconnect(this, SIGNAL(loggerEvent(eVaf::Common::iLogger::Severity,QString,QString,QString)), 0, 0); + + // Destroy the worker thread + if (mWorker) { + delete mWorker; + if (mThread) { + mThread->quit(); + mThread->wait(); + delete mThread; + } + } + + write(Info, QString("%1 destroyed").arg(objectName()), 0, printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__)); +} + +bool Logger::init() +{ + // Register our interface + iRegistry::instance()->registerInterface("iLogger", this); + + // Clear existing sources in case the application was restarted + mSources.clear(); + + // Destroy the previous worker thread + if (mWorker) { + delete mWorker; + if (mThread) { + mThread->quit(); + mThread->wait(); + delete mThread; + } + } + + // Create the worker thread + mWorker = new LoggerWorker; + mThread = new QThread; + mWorker->moveToThread(mThread); + mThread->start(QThread::IdlePriority); + connect(this, SIGNAL(writeToLogFile(LoggerSource,QString)), mWorker, SLOT(writeToLogFile(LoggerSource,QString)), Qt::QueuedConnection); + + write(Info, QString("%1 initialized").arg(objectName()), 0, printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__)); + + return true; +} + +void Logger::setDefaultSource(QString const & source) +{ + mDefaultSource = source; +} + +iLogger::Severity Logger::severity(QString const & source) +{ + return getSource(source)->severity; +} + +void Logger::setSeverity(iLogger::Severity severity, QString const & source) +{ + getSource(source)->severity = severity; +} + +uint Logger::maxSize(QString const & source) +{ + return getSource(source)->maxSize; +} + +void Logger::setMaxSize(uint maxSize, QString const & source) +{ + getSource(source)->maxSize = maxSize; +} + +uint Logger::maxCount(QString const & source) +{ + return getSource(source)->maxCount; +} + +void Logger::setMaxCount(uint maxCount, QString const & source) +{ + getSource(source)->maxCount = maxCount; +} + +void Logger::setConsoleSeverity(iLogger::Severity severity) +{ + mConsoleSeverity = severity; +} + +void Logger::write(Severity severity, QString const & msg, QString const & source, QString const & where) +{ + static char const * const severityText[] = + { + "[NONE] : ", + "[FATAL] : ", + "[ERROR] : ", + "[WARNING]: ", + "[INFO] : ", + "[DEBUG] : " + }; + + // Make sure that we don't output messages with the None severity + if (severity == iLogger::None) + return; + + // Write to the log file + LoggerSource * src = getSource(source); + if (severity <= src->severity && src->severity != iLogger::None) { + QString buf; + QTextStream io(&buf); + + // Date/time stamp + io << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"); + + // Severity + io << " " << severityText[severity]; + + // Message + io << msg; + + // Location in the source file + if (!where.isEmpty()) + io << " (occurred in " << where << ")"; + + io << endl; + io.flush(); + + // If the worker is initialized, use the worker thread to do the job + if (mWorker) { + emit writeToLogFile(*src, buf); + } + // Otherwise we have to do it ourselves + else { + QFile f(src->fileName); + QFile::OpenMode mode; +#ifdef Q_OS_LINUX + mode = QFile::Append | QFile::Text | QFile::Unbuffered; +#else + mode = QFile::Append | QFile::Text; +#endif + + if (f.open(mode)) { + f.write(buf.toLocal8Bit()); + f.close(); + } + } + } + + // Output to the console + if (source.isEmpty() && severity <= mConsoleSeverity && mConsoleSeverity != iLogger::None) { + FILE * f = (severity < iLogger::Info) ? stderr : stdout; + + // Set text colors +#ifdef Q_OS_LINUX + switch (severity) { + case iLogger::Info: + fprintf(f, "\e[32m"); // Green + break; + case iLogger::Warning: + fprintf(f, "\e[1m"); // Bold + break; + case iLogger::Error: + fprintf(f, "\e[31m"); // Red + break; + case iLogger::Fatal: + fprintf(f, "\e[31m\e[1m"); // Bold Red + break; + default: + fprintf(f, "\e[34m"); // Blue + break; + } +#elif defined Q_OS_WIN32 + switch (severity) { + case iLogger::Info: + setColor(FOREGROUND_GREEN); + break; + case iLogger::Warning: + setColor(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED); + break; + case iLogger::Error: + setColor(FOREGROUND_RED | FOREGROUND_INTENSITY); + break; + case iLogger::Fatal: + setColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED); + break; + default: + setColor(FOREGROUND_BLUE); + break; + } +#endif + + // Output the message + fprintf(f, "%s %s\n", severityText[severity], qPrintable(msg)); + + // Add the location + if (!where.isEmpty()) + fprintf(f, "\t(occurred in %s)\n\n", qPrintable(where)); + + // Reset text colors +#ifdef Q_OS_LINUX + fputs("\e[0m", f); +#elif defined Q_OS_WIN32 + setColor(7); +#endif + + // Flush the output + fflush(f); + } + + // Inform others + emit loggerEvent(severity, msg, source, where); + + // Handle fatal error messages + if (severity == iLogger::Fatal && mFatalMsgHandler) + mFatalMsgHandler(msg, source, where); +} + +QString Logger::printf(char const * const fmt, ...) const +{ +#ifdef Q_OS_WIN32 + char str[4096]; +#else + char * str = 0; +#endif + + va_list ap; + ::va_start(ap, fmt); +#ifdef Q_OS_WIN32 + _vsnprintf_s(str, sizeof(str), _TRUNCATE, fmt, ap); +#else + if (::vasprintf(&str, fmt, ap)); // IF is needed to avoid the compiler warning +#endif + ::va_end(ap); + + QString rval(str); + +#ifndef Q_OS_WIN32 + ::free(str); +#endif + + return rval; +} + +QString Logger::printable(QByteArray const & msg) const +{ + static char const * const ctrlChars[] = { + "NUL", "SOH", "STX", "ETX", "EOT", "ENW", "ACK", "BEL", + "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", + "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", + "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" + }; + + QString rval; + int sz = msg.size(); + for (int i = 0; i < sz; ++i) { + uchar ch = uchar(msg.at(i)); + if (ch < 32) + rval.append(QString("[%1]").arg(ctrlChars[ch])); + else if (ch < 127) + rval.append(msg.at(i)); + else if (ch == 127) + rval.append("[DEL]"); + else + rval.append(QString("[\\x%1]").arg(ch, 2, 16, QChar('0'))); + } + + return rval; +} + +FatalMsgHandler Logger::installFatalMsgHandler(FatalMsgHandler newHandler) +{ + FatalMsgHandler oldHandler = mFatalMsgHandler; + mFatalMsgHandler = newHandler; + return oldHandler; +} + +LoggerSource * Logger::getSource(QString const & source) +{ + QHash >::const_iterator it = mSources.constFind(source); + if (it != mSources.constEnd()) + return it->data(); + else { + // Create the new source + QExplicitlySharedDataPointer src(new LoggerSource); + mSources.insert(source, src); + + // Initialize the new source + src->init(source.isEmpty() ? mDefaultSource : source, iEnv::instance()->logDir(), iEnv::instance()->etcDir()); + + return src.data(); + } +} + +#ifdef Q_OS_WIN32 +void Logger::setColor(short int c) +{ + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(handle, c); +} +#endif diff --git a/src/libs/Common/logger.h b/src/libs/Common/logger.h index c47be4f..1dfdc62 100644 --- a/src/libs/Common/logger.h +++ b/src/libs/Common/logger.h @@ -23,6 +23,12 @@ #include "ilogger.h" #include +#include +#include +#include +#include + +class QThread; namespace eVaf { @@ -96,6 +102,30 @@ public: // Members (we don't bother adding getter/setter functions) }; +/** + * Worker class for the logger. + * + * This class separates potentially expensive I/O from the iLogger interface making sure + * that writing to the log file does not block the main thread. + */ +class LoggerWorker : public QObject +{ + Q_OBJECT + +public slots: + + /** + * Writes a message to the log file + * @param src The logger source + * @param msg The message + * + * This function writes the message to the log file. It also controls the size and + * number of log files. + */ + void writeToLogFile(LoggerSource const & src, QString const & msg); + +}; + /** * iLogger interface implementation. * @@ -125,48 +155,61 @@ public: virtual void setDefaultSource(QString const & source); - virtual Severity severity(QString const & source = 0) const; + virtual iLogger::Severity severity(QString const & source = 0); - virtual void setSeverity(Severity severity, QString const & source = 0); + virtual void setSeverity(iLogger::Severity severity, QString const & source = 0); - virtual uint maxSize(QString const & source = 0) const; + virtual uint maxSize(QString const & source = 0); virtual void setMaxSize(uint maxSize, QString const & source = 0); - virtual uint maxCount(QString const & source = 0) const; + virtual uint maxCount(QString const & source = 0); virtual void setMaxCount(uint maxCount, QString const & source = 0); - virtual Severity consoleSeverity() const { return mConsoleSeverity; } + virtual iLogger::Severity consoleSeverity() const { return mConsoleSeverity; } - virtual void setConsoleSeverity(Severity severity); + virtual void setConsoleSeverity(iLogger::Severity severity); virtual void write(Severity severity, QString const & msg, QString const & source = 0, QString const & where = 0); virtual QString printf(char const * const fmt, ...) const; + virtual QString printable(QByteArray const & msg) const; + virtual FatalMsgHandler installFatalMsgHandler(FatalMsgHandler newHandler); +signals: + + void writeToLogFile(LoggerSource const & src, QString const & msg); + + private: // Members /// Current fatal error message handler FatalMsgHandler mFatalMsgHandler; + /// Console output severity level + iLogger::Severity mConsoleSeverity; + /// Current default source (defaults to "evaf") QString mDefaultSource; /// Logger sources QHash > mSources; + /// Worker thread + QThread * mThread; -private: // Methods + /// Worker object + LoggerWorker * mWorker; - /// Returns the source by the name - LoggerSource * getSource(QString const & name) const; - /// Creates a new source - LoggerSource * addSource(QString const & name); +private: // Methods + + /// Returns the source by the name. The source is created if it does not exist yet. + LoggerSource * getSource(QString const & name); #ifdef Q_OS_WIN32 /// Changes text colors on the Windows console diff --git a/src/main/GUI/exithandler.cpp b/src/main/GUI/exithandler.cpp index 99d376f..f0ad4bd 100644 --- a/src/main/GUI/exithandler.cpp +++ b/src/main/GUI/exithandler.cpp @@ -107,9 +107,8 @@ static BOOL WINAPI signalHandler(DWORD sig) } // namespace eVaf::GUI } // namespace eVaf -using namespace eVaf::GUI::Internal; -bool installExitHandler() +bool eVaf::GUI::Internal::installExitHandler() { #ifdef Q_OS_LINUX