X-Git-Url: https://vaikene.ee/gitweb/pswgen11.html?a=blobdiff_plain;f=src%2Flibs%2FCommon%2Flogger.cpp;h=94d9fa9eb9e7ea70136ed359960376a9ce844c68;hb=4c0329c5c2690bde28212c89029015a5da4c7e34;hp=0909fad56ff6e3afb18d349dcf6ce667de6b3c96;hpb=4d81227da330c21c7aa0badc88bd5ad4467067fb;p=evaf diff --git a/src/libs/Common/logger.cpp b/src/libs/Common/logger.cpp index 0909fad..94d9fa9 100644 --- a/src/libs/Common/logger.cpp +++ b/src/libs/Common/logger.cpp @@ -3,7 +3,7 @@ * @brief iLogger interface implementation * @author Enar Vaikene * - * Copyright (c) 2011 Enar Vaikene + * Copyright (c) 2011-2019 Enar Vaikene * * This file is part of the eVaf C++ cross-platform application development framework. * @@ -19,7 +19,11 @@ #include "logger.h" #include "iregistry.h" -#include "ienv.h" +#include "iapp.h" +#include "iconfig.h" +#include "globals.h" +#include "inifile.h" +#include "version.h" #include @@ -36,22 +40,9 @@ //------------------------------------------------------------------- -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) +[[noreturn]] void eVaf::Common::Internal::defFatalMsgHandler(QString const & msg, QString const & source, QString const & where) { - Q_UNUSED(source); + Q_UNUSED(source) fprintf(stderr, "FATAL ERROR: %s (occurred in %s)\n", qPrintable(msg), qPrintable(where)); @@ -65,6 +56,27 @@ void defFatalMsgHandler(QString const & msg, QString const & source, QString con //------------------------------------------------------------------- +using namespace eVaf::Common; + +namespace +{ + static Internal::Logger * singleton = nullptr; +} + +iLogger * iLogger::instance() +{ + if (nullptr == singleton) + { + singleton = new Internal::Logger; + } + return singleton; +} + + +//------------------------------------------------------------------- + +using namespace eVaf::Common::Internal; + LoggerSource::LoggerSource() : QSharedData() , severity(iLogger::Fatal) @@ -74,17 +86,433 @@ LoggerSource::LoggerSource() LoggerSource::LoggerSource(LoggerSource const & o) : QSharedData() + , name(o.name) , severity(o.severity) + , fileName(o.fileName) , maxSize(o.maxSize) , maxCount(o.maxCount) {} -void LoggerSource::init(QString const & source, QString const & logDir, QString const & etcDir) +void LoggerSource::init(QString const & source) +{ + name = source; + fileName = iApp::instance()->logDir() + source + ".log"; + + // Set default settings + severity = iLogger::Fatal; + maxSize = 100 * 1024; + maxCount = 3; + + // Read settings from the 'logger.ini' file + QString confFileName = iApp::instance()->etcDir() + "logger.ini"; + if (QFile::exists(confFileName)) { + IniFile ini(confFileName, QIODevice::ReadOnly); + + // Default values for all sources + maxSize = 1024 * ini.getValue(".default/log_size", maxSize / 1024).toUInt(); + maxCount = ini.getValue(".default/log_count", maxCount).toUInt(); + + // Default values for this source + maxSize = 1024 * ini.getValue(source.toLatin1() + "/log_size", maxSize / 1024).toUInt(); + maxCount = ini.getValue(source.toLatin1() + "/log_count", maxCount).toUInt(); + } +} + + +//------------------------------------------------------------------- + +namespace +{ + /// 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) { - Q_UNUSED(etcDir); + //::printf("writeToLogFile(\'%s\', \'%s\') fileName = \'%s\'\n", qPrintable(src.name), qPrintable(msg), qPrintable(src.fileName)); + if (src.fileName.isEmpty()) + return; + 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); - fileName = logDir + source + ".log"; + // Rename the current log file to the backup #0 file + dir.rename(baseName, baseName + ".0"); + + } + } } //------------------------------------------------------------------- + +void Logger::destroyInstance() +{ + if (singleton != nullptr) + { + delete singleton; + singleton = nullptr; + } +} + +Logger::Logger() + : iLogger() + , mReady(false) + , mFatalMsgHandler(defFatalMsgHandler) + , mConsoleSeverity(iLogger::Fatal) +{ + setObjectName(QString("%1-iLogger").arg(VER_MODULE_NAME_STR)); + + qRegisterMetaType("LoggerSource"); + + // Create the default source + mDefaultSource = new LoggerSource; + mDefaultSource->name = "common"; + + write(Info, QString("%1 created").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__)); +} + +Logger::~Logger() +{ + // Disconnect any potential receivers from this object + disconnect(this, SIGNAL(loggerEvent(Common::iLogger::Severity,QString,QString,QString)), nullptr, nullptr); + + // Destroy the worker thread + if (mWorker) { + mWorker.reset(); + if (mThread) { + mThread->quit(); + mThread->wait(); + mThread.reset(); + } + } + + write(Info, QString("%1 destroyed").arg(objectName()), QString(), 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(); + + // Set the default source name to the name of the application + setDefaultSource(iApp::instance()->name()); + + // Read configuration parameters from the application's INI file + QVariant v = iConfig::instance()->getValue(QString("%1/general/log_level").arg(iApp::instance()->name()), severity()); + if (v.isValid()) + setSeverity(iLogger::Severity(qBound(int(iLogger::None), v.toInt(), int(iLogger::Debug)))); + v = iConfig::instance()->getValue(QString("%1/general/log_size").arg(iApp::instance()->name()), maxSize()); + if (v.isValid()) + setMaxSize(v.toUInt()); + v = iConfig::instance()->getValue(QString("%1/general/log_cnt").arg(iApp::instance()->name()), maxCount()); + if (v.isValid()) + setMaxCount(v.toUInt()); + + // Destroy the previous worker thread + if (mThread) { + mThread->quit(); + mThread->wait(); + } + + // Create the worker thread + mWorker.reset(new LoggerWorker); + mThread.reset(new QThread); + mWorker->moveToThread(mThread.data()); + mThread->start(QThread::IdlePriority); + connect(this, SIGNAL(writeToLogFile(LoggerSource,QString)), mWorker.data(), SLOT(writeToLogFile(LoggerSource,QString)), Qt::QueuedConnection); + + mReady = true; + + write(Info, QString("%1 initialized").arg(objectName()), QString(), printf("%s:%s:%d", __FILE__, __FUNCTION__, __LINE__)); + + return true; +} + +QString Logger::defaultSource() const +{ + return mDefaultSource->name; +} + +void Logger::setDefaultSource(QString const & source) +{ + LoggerSource * src = getSource(); + if (src && src->name != source) + getSource(QString())->init(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 * 1024; +} + +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 + if (mReady) { + 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(); + + emit writeToLogFile(*src, buf); + } + } + + // Output to the console + if (source.isEmpty() && severity <= mConsoleSeverity && mConsoleSeverity != iLogger::None) { + FILE * f = (severity < iLogger::Info) ? stderr : stdout; + + // Set text colors +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + switch (severity) { + case iLogger::Info: + fprintf(f, "\033[32m"); // Green + break; + case iLogger::Warning: + fprintf(f, "\033[1m"); // Bold + break; + case iLogger::Error: + fprintf(f, "\033[31m"); // Red + break; + case iLogger::Fatal: + fprintf(f, "\033[31m\033[1m"); // Bold Red + break; + default: + fprintf(f, "\033[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 +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + fputs("\033[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 = nullptr; +#endif + + va_list ap; +#ifdef Q_OS_WIN32 + va_start(ap, fmt); +# ifdef Q_CC_GNU + vsnprintf(str, sizeof(str), fmt, ap); +# else + _vsnprintf_s(str, sizeof(str), _TRUNCATE, fmt, ap); +# endif + va_end(ap); +#else + ::va_start(ap, fmt); + if (::vasprintf(&str, fmt, ap)) {} // IF is needed to avoid the compiler warning + ::va_end(ap); +#endif + + 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) +{ + if (source.isEmpty() || source == mDefaultSource->name) + return mDefaultSource.data(); + + 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); + + return src.data(); + } +} + +#ifdef Q_OS_WIN32 +void Logger::setColor(short int c) +{ + HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(handle, c); +} +#endif