/** * @file Common/logger.cpp * @brief iLogger interface implementation * @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 "logger.h" #include "iregistry.h" #include "ienv.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() , severity(o.severity) , maxSize(o.maxSize) , maxCount(o.maxCount) {} void LoggerSource::init(QString const & source, QString const & logDir, QString const & etcDir) { Q_UNUSED(etcDir); fileName = logDir + source + ".log"; } //------------------------------------------------------------------- /// 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; #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) { 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