#include "logger.h"
#include "iregistry.h"
#include "ienv.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)
+void eVaf::Common::Internal::defFatalMsgHandler(QString const & msg, QString const & source, QString const & where)
{
Q_UNUSED(source);
//-------------------------------------------------------------------
+using namespace eVaf::Common;
+
+iLogger * iLogger::instance()
+{
+ static Internal::Logger singleton;
+ return &singleton;
+}
+
+
+//-------------------------------------------------------------------
+
+using namespace eVaf::Common::Internal;
+
LoggerSource::LoggerSource()
: QSharedData()
, severity(iLogger::Fatal)
//-------------------------------------------------------------------
+
+/// 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>("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;
+ ::va_start(ap, fmt);
+#ifdef Q_OS_WIN32
+ _vsnprintf_s(str, sizeof(str), _TRUNCATE, fmt, ap);
+#else
+ if (::vasprintf(&str, fmt, ap)); // IF is needed to avoid the compiler warning
+#endif
+ ::va_end(ap);
+
+ 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<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.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
#include "ilogger.h"
#include <QObject>
+#include <QString>
+#include <QHash>
+#include <QExplicitlySharedDataPointer>
+#include <QSharedData>
+
+class QThread;
namespace eVaf {
};
+/**
+ * Worker class for the logger.
+ *
+ * This class separates potentially expensive I/O from the iLogger interface making sure
+ * that writing to the log file does not block the main thread.
+ */
+class LoggerWorker : public QObject
+{
+ Q_OBJECT
+
+public slots:
+
+ /**
+ * Writes a message to the log file
+ * @param src The logger source
+ * @param msg The message
+ *
+ * This function writes the message to the log file. It also controls the size and
+ * number of log files.
+ */
+ void writeToLogFile(LoggerSource const & src, QString const & msg);
+
+};
+
/**
* iLogger interface implementation.
*
virtual void setDefaultSource(QString const & source);
- virtual Severity severity(QString const & source = 0) const;
+ virtual iLogger::Severity severity(QString const & source = 0);
- virtual void setSeverity(Severity severity, QString const & source = 0);
+ virtual void setSeverity(iLogger::Severity severity, QString const & source = 0);
- virtual uint maxSize(QString const & source = 0) const;
+ virtual uint maxSize(QString const & source = 0);
virtual void setMaxSize(uint maxSize, QString const & source = 0);
- virtual uint maxCount(QString const & source = 0) const;
+ virtual uint maxCount(QString const & source = 0);
virtual void setMaxCount(uint maxCount, QString const & source = 0);
- virtual Severity consoleSeverity() const { return mConsoleSeverity; }
+ virtual iLogger::Severity consoleSeverity() const { return mConsoleSeverity; }
- virtual void setConsoleSeverity(Severity severity);
+ virtual void setConsoleSeverity(iLogger::Severity severity);
virtual void write(Severity severity, QString const & msg, QString const & source = 0, QString const & where = 0);
virtual QString printf(char const * const fmt, ...) const;
+ virtual QString printable(QByteArray const & msg) const;
+
virtual FatalMsgHandler installFatalMsgHandler(FatalMsgHandler newHandler);
+signals:
+
+ void writeToLogFile(LoggerSource const & src, QString const & msg);
+
+
private: // Members
/// Current fatal error message handler
FatalMsgHandler mFatalMsgHandler;
+ /// Console output severity level
+ iLogger::Severity mConsoleSeverity;
+
/// Current default source (defaults to "evaf")
QString mDefaultSource;
/// Logger sources
QHash<QString, QExplicitlySharedDataPointer<LoggerSource> > mSources;
+ /// Worker thread
+ QThread * mThread;
-private: // Methods
+ /// Worker object
+ LoggerWorker * mWorker;
- /// Returns the source by the name
- LoggerSource * getSource(QString const & name) const;
- /// Creates a new source
- LoggerSource * addSource(QString const & name);
+private: // Methods
+
+ /// Returns the source by the name. The source is created if it does not exist yet.
+ LoggerSource * getSource(QString const & name);
#ifdef Q_OS_WIN32
/// Changes text colors on the Windows console