/** * @file Common/logger.cpp * @brief iLogger interface implementation * @author Enar Vaikene * * Copyright (c) 2011-2012 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 "logger.h" #include "iregistry.h" #include "iapp.h" #include "iconfig.h" #include "globals.h" #include "inifile.h" #include "version.h" #include #ifdef Q_OS_WIN32 # include #endif #ifdef Q_OS_LINUX # include # include # include #endif //------------------------------------------------------------------- void eVaf::Common::Internal::defFatalMsgHandler(QString const & msg, QString const & source, QString const & where) { Q_UNUSED(source); fprintf(stderr, "FATAL ERROR: %s (occurred in %s)\n", qPrintable(msg), qPrintable(where)); #ifdef Q_OS_LINUX abort(); #else exit(1); #endif } //------------------------------------------------------------------- using namespace eVaf::Common; iLogger * iLogger::instance() { static Internal::Logger singleton; return &singleton; } //------------------------------------------------------------------- using namespace eVaf::Common::Internal; LoggerSource::LoggerSource() : QSharedData() , severity(iLogger::Fatal) , maxSize(100 * 1024) , maxCount(3) {} 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) { 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).toInt(); maxCount = ini.getValue(".default/log_count", maxCount).toInt(); // Default values for this source maxSize = 1024 * ini.getValue(source.toLatin1() + "/log_size", maxSize / 1024).toInt(); maxCount = ini.getValue(source.toLatin1() + "/log_count", maxCount).toInt(); } } //------------------------------------------------------------------- /// 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) { //::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); // Rename the current log file to the backup #0 file dir.rename(baseName, baseName + ".0"); } } } //------------------------------------------------------------------- Logger::Logger() : iLogger() , mReady(false) , mFatalMsgHandler(defFatalMsgHandler) , mConsoleSeverity(iLogger::Fatal) , mThread(0) , mWorker(0) { 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()), 0, 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)), 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(); // 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 (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); mReady = true; write(Info, QString("%1 initialized").arg(objectName()), 0, 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 #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; #ifdef Q_OS_WIN32 va_start(ap, fmt); _vsnprintf_s(str, sizeof(str), _TRUNCATE, fmt, ap); 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