]> vaikene.ee Git - evaf/blobdiff - src/libs/Common/logger.cpp
Warning fixes and copyright update.
[evaf] / src / libs / Common / logger.cpp
index 0909fad56ff6e3afb18d349dcf6ce667de6b3c96..94d9fa9eb9e7ea70136ed359960376a9ce844c68 100644 (file)
@@ -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.
  *
 
 #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 <QtCore>
 
 
 //-------------------------------------------------------------------
 
-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>("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<QString, QExplicitlySharedDataPointer<LoggerSource> >::const_iterator it = mSources.constFind(source);
+    if (it != mSources.constEnd())
+        return it->data();
+    else {
+        // Create the new source
+        QExplicitlySharedDataPointer<LoggerSource> 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