* @brief iLogger interface implementation
* @author Enar Vaikene
*
- * Copyright (c) 2011 Enar Vaikene
+ * Copyright (c) 2011-2012 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);
//-------------------------------------------------------------------
+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)
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