diff --git a/CMakeLists.txt b/CMakeLists.txt index efb5e20db..4ee58e0a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,6 +243,7 @@ if (BUILD_DEBIAN) endif (BUILD_DEBIAN) add_subdirectory(httpserver) +add_subdirectory(logging) add_subdirectory(devices) add_subdirectory(plugins) diff --git a/httpserver/readme.md b/httpserver/readme.md index a1522ecde..41b6788e7 100644 --- a/httpserver/readme.md +++ b/httpserver/readme.md @@ -4,6 +4,7 @@ This is the httpserver part of QtWebApp from Stefan Frings - [Link to the main page](http://stefanfrings.de/qtwebapp/index-en.html) - [Link to API documentation](http://stefanfrings.de/qtwebapp/api/index.html) + - [Link to tutorial](http://stefanfrings.de/qtwebapp/tutorial/index.html) Files copied over from the original 'doc' folder: diff --git a/logging/CMakeLists.txt b/logging/CMakeLists.txt new file mode 100644 index 000000000..38e2cb3de --- /dev/null +++ b/logging/CMakeLists.txt @@ -0,0 +1,38 @@ +project(logging) + +set(logging_SOURCES + dualfilelogger.cpp + filelogger.cpp + logger.cpp + logmessage.cpp +) + +set(httpserver_HEADERS + dualfilelogger.h + filelogger.h + logger.h + logmessage.h + logglobal.h +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_SHARED) + +add_library(logging SHARED + ${logging_SOURCES} + ${logging_HEADERS_MOC} +) + +target_link_libraries(logging + ${QT_LIBRARIES} +) + +qt5_use_modules(logging Core Network) + +install(TARGETS logging DESTINATION lib) diff --git a/logging/dualfilelogger.cpp b/logging/dualfilelogger.cpp new file mode 100644 index 000000000..338b948fa --- /dev/null +++ b/logging/dualfilelogger.cpp @@ -0,0 +1,27 @@ +/** + @file + @author Stefan Frings +*/ + +#include "dualfilelogger.h" + +using namespace qtwebapp; + +DualFileLogger::DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval, QObject* parent) + :Logger(parent) +{ + firstLogger=new FileLogger(firstSettings, refreshInterval, this); + secondLogger=new FileLogger(secondSettings, refreshInterval, this); +} + +void DualFileLogger::log(const QtMsgType type, const QString& message, const QString &file, const QString &function, const int line) +{ + firstLogger->log(type,message,file,function,line); + secondLogger->log(type,message,file,function,line); +} + +void DualFileLogger::clear(const bool buffer, const bool variables) +{ + firstLogger->clear(buffer,variables); + secondLogger->clear(buffer,variables); +} diff --git a/logging/dualfilelogger.h b/logging/dualfilelogger.h new file mode 100644 index 000000000..6fb16c568 --- /dev/null +++ b/logging/dualfilelogger.h @@ -0,0 +1,74 @@ +/** + @file + @author Stefan Frings +*/ + +#ifndef DUALFILELOGGER_H +#define DUALFILELOGGER_H + +#include +#include +#include +#include "logglobal.h" +#include "logger.h" +#include "filelogger.h" + +namespace qtwebapp { + +/** + Logs messages into two log files simultaneously. + May be used to create two logfiles with different configuration settings. + @see FileLogger for a description of the two underlying loggers. +*/ + +class DECLSPEC DualFileLogger : public Logger { + Q_OBJECT + Q_DISABLE_COPY(DualFileLogger) +public: + + /** + Constructor. + @param firstSettings Configuration settings for the first log file, usually stored in an INI file. + Must not be 0. + Settings are read from the current group, so the caller must have called settings->beginGroup(). + Because the group must not change during runtime, it is recommended to provide a + separate QSettings instance to the logger that is not used by other parts of the program. + @param secondSettings Same as firstSettings, but for the second log file. + @param refreshInterval Interval of checking for changed config settings in msec, or 0=disabled + @param parent Parent object. + */ + DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval=10000, QObject *parent = 0); + + /** + Decorate and log the message, if type>=minLevel. + This method is thread safe. + @param type Message type (level) + @param message Message text + @param file Name of the source file where the message was generated (usually filled with the macro __FILE__) + @param function Name of the function where the message was generated (usually filled with the macro __LINE__) + @param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__) + @see LogMessage for a description of the message decoration. + */ + virtual void log(const QtMsgType type, const QString& message, const QString &file="", const QString &function="", const int line=0); + + /** + Clear the thread-local data of the current thread. + This method is thread safe. + @param buffer Whether to clear the backtrace buffer + @param variables Whether to clear the log variables + */ + virtual void clear(const bool buffer=true, const bool variables=true); + +private: + + /** First logger */ + FileLogger* firstLogger; + + /** Second logger */ + FileLogger* secondLogger; + +}; + +} // end of namespace + +#endif // DUALFILELOGGER_H diff --git a/logging/filelogger.cpp b/logging/filelogger.cpp new file mode 100644 index 000000000..9c8300612 --- /dev/null +++ b/logging/filelogger.cpp @@ -0,0 +1,198 @@ +/** + @file + @author Stefan Frings +*/ + +#include "filelogger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace qtwebapp; + +void FileLogger::refreshSettings() +{ + mutex.lock(); + // Save old file name for later comparision with new settings + QString oldFileName=fileName; + + // Load new config settings + settings->sync(); + fileName=settings->value("fileName").toString(); + // Convert relative fileName to absolute, based on the directory of the config file. +#ifdef Q_OS_WIN32 + if (QDir::isRelativePath(fileName) && settings->format()!=QSettings::NativeFormat) +#else + if (QDir::isRelativePath(fileName)) +#endif + { + QFileInfo configFile(settings->fileName()); + fileName=QFileInfo(configFile.absolutePath(),fileName).absoluteFilePath(); + } + maxSize=settings->value("maxSize",0).toLongLong(); + maxBackups=settings->value("maxBackups",0).toInt(); + msgFormat=settings->value("msgFormat","{timestamp} {type} {msg}").toString(); + timestampFormat=settings->value("timestampFormat","yyyy-MM-dd hh:mm:ss.zzz").toString(); + minLevel=static_cast(settings->value("minLevel",0).toInt()); + bufferSize=settings->value("bufferSize",0).toInt(); + + // Create new file if the filename has been changed + if (oldFileName!=fileName) + { + fprintf(stderr,"Logging to %s\n",qPrintable(fileName)); + close(); + open(); + } + mutex.unlock(); +} + + +FileLogger::FileLogger(QSettings* settings, const int refreshInterval, QObject* parent) + : Logger(parent) +{ + Q_ASSERT(settings!=0); + Q_ASSERT(refreshInterval>=0); + this->settings=settings; + file=0; + if (refreshInterval>0) + { + refreshTimer.start(refreshInterval,this); + } + flushTimer.start(1000,this); + refreshSettings(); +} + + +FileLogger::~FileLogger() +{ + close(); +} + + +void FileLogger::write(const LogMessage* logMessage) +{ + // Try to write to the file + if (file) + { + + // Write the message + file->write(qPrintable(logMessage->toString(msgFormat,timestampFormat))); + + // Flush error messages immediately, to ensure that no important message + // gets lost when the program terinates abnormally. + if (logMessage->getType()>=QtCriticalMsg) + { + file->flush(); + } + + // Check for success + if (file->error()) + { + close(); + qWarning("Cannot write to log file %s: %s",qPrintable(fileName),qPrintable(file->errorString())); + } + + } + + // Fall-back to the super class method, if writing failed + if (!file) + { + Logger::write(logMessage); + } + +} + +void FileLogger::open() +{ + if (fileName.isEmpty()) + { + qWarning("Name of logFile is empty"); + } + else { + file=new QFile(fileName); + if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) + { + qWarning("Cannot open log file %s: %s",qPrintable(fileName),qPrintable(file->errorString())); + file=0; + } + } +} + + +void FileLogger::close() +{ + if (file) + { + file->close(); + delete file; + file=0; + } +} + +void FileLogger::rotate() { + // count current number of existing backup files + int count=0; + forever + { + QFile bakFile(QString("%1.%2").arg(fileName).arg(count+1)); + if (bakFile.exists()) + { + ++count; + } + else + { + break; + } + } + + // Remove all old backup files that exceed the maximum number + while (maxBackups>0 && count>=maxBackups) + { + QFile::remove(QString("%1.%2").arg(fileName).arg(count)); + --count; + } + + // Rotate backup files + for (int i=count; i>0; --i) { + QFile::rename(QString("%1.%2").arg(fileName).arg(i),QString("%1.%2").arg(fileName).arg(i+1)); + } + + // Backup the current logfile + QFile::rename(fileName,fileName+".1"); +} + + +void FileLogger::timerEvent(QTimerEvent* event) +{ + if (!event) + { + return; + } + else if (event->timerId()==refreshTimer.timerId()) + { + refreshSettings(); + } + else if (event->timerId()==flushTimer.timerId() && file) + { + mutex.lock(); + + // Flush the I/O buffer + file->flush(); + + // Rotate the file if it is too large + if (maxSize>0 && file->size()>=maxSize) + { + close(); + rotate(); + open(); + } + + mutex.unlock(); + } +} diff --git a/logging/filelogger.h b/logging/filelogger.h new file mode 100644 index 000000000..f195e67a5 --- /dev/null +++ b/logging/filelogger.h @@ -0,0 +1,127 @@ +/** + @file + @author Stefan Frings +*/ + +#ifndef FILELOGGER_H +#define FILELOGGER_H + +#include +#include +#include +#include +#include +#include "logglobal.h" +#include "logger.h" + +namespace qtwebapp { + +/** + Logger that uses a text file for output. Settings are read from a + config file using a QSettings object. Config settings can be changed at runtime. +

+ Example for the configuration settings: +

+  fileName=logs/QtWebApp.log
+  maxSize=1000000
+  maxBackups=2
+  minLevel=0
+  msgformat={timestamp} {typeNr} {type} thread={thread}: {msg}
+  timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
+  bufferSize=0
+  
+ + - fileName is the name of the log file, relative to the directory of the settings file. + In case of windows, if the settings are in the registry, the path is relative to the current + working directory. + - maxSize is the maximum size of that file in bytes. The file will be backed up and + replaced by a new file if it becomes larger than this limit. Please note that + the actual file size may become a little bit larger than this limit. Default is 0=unlimited. + - maxBackups defines the number of backup files to keep. Default is 0=unlimited. + - minLevel defines the minimum type of messages that are written (together with buffered messages) into the file. Defaults is 0=debug. + - msgFormat defines the decoration of log messages, see LogMessage class. Default is "{timestamp} {type} {msg}". + - timestampFormat defines the format of timestamps, see QDateTime::toString(). Default is "yyyy-MM-dd hh:mm:ss.zzz". + - bufferSize defines the size of the buffer. Default is 0=disabled. + + @see set() describes how to set logger variables + @see LogMessage for a description of the message decoration. + @see Logger for a descrition of the buffer. +*/ + +class DECLSPEC FileLogger : public Logger { + Q_OBJECT + Q_DISABLE_COPY(FileLogger) +public: + + /** + Constructor. + @param settings Configuration settings, usually stored in an INI file. Must not be 0. + Settings are read from the current group, so the caller must have called settings->beginGroup(). + Because the group must not change during runtime, it is recommended to provide a + separate QSettings instance to the logger that is not used by other parts of the program. + @param refreshInterval Interval of checking for changed config settings in msec, or 0=disabled + @param parent Parent object + */ + FileLogger(QSettings* settings, const int refreshInterval=10000, QObject* parent = 0); + + /** + Destructor. Closes the file. + */ + virtual ~FileLogger(); + + /** Write a message to the log file */ + virtual void write(const LogMessage* logMessage); + +protected: + + /** + Handler for timer events. + Refreshes config settings or synchronizes I/O buffer, depending on the event. + This method is thread-safe. + @param event used to distinguish between the two timers. + */ + void timerEvent(QTimerEvent* event); + +private: + + /** Configured name of the log file */ + QString fileName; + + /** Configured maximum size of the file in bytes, or 0=unlimited */ + long maxSize; + + /** Configured maximum number of backup files, or 0=unlimited */ + int maxBackups; + + /** Pointer to the configuration settings */ + QSettings* settings; + + /** Output file, or 0=disabled */ + QFile* file; + + /** Timer for refreshing configuration settings */ + QBasicTimer refreshTimer; + + /** Timer for flushing the file I/O buffer */ + QBasicTimer flushTimer; + + /** Open the output file */ + void open(); + + /** Close the output file */ + void close(); + + /** Rotate files and delete some backups if there are too many */ + void rotate(); + + /** + Refreshes the configuration settings. + This method is thread-safe. + */ + void refreshSettings(); + +}; + +} // end of namespace + +#endif // FILELOGGER_H diff --git a/logging/logger.cpp b/logging/logger.cpp new file mode 100644 index 000000000..8e440a3e8 --- /dev/null +++ b/logging/logger.cpp @@ -0,0 +1,192 @@ +/** + @file + @author Stefan Frings +*/ + +#include "logger.h" +#include +#include +#include +#include +#include +#include + +using namespace qtwebapp; + +Logger* Logger::defaultLogger=0; + + +QThreadStorage*> Logger::logVars; + + +QMutex Logger::mutex; + + +Logger::Logger(QObject* parent) + : QObject(parent), + msgFormat("{timestamp} {type} {msg}"), + timestampFormat("dd.MM.yyyy hh:mm:ss.zzz"), + minLevel(QtDebugMsg), + bufferSize(0) + {} + + +Logger::Logger(const QString msgFormat, const QString timestampFormat, const QtMsgType minLevel, const int bufferSize, QObject* parent) + :QObject(parent) +{ + this->msgFormat=msgFormat; + this->timestampFormat=timestampFormat; + this->minLevel=minLevel; + this->bufferSize=bufferSize; +} + + +void Logger::msgHandler(const QtMsgType type, const QString &message, const QString &file, const QString &function, const int line) +{ + static QMutex recursiveMutex(QMutex::Recursive); + static QMutex nonRecursiveMutex(QMutex::NonRecursive); + + // Prevent multiple threads from calling this method simultaneoulsy. + // But allow recursive calls, which is required to prevent a deadlock + // if the logger itself produces an error message. + recursiveMutex.lock(); + + // Fall back to stderr when this method has been called recursively. + if (defaultLogger && nonRecursiveMutex.tryLock()) + { + defaultLogger->log(type, message, file, function, line); + nonRecursiveMutex.unlock(); + } + else + { + fputs(qPrintable(message),stderr); + fflush(stderr); + } + + // Abort the program after logging a fatal message + if (type==QtFatalMsg) + { + abort(); + } + + recursiveMutex.unlock(); +} + + +#if QT_VERSION >= 0x050000 + void Logger::msgHandler5(const QtMsgType type, const QMessageLogContext &context, const QString &message) + { + (void)(context); // suppress "unused parameter" warning + msgHandler(type,message,context.file,context.function,context.line); + } +#else + void Logger::msgHandler4(const QtMsgType type, const char* message) + { + msgHandler(type,message); + } +#endif + + +Logger::~Logger() +{ + if (defaultLogger==this) + { +#if QT_VERSION >= 0x050000 + qInstallMessageHandler(0); +#else + qInstallMsgHandler(0); +#endif + defaultLogger=0; + } +} + + +void Logger::write(const LogMessage* logMessage) +{ + fputs(qPrintable(logMessage->toString(msgFormat,timestampFormat)),stderr); + fflush(stderr); +} + + +void Logger::installMsgHandler() +{ + defaultLogger=this; +#if QT_VERSION >= 0x050000 + qInstallMessageHandler(msgHandler5); +#else + qInstallMsgHandler(msgHandler4); +#endif +} + + +void Logger::set(const QString& name, const QString& value) +{ + mutex.lock(); + if (!logVars.hasLocalData()) + { + logVars.setLocalData(new QHash); + } + logVars.localData()->insert(name,value); + mutex.unlock(); +} + + +void Logger::clear(const bool buffer, const bool variables) +{ + mutex.lock(); + if (buffer && buffers.hasLocalData()) + { + QList* buffer=buffers.localData(); + while (buffer && !buffer->isEmpty()) { + LogMessage* logMessage=buffer->takeLast(); + delete logMessage; + } + } + if (variables && logVars.hasLocalData()) + { + logVars.localData()->clear(); + } + mutex.unlock(); +} + + +void Logger::log(const QtMsgType type, const QString& message, const QString &file, const QString &function, const int line) +{ + mutex.lock(); + + // If the buffer is enabled, write the message into it + if (bufferSize>0) { + // Create new thread local buffer, if necessary + if (!buffers.hasLocalData()) { + buffers.setLocalData(new QList()); + } + QList* buffer=buffers.localData(); + // Append the decorated log message + LogMessage* logMessage=new LogMessage(type,message,logVars.localData(),file,function,line); + buffer->append(logMessage); + // Delete oldest message if the buffer became too large + if (buffer->size()>bufferSize) + { + delete buffer->takeFirst(); + } + // If the type of the message is high enough, print the whole buffer + if (type>=minLevel) { + while (!buffer->isEmpty()) + { + LogMessage* logMessage=buffer->takeFirst(); + write(logMessage); + delete logMessage; + } + } + } + + // Buffer is disabled, print the message if the type is high enough + else { + if (type>=minLevel) + { + LogMessage logMessage(type,message,logVars.localData(),file,function,line); + write(&logMessage); + } + } + mutex.unlock(); +} diff --git a/logging/logger.h b/logging/logger.h new file mode 100644 index 000000000..ea93826cd --- /dev/null +++ b/logging/logger.h @@ -0,0 +1,188 @@ +/** + @file + @author Stefan Frings +*/ + +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include +#include +#include +#include +#include +#include "logglobal.h" +#include "logmessage.h" + +namespace qtwebapp { + +/** + Decorates and writes log messages to the console, stderr. +

+ The decorator uses a predefined msgFormat string to enrich log messages + with additional information (e.g. timestamp). +

+ The msgFormat string and also the message text may contain additional + variable names in the form {name} that are filled by values + taken from a static thread local dictionary. +

+ The logger keeps a configurable number of messages in a ring-buffer. + A log message with a severity >= minLevel flushes the buffer, + so the stored messages get written out. If the buffer is disabled, then + only messages with severity >= minLevel are written out. +

+ If you enable the buffer and use minLevel=2, then the application logs + only errors together with some buffered debug messages. But as long no + error occurs, nothing gets written out. +

+ Each thread has it's own buffer. +

+ The logger can be registered to handle messages from + the static global functions qDebug(), qWarning(), qCritical() and qFatal(). + + @see set() describes how to set logger variables + @see LogMessage for a description of the message decoration. + @warning You should prefer a derived class, for example FileLogger, + because logging to the console is less useful. +*/ + +class DECLSPEC Logger : public QObject { + Q_OBJECT + Q_DISABLE_COPY(Logger) +public: + + /** + Constructor. + Uses the same defaults as the other constructor. + @param parent Parent object + */ + Logger(QObject* parent); + + + /** + Constructor. + @param msgFormat Format of the decoration, e.g. "{timestamp} {type} thread={thread}: {msg}" + @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz" + @param minLevel Minimum severity that genertes an output (0=debug, 1=warning, 2=critical, 3=fatal). + @param bufferSize Size of the backtrace buffer, number of messages per thread. 0=disabled. + @param parent Parent object + @see LogMessage for a description of the message decoration. + */ + Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="dd.MM.yyyy hh:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0); + + /** Destructor */ + virtual ~Logger(); + + /** + Decorate and log the message, if type>=minLevel. + This method is thread safe. + @param type Message type (level) + @param message Message text + @param file Name of the source file where the message was generated (usually filled with the macro __FILE__) + @param function Name of the function where the message was generated (usually filled with the macro __LINE__) + @param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__) + @see LogMessage for a description of the message decoration. + */ + virtual void log(const QtMsgType type, const QString& message, const QString &file="", const QString &function="", const int line=0); + + /** + Installs this logger as the default message handler, so it + can be used through the global static logging functions (e.g. qDebug()). + */ + void installMsgHandler(); + + /** + Sets a thread-local variable that may be used to decorate log messages. + This method is thread safe. + @param name Name of the variable + @param value Value of the variable + */ + static void set(const QString& name, const QString& value); + + /** + Clear the thread-local data of the current thread. + This method is thread safe. + @param buffer Whether to clear the backtrace buffer + @param variables Whether to clear the log variables + */ + virtual void clear(const bool buffer=true, const bool variables=true); + +protected: + + /** Format string for message decoration */ + QString msgFormat; + + /** Format string of timestamps */ + QString timestampFormat; + + /** Minimum level of message types that are written out */ + QtMsgType minLevel; + + /** Size of backtrace buffer, number of messages per thread. 0=disabled */ + int bufferSize; + + /** Used to synchronize access of concurrent threads */ + static QMutex mutex; + + /** + Decorate and write a log message to stderr. Override this method + to provide a different output medium. + */ + virtual void write(const LogMessage* logMessage); + +private: + + /** Pointer to the default logger, used by msgHandler() */ + static Logger* defaultLogger; + + /** + Message Handler for the global static logging functions (e.g. qDebug()). + Forward calls to the default logger. +

+ In case of a fatal message, the program will abort. + Variables in the in the message are replaced by their values. + This method is thread safe. + @param type Message type (level) + @param message Message text + @param file Name of the source file where the message was generated (usually filled with the macro __FILE__) + @param function Name of the function where the message was generated (usually filled with the macro __LINE__) + @param line Line Number of the source file, where the message was generated (usually filles with the macro __func__ or __FUNCTION__) + */ + static void msgHandler(const QtMsgType type, const QString &message, const QString &file="", const QString &function="", const int line=0); + + +#if QT_VERSION >= 0x050000 + + /** + Wrapper for QT version 5. + @param type Message type (level) + @param context Message context + @param message Message text + @see msgHandler() + */ + static void msgHandler5(const QtMsgType type, const QMessageLogContext& context, const QString &message); + +#else + + /** + Wrapper for QT version 4. + @param type Message type (level) + @param message Message text + @see msgHandler() + */ + static void msgHandler4(const QtMsgType type, const char * message); + +#endif + + /** Thread local variables to be used in log messages */ + static QThreadStorage*> logVars; + + /** Thread local backtrace buffers */ + QThreadStorage*> buffers; + +}; + +} // end of namespace + +#endif // LOGGER_H diff --git a/logging/logging.pri b/logging/logging.pri new file mode 100644 index 000000000..f62338d66 --- /dev/null +++ b/logging/logging.pri @@ -0,0 +1,6 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +HEADERS += $$PWD/logglobal.h $$PWD/logmessage.h $$PWD/logger.h $$PWD/filelogger.h $$PWD/dualfilelogger.h + +SOURCES += $$PWD/logmessage.cpp $$PWD/logger.cpp $$PWD/filelogger.cpp $$PWD/dualfilelogger.cpp diff --git a/logging/logglobal.h b/logging/logglobal.h new file mode 100644 index 000000000..5f6261cb3 --- /dev/null +++ b/logging/logglobal.h @@ -0,0 +1,24 @@ +/** + @file + @author Stefan Frings +*/ + +#ifndef LOGGLOBAL_H +#define LOGGLOBAL_H + +#include + +// This is specific to Windows dll's +#if defined(Q_OS_WIN) + #if defined(QTWEBAPPLIB_EXPORT) + #define DECLSPEC Q_DECL_EXPORT + #elif defined(QTWEBAPPLIB_IMPORT) + #define DECLSPEC Q_DECL_IMPORT + #endif +#endif +#if !defined(DECLSPEC) + #define DECLSPEC +#endif + +#endif // LOGGLOBAL_H + diff --git a/logging/logmessage.cpp b/logging/logmessage.cpp new file mode 100644 index 000000000..eecd981b6 --- /dev/null +++ b/logging/logmessage.cpp @@ -0,0 +1,90 @@ +/** + @file + @author Stefan Frings +*/ + +#include "logmessage.h" +#include + +using namespace qtwebapp; + +LogMessage::LogMessage(const QtMsgType type, const QString& message, QHash* logVars, const QString &file, const QString &function, const int line) +{ + this->type=type; + this->message=message; + this->file=file; + this->function=function; + this->line=line; + timestamp=QDateTime::currentDateTime(); + threadId=QThread::currentThreadId(); + + // Copy the logVars if not null, + // so that later changes in the original do not affect the copy + if (logVars) + { + this->logVars=*logVars; + } +} + +QString LogMessage::toString(const QString& msgFormat, const QString& timestampFormat) const +{ + QString decorated=msgFormat+"\n"; + decorated.replace("{msg}",message); + + if (decorated.contains("{timestamp}")) + { + decorated.replace("{timestamp}",timestamp.toString(timestampFormat)); + } + + QString typeNr; + typeNr.setNum(type); + decorated.replace("{typeNr}",typeNr); + + switch (type) + { + case QtDebugMsg: + decorated.replace("{type}","DEBUG "); + break; + case QtWarningMsg: + decorated.replace("{type}","WARNING "); + break; + case QtCriticalMsg: + decorated.replace("{type}","CRITICAL"); + break; + case QtFatalMsg: + decorated.replace("{type}","FATAL "); + break; + #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + case QtInfoMsg: + decorated.replace("{type}","INFO "); + break; + #endif + default: + decorated.replace("{type}",typeNr); + } + + decorated.replace("{file}",file); + decorated.replace("{function}",function); + decorated.replace("{line}",QString::number(line)); + + QString threadId; + threadId.setNum((unsigned long)QThread::currentThreadId()); + decorated.replace("{thread}",threadId); + + // Fill in variables + if (decorated.contains("{") && !logVars.isEmpty()) + { + QList keys=logVars.keys(); + foreach (QString key, keys) + { + decorated.replace("{"+key+"}",logVars.value(key)); + } + } + + return decorated; +} + +QtMsgType LogMessage::getType() const +{ + return type; +} diff --git a/logging/logmessage.h b/logging/logmessage.h new file mode 100644 index 000000000..c84c83b4d --- /dev/null +++ b/logging/logmessage.h @@ -0,0 +1,97 @@ +/** + @file + @author Stefan Frings +*/ + +#ifndef LOGMESSAGE_H +#define LOGMESSAGE_H + +#include +#include +#include +#include "logglobal.h" + +namespace qtwebapp { + +/** + Represents a single log message together with some data + that are used to decorate the log message. + + The following variables may be used in the message and in msgFormat: + + - {timestamp} Date and time of creation + - {typeNr} Type of the message in numeric format (0-3) + - {type} Type of the message in string format (DEBUG, WARNING, CRITICAL, FATAL) + - {thread} ID number of the thread + - {msg} Message text + - {xxx} For any user-defined logger variable + + Plus some new variables since QT 5.0, only filled when compiled in debug mode: + + - {file} Filename where the message was generated + - {function} Function where the message was generated + - {line} Line number where the message was generated +*/ + +class DECLSPEC LogMessage +{ + Q_DISABLE_COPY(LogMessage) +public: + + /** + Constructor. All parameters are copied, so that later changes to them do not + affect this object. + @param type Type of the message + @param message Message text + @param logVars Logger variables, 0 is allowed + @param file Name of the source file where the message was generated + @param function Name of the function where the message was generated + @param line Line Number of the source file, where the message was generated + */ + LogMessage(const QtMsgType type, const QString& message, QHash* logVars, const QString &file, const QString &function, const int line); + + /** + Returns the log message as decorated string. + @param msgFormat Format of the decoration. May contain variables and static text, + e.g. "{timestamp} {type} thread={thread}: {msg}". + @param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz", see QDateTime::toString(). + @see QDatetime for a description of the timestamp format pattern + */ + QString toString(const QString& msgFormat, const QString& timestampFormat) const; + + /** + Get the message type. + */ + QtMsgType getType() const; + +private: + + /** Logger variables */ + QHash logVars; + + /** Date and time of creation */ + QDateTime timestamp; + + /** Type of the message */ + QtMsgType type; + + /** ID number of the thread */ + Qt::HANDLE threadId; + + /** Message text */ + QString message; + + /** Filename where the message was generated */ + QString file; + + /** Function name where the message was generated */ + QString function; + + /** Line number where the message was generated */ + int line; + +}; + +} // end of namespace + +#endif // LOGMESSAGE_H diff --git a/logging/readme.md b/logging/readme.md new file mode 100644 index 000000000..8b6af9220 --- /dev/null +++ b/logging/readme.md @@ -0,0 +1,9 @@ +## QtWebApp logger ## + +This is based on the logging part of QtWebApp from Stefan Frings + + - [Link to the main page](http://stefanfrings.de/qtwebapp/index-en.html) + - [Link to API documentation](http://stefanfrings.de/qtwebapp/api/index.html) + - [Link to tutorial](http://stefanfrings.de/qtwebapp/tutorial/index.html) + +Some changes have been made to support the option of having a console logging plus optional file logging \ No newline at end of file diff --git a/sdrangel.windows.pro b/sdrangel.windows.pro index cdd3d9c49..f15f26287 100644 --- a/sdrangel.windows.pro +++ b/sdrangel.windows.pro @@ -23,6 +23,7 @@ SUBDIRS += devices SUBDIRS += mbelib SUBDIRS += dsdcc SUBDIRS += httpserver +SUBDIRS += logging CONFIG(MINGW64)SUBDIRS += cm256cc SUBDIRS += plugins/samplesource/filesource CONFIG(MINGW64)SUBDIRS += plugins/samplesource/sdrdaemonsource diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index e9e8e4846..b7ecc85d3 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -147,6 +147,7 @@ add_library(sdrgui SHARED include_directories( . ${CMAKE_SOURCE_DIR}/sdrbase + ${CMAKE_SOURCE_DIR}/logging ${CMAKE_CURRENT_BINARY_DIR} ${OPENGL_INCLUDE_DIR} ) @@ -155,6 +156,7 @@ target_link_libraries(sdrgui ${QT_LIBRARIES} ${OPENGL_LIBRARIES} sdrbase + logging ) set_target_properties(sdrgui PROPERTIES DEFINE_SYMBOL "sdrangel_EXPORTS") diff --git a/sdrgui/mainwindow.cpp b/sdrgui/mainwindow.cpp index 26e79b07d..552342956 100644 --- a/sdrgui/mainwindow.cpp +++ b/sdrgui/mainwindow.cpp @@ -50,6 +50,7 @@ #include "plugin/pluginapi.h" #include "gui/glspectrum.h" #include "gui/glspectrumgui.h" +#include "logger.h" #include "mainwindow.h" #include "ui_mainwindow.h" @@ -71,6 +72,9 @@ MainWindow::MainWindow(QWidget* parent) : m_centerFrequency(0), m_sampleFileName(std::string("./test.sdriq")) { + m_logger = new qtwebapp::Logger(this); + m_logger->installMsgHandler(); + qDebug() << "MainWindow::MainWindow: start"; m_instance = this; @@ -202,6 +206,9 @@ MainWindow::~MainWindow() delete m_showSystemWidget; delete ui; + + qDebug() << "MainWindow::~MainWindow: end"; + delete m_logger; } void MainWindow::addSourceDevice() diff --git a/sdrgui/mainwindow.h b/sdrgui/mainwindow.h index e5b37b832..e5ad95c02 100644 --- a/sdrgui/mainwindow.h +++ b/sdrgui/mainwindow.h @@ -50,6 +50,10 @@ class DeviceUISet; class PluginInterface; class QWidget; +namespace qtwebapp { + class Logger; +} + namespace Ui { class MainWindow; } @@ -110,6 +114,8 @@ private: quint64 m_centerFrequency; std::string m_sampleFileName; + qtwebapp::Logger *m_logger; + void loadSettings(); void loadPresetSettings(const Preset* preset, int tabIndex); void savePresetSettings(Preset* preset, int tabIndex);