Update QsLog to 2.1 snapshot 46b643d5bcbc

This commit is contained in:
Felix Kauselmann 2020-07-24 19:05:01 +02:00
parent c13ec618d0
commit 1568a5f253
45 changed files with 2579 additions and 269 deletions

110
third_party/QsLog/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,110 @@
# CMake build support courtesy of Axel Gembe <axel@gembe.net>
cmake_minimum_required(VERSION 2.8)
project(QsLog)
# Add CMake modules
set(CMAKE_MODULE_PATH
"${QsLog_SOURCE_DIR}/cmake"
"${CMAKE_MODULE_PATH}"
)
include(QsLogConfigTargets)
# Add a _d to debug binaries
set(CMAKE_DEBUG_POSTFIX "_d")
# Qt5
find_package(Qt5Core REQUIRED)
set(CMAKE_AUTOMOC ON)
# As moc files are generated in the binary dir, tell to always look for includes there
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Specify build paths
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${QsLog_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${QsLog_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${QsLog_BINARY_DIR}/bin)
set(HEADER_FILES
QsLog.h
QsLogDest.h
QsLogDestConsole.h
QsLogDestFile.h
QsLogDestFunctor.h
QsLogDisableForThisFile.h
QsLogLevel.h
QsLogMessage.h
QsLogSharedLibrary.h
)
set(SOURCE_FILES
QsLog.cpp
QsLogDest.cpp
QsLogDestConsole.cpp
QsLogDestFile.cpp
QsLogDestFunctor.cpp
QsLogMessage.cpp
QsLogLevel.cpp
)
if(APPLE)
# Apple's compiler will not find standard includes like <thread> or <mutex> with 10.7 target otherwise
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
# Use 10.7 OSX SDK
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7")
option(QS_LOG_LINE_NUMBERS "Automatically writes the file and line for each log message" OFF)
if(QS_LOG_LINE_NUMBERS)
add_definitions(-DQS_LOG_LINE_NUMBERS)
endif()
option(QS_LOG_DISABLE "Logging code is replaced with a no-op" OFF)
if(QS_LOG_DISABLE)
add_definitions(-DQS_LOG_DISABLE)
endif()
option(QS_LOG_SEPARATE_THREAD "Messages are queued and written from a separate thread" OFF)
if(QS_LOG_SEPARATE_THREAD)
add_definitions(-DQS_LOG_SEPARATE_THREAD)
endif()
option(QS_LOG_WIN_PRINTF_CONSOLE "Use fprintf instead of OutputDebugString on Windows" OFF)
if(QS_LOG_WIN_PRINTF_CONSOLE)
add_definitions(-DQS_LOG_WIN_PRINTF_CONSOLE)
endif()
option(QS_LOG_IS_SHARED_LIBRARY "Build shared library" ON)
if(QS_LOG_IS_SHARED_LIBRARY)
set(QS_LOG_LIBRARY_TYPE SHARED)
add_definitions(-DQSLOG_IS_SHARED_LIBRARY)
else(QS_LOG_IS_SHARED_LIBRARY)
set(QS_LOG_LIBRARY_TYPE STATIC)
endif(QS_LOG_IS_SHARED_LIBRARY)
option(QS_LOG_BUILD_WINDOW "Build log window, depends on Qt5::Widgets" OFF)
if(QS_LOG_BUILD_WINDOW)
find_package(Qt5Widgets REQUIRED)
list(APPEND ADDITIONAL_LIBRARIES Qt5::Widgets)
list(APPEND HEADER_FILES QsLogWindowModel.h QsLogWindow.h)
list(APPEND SOURCE_FILES QsLogWindowModel.cpp QsLogWindow.cpp)
list(APPEND UI_FILES QsLogWindow.ui)
list(APPEND RES_FILES QsLogWindow.qrc)
qt5_wrap_ui(UI_SOURCE_FILES ${UI_FILES})
qt5_add_resources(RES_SOURCE_FILES ${RES_FILES})
add_definitions(-DQS_LOG_WINDOW)
endif()
add_library(QsLog ${QS_LOG_LIBRARY_TYPE} ${HEADER_FILES} ${SOURCE_FILES} ${UI_SOURCE_FILES} ${RES_SOURCE_FILES})
target_link_libraries(QsLog Qt5::Core ${ADDITIONAL_LIBRARIES})
install(FILES ${HEADER_FILES} DESTINATION include/QsLog)
QsLog_install_target(QsLog "")
option(QS_LOG_BUILD_TESTS "Build unit tests" OFF)
if(QS_LOG_BUILD_TESTS)
add_subdirectory(unittest)
endif()

24
third_party/QsLog/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,24 @@
Copyright (c) 2010 - 2016 Razvan Petru
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
* The name of the contributors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -26,147 +26,152 @@
#include "QsLog.h" #include "QsLog.h"
#include "QsLogDest.h" #include "QsLogDest.h"
#ifdef QS_LOG_SEPARATE_THREAD #ifdef QS_LOG_SEPARATE_THREAD
#include <QThreadPool> #include <QThread>
#include <QRunnable> #include <QWaitCondition>
#include <queue>
#endif #endif
#include <QMutex> #include <QMutex>
#include <QVector>
#include <QDateTime> #include <QDateTime>
#include <QLatin1String>
#include <QtGlobal> #include <QtGlobal>
#include <cassert>
#include <cstdlib> #include <cstdlib>
#include <stdexcept> #include <stdexcept>
#include <algorithm>
#include <vector>
namespace QsLogging namespace QsLogging
{ {
typedef QVector<DestinationPtr> DestinationList; using DestinationList = std::vector<DestinationPtrU>;
static const char TraceString[] = "TRACE";
static const char DebugString[] = "DEBUG";
static const char InfoString[] = "INFO ";
static const char WarnString[] = "WARN ";
static const char ErrorString[] = "ERROR";
static const char FatalString[] = "FATAL";
// not using Qt::ISODate because we need the milliseconds too
static const QString fmtDateTime("yyyy-MM-ddThh:mm:ss.zzz");
static Logger* sInstance = 0;
static const char* LevelToText(Level theLevel)
{
switch (theLevel) {
case TraceLevel:
return TraceString;
case DebugLevel:
return DebugString;
case InfoLevel:
return InfoString;
case WarnLevel:
return WarnString;
case ErrorLevel:
return ErrorString;
case FatalLevel:
return FatalString;
case OffLevel:
return "";
default: {
assert(!"bad log level");
return InfoString;
}
}
}
#ifdef QS_LOG_SEPARATE_THREAD #ifdef QS_LOG_SEPARATE_THREAD
class LogWriterRunnable : public QRunnable //! Messages can be enqueued from other threads and will be logged one by one.
//! Note: std::queue was used instead of QQueue because it accepts types missing operator=.
class LoggerThread : public QThread
{ {
public: public:
LogWriterRunnable(QString message, Level level); void enqueue(const LogMessage& message)
virtual void run(); {
QMutexLocker locker(&mutex);
messageQueue.push(message);
waitCondition.wakeOne();
}
void requestStop()
{
QMutexLocker locker(&mutex);
requestInterruption();
waitCondition.wakeOne();
}
protected:
virtual void run()
{
while (true) {
QMutexLocker locker(&mutex);
if (messageQueue.empty() && !isInterruptionRequested()) {
waitCondition.wait(&mutex);
}
if (isInterruptionRequested()) {
break;
}
const LogMessage msg = messageQueue.front();
messageQueue.pop();
locker.unlock();
Logger::instance().write(msg);
}
}
private: private:
QString mMessage; QMutex mutex;
Level mLevel; QWaitCondition waitCondition;
std::queue<LogMessage> messageQueue;
}; };
#endif #endif
class LoggerImpl class LoggerImpl
{ {
public: public:
LoggerImpl(); LoggerImpl();
~LoggerImpl();
#ifdef QS_LOG_SEPARATE_THREAD #ifdef QS_LOG_SEPARATE_THREAD
QThreadPool threadPool; bool shutDownLoggerThread();
LoggerThread loggerThread;
#endif #endif
QMutex logMutex; QMutex logMutex;
Level level; Level level;
DestinationList destList; DestinationList destList;
}; };
#ifdef QS_LOG_SEPARATE_THREAD
LogWriterRunnable::LogWriterRunnable(QString message, Level level)
: QRunnable()
, mMessage(message)
, mLevel(level)
{
}
void LogWriterRunnable::run()
{
Logger::instance().write(mMessage, mLevel);
}
#endif
LoggerImpl::LoggerImpl() LoggerImpl::LoggerImpl()
: level(InfoLevel) : level(InfoLevel)
{ {
// assume at least file + console destList.reserve(2); // assume at least file + console
destList.reserve(2);
#ifdef QS_LOG_SEPARATE_THREAD #ifdef QS_LOG_SEPARATE_THREAD
threadPool.setMaxThreadCount(1); loggerThread.start(QThread::LowPriority);
threadPool.setExpiryTimeout(-1);
#endif #endif
} }
LoggerImpl::~LoggerImpl()
Logger::Logger()
: d(new LoggerImpl)
{ {
#ifdef QS_LOG_SEPARATE_THREAD
#if defined(Q_OS_WIN) && defined(QSLOG_IS_SHARED_LIBRARY)
// Waiting on the thread here is too late and can lead to deadlocks. More details:
// * Another reason not to do anything scary in your DllMain:
// https://blogs.msdn.microsoft.com/oldnewthing/20040128-00/?p=40853
// * Dynamic Link libraries best practices:
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn633971%28v=vs.85%29.aspx#general_best_practices
Q_ASSERT(loggerThread.isFinished());
if (!loggerThread.isFinished()) {
qCritical("You must shut down the QsLog thread, otherwise deadlocks can occur!");
}
#endif
shutDownLoggerThread();
#endif
} }
#ifdef QS_LOG_SEPARATE_THREAD
bool LoggerImpl::shutDownLoggerThread()
{
if (loggerThread.isFinished()) {
return true;
}
loggerThread.requestStop();
return loggerThread.wait();
}
#endif
Logger& Logger::instance() Logger& Logger::instance()
{ {
if (!sInstance) static Logger instance;
sInstance = new Logger; return instance;
return *sInstance;
}
void Logger::destroyInstance()
{
delete sInstance;
sInstance = 0;
} }
// tries to extract the level from a string log message. If available, conversionSucceeded will // tries to extract the level from a string log message. If available, conversionSucceeded will
// contain the conversion result. // contain the conversion result.
Level Logger::levelFromLogMessage(const QString& logMessage, bool* conversionSucceeded) Level Logger::levelFromLogMessage(const QString& logMessage, bool* conversionSucceeded)
{ {
using namespace QsLogging;
if (conversionSucceeded) if (conversionSucceeded)
*conversionSucceeded = true; *conversionSucceeded = true;
if (logMessage.startsWith(QLatin1String(TraceString))) if (logMessage.startsWith(QLatin1String(LevelName(TraceLevel))))
return TraceLevel; return TraceLevel;
if (logMessage.startsWith(QLatin1String(DebugString))) if (logMessage.startsWith(QLatin1String(LevelName(DebugLevel))))
return DebugLevel; return DebugLevel;
if (logMessage.startsWith(QLatin1String(InfoString))) if (logMessage.startsWith(QLatin1String(LevelName(InfoLevel))))
return InfoLevel; return InfoLevel;
if (logMessage.startsWith(QLatin1String(WarnString))) if (logMessage.startsWith(QLatin1String(LevelName(WarnLevel))))
return WarnLevel; return WarnLevel;
if (logMessage.startsWith(QLatin1String(ErrorString))) if (logMessage.startsWith(QLatin1String(LevelName(ErrorLevel))))
return ErrorLevel; return ErrorLevel;
if (logMessage.startsWith(QLatin1String(FatalString))) if (logMessage.startsWith(QLatin1String(LevelName(FatalLevel))))
return FatalLevel; return FatalLevel;
if (conversionSucceeded) if (conversionSucceeded)
@ -174,19 +179,54 @@ Level Logger::levelFromLogMessage(const QString& logMessage, bool* conversionSuc
return OffLevel; return OffLevel;
} }
Logger::~Logger() Logger::~Logger() noexcept = default;
#if defined(Q_OS_WIN)
bool Logger::shutDownLoggerThread()
{ {
#ifdef QS_LOG_SEPARATE_THREAD #ifdef QS_LOG_SEPARATE_THREAD
d->threadPool.waitForDone(); return d->shutDownLoggerThread();
#else
return true;
#endif #endif
delete d; }
d = 0; #endif
void Logger::addDestination(DestinationPtrU&& destination)
{
Q_ASSERT(destination.get());
QMutexLocker lock(&d->logMutex);
d->destList.emplace_back(std::move(destination));
} }
void Logger::addDestination(DestinationPtr destination) DestinationPtrU Logger::removeDestination(const QString &type)
{ {
assert(destination.data()); QMutexLocker lock(&d->logMutex);
d->destList.push_back(destination);
const auto it = std::find_if(d->destList.begin(), d->destList.end(), [&type](const DestinationPtrU& dest){
return dest->type() == type;
});
if (it != d->destList.end()) {
auto removed = std::move(*it);
d->destList.erase(it);
return removed;
}
return DestinationPtrU();
}
bool Logger::hasDestinationOfType(const char* type) const
{
QMutexLocker lock(&d->logMutex);
const QLatin1String latin1Type(type);
for (const auto& dest : d->destList) {
if (dest->type() == latin1Type) {
return true;
}
}
return false;
} }
void Logger::setLoggingLevel(Level newLevel) void Logger::setLoggingLevel(Level newLevel)
@ -199,50 +239,36 @@ Level Logger::loggingLevel() const
return d->level; return d->level;
} }
//! creates the complete log message and passes it to the logger Logger::Helper::~Helper() noexcept
void Logger::Helper::writeToLog()
{ {
const char* const levelName = LevelToText(level); const LogMessage msg(buffer, QDateTime::currentDateTimeUtc(), level);
const QString completeMessage(QString("%1 %2 %3") Logger::instance().enqueueWrite(msg);
.arg(levelName)
.arg(QDateTime::currentDateTime().toString(fmtDateTime))
.arg(buffer)
);
Logger::instance().enqueueWrite(completeMessage, level);
} }
Logger::Helper::~Helper()
Logger::Logger()
: d(new LoggerImpl)
{ {
try { qRegisterMetaType<LogMessage>("QsLogging::LogMessage");
writeToLog();
}
catch(std::exception&) {
// you shouldn't throw exceptions from a sink
assert(!"exception in logger helper destructor");
//CHANGED throw;
}
} }
//! directs the message to the task queue or writes it directly //! directs the message to the task queue or writes it directly
void Logger::enqueueWrite(const QString& message, Level level) void Logger::enqueueWrite(const LogMessage& message)
{ {
#ifdef QS_LOG_SEPARATE_THREAD #ifdef QS_LOG_SEPARATE_THREAD
LogWriterRunnable *r = new LogWriterRunnable(message, level); d->loggerThread.enqueue(message);
d->threadPool.start(r);
#else #else
write(message, level); write(message);
#endif #endif
} }
//! Sends the message to all the destinations. The level for this message is passed in case //! Sends the message to all the destinations. The level for this message is passed in case
//! it's useful for processing in the destination. //! it's useful for processing in the destination.
void Logger::write(const QString& message, Level level) void Logger::write(const LogMessage& message)
{ {
QMutexLocker lock(&d->logMutex); QMutexLocker lock(&d->logMutex);
for (DestinationList::iterator it = d->destList.begin(), for (auto& dest : d->destList) {
endIt = d->destList.end();it != endIt;++it) { dest->write(message);
(*it)->write(message, level);
} }
} }

View File

@ -27,30 +27,55 @@
#define QSLOG_H #define QSLOG_H
#include "QsLogLevel.h" #include "QsLogLevel.h"
#include "QsLogDest.h" #include "QsLogMessage.h"
#include "QsLogSharedLibrary.h"
#include <QDebug> #include <QDebug>
#include <QString> #include <QString>
#include <memory>
#define QS_LOG_VERSION "2.0b3" #define QS_LOG_VERSION "2.1"
namespace QsLogging namespace QsLogging
{ {
class Destination; class Destination;
class LoggerImpl; // d pointer class LoggerImpl; // d pointer
using DestinationPtrU = std::unique_ptr<Destination>;
class QSLOG_SHARED_OBJECT Logger class QSLOG_SHARED_OBJECT Logger
{ {
public: public:
static Logger& instance(); static Logger& instance();
static void destroyInstance();
static Level levelFromLogMessage(const QString& logMessage, bool* conversionSucceeded = 0); static Level levelFromLogMessage(const QString& logMessage, bool* conversionSucceeded = 0);
~Logger(); public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
~Logger() noexcept;
#if defined(Q_OS_WIN)
//! When QS_LOG_SEPARATE_THREAD is defined on Windows, and you are using this library as a DLL,
//! this function must be called before your program ends, to ensure a clean shutdown of the logger thread.
//! Failing to call it will result in an assert being triggered, an error message being printed
//! out and most probably a deadlock.
//! Returns the wait result for the thread. When called on a non-threaded logger returns true
//! immediately.
bool shutDownLoggerThread();
#endif
//! Adds a log message destination. Don't add null destinations. //! Adds a log message destination. Don't add null destinations.
void addDestination(DestinationPtr destination); void addDestination(DestinationPtrU &&destination);
//! Logging at a level < 'newLevel' will be ignored
//! Removes and returns a previously added destination. Returns null if not found.
DestinationPtrU removeDestination(const QString& type);
//! Checks if a destination of a specific type has been added. Pass T::Type as parameter.
bool hasDestinationOfType(const char* type) const;
//! Messages at a level < 'newLevel' will be ignored
void setLoggingLevel(Level newLevel); void setLoggingLevel(Level newLevel);
//! The default level is INFO //! The default level is INFO
Level loggingLevel() const; Level loggingLevel() const;
@ -62,12 +87,10 @@ public:
explicit Helper(Level logLevel) : explicit Helper(Level logLevel) :
level(logLevel), level(logLevel),
qtDebug(&buffer) {} qtDebug(&buffer) {}
~Helper(); ~Helper() noexcept;
QDebug& stream(){ return qtDebug; } QDebug& stream(){ return qtDebug; }
private: private:
void writeToLog();
Level level; Level level;
QString buffer; QString buffer;
QDebug qtDebug; QDebug qtDebug;
@ -75,15 +98,13 @@ public:
private: private:
Logger(); Logger();
Logger(const Logger&); // not available
Logger& operator=(const Logger&); // not available
void enqueueWrite(const QString& message, Level level); void enqueueWrite(const LogMessage& message);
void write(const QString& message, Level level); void write(const LogMessage& message);
LoggerImpl* d; std::unique_ptr<LoggerImpl> d;
friend class LogWriterRunnable; friend class LoggerThread;
}; };
} // end namespace } // end namespace

View File

@ -1,22 +1,41 @@
INCLUDEPATH += $$PWD INCLUDEPATH += $$PWD
#DEFINES += QS_LOG_LINE_NUMBERS # automatically writes the file and line for each log message #DEFINES += QS_LOG_LINE_NUMBERS # automatically writes the file and line for each log message
#DEFINES += QS_LOG_DISABLE # logging code is replaced with a no-op #DEFINES += QS_LOG_DISABLE # logging code is replaced with a no-op
#DEFINES += QS_LOG_SEPARATE_THREAD # messages are queued and written from a separate thread #DEFINES += QS_LOG_SEPARATE_THREAD # messages are queued and written from a separate thread
#DEFINES += QS_LOG_WIN_PRINTF_CONSOLE # Use fprintf instead of OutputDebugString on Windows
#DEFINES += QS_LOG_WINDOW # allows easily showing log messages in a UI
contains(DEFINES, QS_LOG_WINDOW) {
message("Will include log window destination")
QT += gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
SOURCES += $$PWD/QsLogWindow.cpp \
$$PWD/QsLogWindowModel.cpp
HEADERS += $$PWD/QsLogWindow.h \
$$PWD/QsLogWindowModel.h
FORMS += $$PWD/QsLogWindow.ui
RESOURCES += $$PWD/QsLogWindow.qrc
}
SOURCES += $$PWD/QsLogDest.cpp \ SOURCES += $$PWD/QsLogDest.cpp \
$$PWD/QsLog.cpp \ $$PWD/QsLog.cpp \
$$PWD/QsLogDestConsole.cpp \ $$PWD/QsLogDestConsole.cpp \
$$PWD/QsLogDestFile.cpp \ $$PWD/QsLogDestFile.cpp \
$$PWD/QsLogDestFunctor.cpp $$PWD/QsLogDestFunctor.cpp \
$$PWD/QsLogMessage.cpp \
$$PWD/QsLogLevel.cpp
HEADERS += $$PWD/QSLogDest.h \ HEADERS += $$PWD/QsLogDest.h \
$$PWD/QsLog.h \ $$PWD/QsLog.h \
$$PWD/QsLogDestConsole.h \ $$PWD/QsLogDestConsole.h \
$$PWD/QsLogLevel.h \ $$PWD/QsLogLevel.h \
$$PWD/QsLogDestFile.h \ $$PWD/QsLogDestFile.h \
$$PWD/QsLogDisableForThisFile.h \ $$PWD/QsLogDisableForThisFile.h \
$$PWD/QsLogDestFunctor.h $$PWD/QsLogDestFunctor.h \
$$PWD/QsLogMessage.h \
$$PWD/QsLogSharedLibrary.h
OTHER_FILES += \ OTHER_FILES += \
$$PWD/QsLogChanges.txt \ $$PWD/QsLogChanges.txt \
$$PWD/QsLogReadme.txt \ $$PWD/README.md \
$$PWD/LICENSE.txt $$PWD/LICENSE.txt

69
third_party/QsLog/QsLogChanges.txt vendored Normal file
View File

@ -0,0 +1,69 @@
-------------------
QsLog version 2.1
Changes:
* Allowed checking if a destination of a certain type has already been added.
* Allowed using fprintf instead of OutputDebugString on Windows.
* Made building/running the example from QtCreator easier. Unfortunately I couldn't find a
satisfactory way of doing this using pure qmake, so an env var is needed.
* Added support for CMake builds (Qt5 only, courtesy of A.Gembe).
Fixes:
* Fixed build for Qt < 5.4 (courtesy of P.Hille).
-------------------
QsLog version 2.0
Changes:
* Added possibility to remove destinations.
* Added unit tests for rotation and destination removal.
Fixes:
* Fixed rare codec error in combination with custom ICU.
-------------------
QsLog version 2.0b4
Fixes:
Fixed file name case error for Linux.
Misc:
* Moved to separate git repository.
-------------------
QsLog version 2.0b3
Changes:
* added functor logging destination (thanks to Omar Carey for providing a patch)
-------------------
QsLog version 2.0b2
Changes:
* function signature redefinition for file destination creation
* added shared library build configuration. Credit goes to Xavier Lamien <laxathom@fedoraproject.org>
for the Linux-specific config.
* removed Symbian support
* this version does not guarantee support for Nokia N9 devices, although it should in theory still
work with them
* workaround for Issue 8 - when used from an executable and a plugin QsLog only logs from the
executable.
* utility function to parse the level from an existing log message
Known issues:
* The current version will crash at program exit when using QS_LOG_SEPARATE_THREAD and linking
with QsLog dynamically.
-------------------
QsLog version 2.0b1
Changes:
* destination pointers use shared pointer semantics
* the file destination supports log rotation. As a consequence, log files are encoded as UTF-8 and
the log appends rather than truncating on every startup when rotation is enabled.
* added the posibility of disabling logging either at run time or compile time.
* added the possibility of using a separate thread for writing to the log destinations.
Fixes:
* renamed the main.cpp example to avoid QtCreator confusion
* offer a way to check if the destination creation has failed (for e.g files)

View File

@ -32,39 +32,37 @@
namespace QsLogging namespace QsLogging
{ {
Destination::~Destination() Destination::~Destination() noexcept = default;
{
}
//! destination factory //! destination factory
DestinationPtr DestinationFactory::MakeFileDestination(const QString& filePath, DestinationPtrU DestinationFactory::MakeFileDestination(const QString& filePath,
LogRotationOption rotation, const MaxSizeBytes &sizeInBytesToRotateAfter, LogRotationOption rotation, const MaxSizeBytes &sizeInBytesToRotateAfter,
const MaxOldLogCount &oldLogsToKeep) const MaxOldLogCount &oldLogsToKeep)
{ {
if (EnableLogRotation == rotation) { if (LogRotationOption::EnableLogRotation == rotation) {
QScopedPointer<SizeRotationStrategy> logRotation(new SizeRotationStrategy); std::unique_ptr<SizeRotationStrategy> logRotation(new SizeRotationStrategy);
logRotation->setMaximumSizeInBytes(sizeInBytesToRotateAfter.size); logRotation->setMaximumSizeInBytes(sizeInBytesToRotateAfter.size);
logRotation->setBackupCount(oldLogsToKeep.count); logRotation->setBackupCount(oldLogsToKeep.count);
return DestinationPtr(new FileDestination(filePath, RotationStrategyPtr(logRotation.take()))); return DestinationPtrU(new FileDestination(filePath, std::move(logRotation)));
} }
return DestinationPtr(new FileDestination(filePath, RotationStrategyPtr(new NullRotationStrategy))); return DestinationPtrU(new FileDestination(filePath, RotationStrategyPtrU(new NullRotationStrategy)));
} }
DestinationPtr DestinationFactory::MakeDebugOutputDestination() DestinationPtrU DestinationFactory::MakeDebugOutputDestination()
{ {
return DestinationPtr(new DebugOutputDestination); return DestinationPtrU(new DebugOutputDestination);
} }
DestinationPtr DestinationFactory::MakeFunctorDestination(QsLogging::Destination::LogFunction f) DestinationPtrU DestinationFactory::MakeFunctorDestination(QsLogging::Destination::LogFunction f)
{ {
return DestinationPtr(new FunctorDestination(f)); return DestinationPtrU(new FunctorDestination(f));
} }
DestinationPtr DestinationFactory::MakeFunctorDestination(QObject *receiver, const char *member) DestinationPtrU DestinationFactory::MakeFunctorDestination(QObject *receiver, const char *member)
{ {
return DestinationPtr(new FunctorDestination(receiver, member)); return DestinationPtrU(new FunctorDestination(receiver, member));
} }
} // end namespace } // end namespace

View File

@ -27,37 +27,44 @@
#define QSLOGDEST_H #define QSLOGDEST_H
#include "QsLogLevel.h" #include "QsLogLevel.h"
#include <QSharedPointer> #include "QsLogMessage.h"
#include "QsLogSharedLibrary.h"
#include <QtGlobal> #include <QtGlobal>
#include <limits>
#include <memory>
#include <functional>
class QString; class QString;
class QObject; class QObject;
#ifdef QSLOG_IS_SHARED_LIBRARY
#define QSLOG_SHARED_OBJECT Q_DECL_EXPORT
#elif QSLOG_IS_SHARED_LIBRARY_IMPORT
#define QSLOG_SHARED_OBJECT Q_DECL_IMPORT
#else
#define QSLOG_SHARED_OBJECT
#endif
namespace QsLogging namespace QsLogging
{ {
class QSLOG_SHARED_OBJECT Destination class QSLOG_SHARED_OBJECT Destination
{ {
public: public:
typedef void (*LogFunction)(const QString &message, Level level); using LogFunction = std::function<void(const LogMessage& message)>;
public: public:
virtual ~Destination(); virtual ~Destination() noexcept;
virtual void write(const QString& message, Level level) = 0; virtual void write(const LogMessage& message) = 0;
virtual bool isValid() = 0; // returns whether the destination was created correctly //!
//! \brief isValid
//! \return whether the destination was created correctly
//!
virtual bool isValid() = 0;
//!
//! \brief type
//! \return the type as a string e.g: console, file.
//! The returned value may change in different versions of QsLog, but two destinations
//! of the same type will return the same value.
//!
virtual QString type() const = 0;
}; };
typedef QSharedPointer<Destination> DestinationPtr;
using DestinationPtrU = std::unique_ptr<Destination>;
// a series of "named" paramaters, to make the file destination creation more readable // a series of "named" paramaters, to make the file destination creation more readable
enum LogRotationOption enum class LogRotationOption
{ {
DisableLogRotation = 0, DisableLogRotation = 0,
EnableLogRotation = 1 EnableLogRotation = 1
@ -78,20 +85,20 @@ struct QSLOG_SHARED_OBJECT MaxOldLogCount
}; };
//! Creates logging destinations/sinks. The caller shares ownership of the destinations with the logger. //! Creates logging destinations/sinks. The caller takes ownership of the destinations from the
//! After being added to a logger, the caller can discard the pointers. //! factory and will pass ownership to the logger when adding the destination.
class QSLOG_SHARED_OBJECT DestinationFactory class QSLOG_SHARED_OBJECT DestinationFactory
{ {
public: public:
static DestinationPtr MakeFileDestination(const QString& filePath, static DestinationPtrU MakeFileDestination(const QString& filePath,
LogRotationOption rotation = DisableLogRotation, LogRotationOption rotation = LogRotationOption::DisableLogRotation,
const MaxSizeBytes &sizeInBytesToRotateAfter = MaxSizeBytes(), const MaxSizeBytes &sizeInBytesToRotateAfter = MaxSizeBytes(),
const MaxOldLogCount &oldLogsToKeep = MaxOldLogCount()); const MaxOldLogCount &oldLogsToKeep = MaxOldLogCount());
static DestinationPtr MakeDebugOutputDestination(); static DestinationPtrU MakeDebugOutputDestination();
// takes a pointer to a function // takes a pointer to a function
static DestinationPtr MakeFunctorDestination(Destination::LogFunction f); static DestinationPtrU MakeFunctorDestination(Destination::LogFunction f);
// takes a QObject + signal/slot // takes a QObject + signal/slot
static DestinationPtr MakeFunctorDestination(QObject *receiver, const char *member); static DestinationPtrU MakeFunctorDestination(QObject* receiver, const char* member);
}; };
} // end namespace } // end namespace

View File

@ -27,29 +27,36 @@
#include <QString> #include <QString>
#include <QtGlobal> #include <QtGlobal>
#if defined(Q_OS_WIN) #if defined(Q_OS_UNIX) || defined(Q_OS_WIN) && defined(QS_LOG_WIN_PRINTF_CONSOLE)
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
void QsDebugOutput::output( const QString& message )
{
OutputDebugStringW(reinterpret_cast<const WCHAR*>(message.utf16()));
OutputDebugStringW(L"\n");
}
#elif defined(Q_OS_UNIX)
#include <cstdio> #include <cstdio>
void QsDebugOutput::output( const QString& message ) void QsDebugOutput::output( const QString& message )
{ {
fprintf(stderr, "%s\n", qPrintable(message)); fprintf(stderr, "%s\n", qPrintable(message));
fflush(stderr); fflush(stderr);
} }
#elif defined(Q_OS_WIN)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void QsDebugOutput::output( const QString& message )
{
OutputDebugStringW(reinterpret_cast<const WCHAR*>(message.utf16()));
OutputDebugStringW(L"\n");
}
#endif #endif
void QsLogging::DebugOutputDestination::write(const QString& message, Level) const char* const QsLogging::DebugOutputDestination::Type = "console";
void QsLogging::DebugOutputDestination::write(const LogMessage& message)
{ {
QsDebugOutput::output(message); QsDebugOutput::output(message.formatted);
} }
bool QsLogging::DebugOutputDestination::isValid() bool QsLogging::DebugOutputDestination::isValid()
{ {
return true; return true;
} }
QString QsLogging::DebugOutputDestination::type() const
{
return QString::fromLatin1(Type);
}

View File

@ -27,7 +27,6 @@
#define QSLOGDESTCONSOLE_H #define QSLOGDESTCONSOLE_H
#include "QsLogDest.h" #include "QsLogDest.h"
class QString; class QString;
class QsDebugOutput class QsDebugOutput
@ -40,11 +39,14 @@ namespace QsLogging
{ {
// debugger sink // debugger sink
class DebugOutputDestination : public Destination class QSLOG_SHARED_OBJECT DebugOutputDestination : public Destination
{ {
public: public:
virtual void write(const QString& message, Level level); static const char* const Type;
virtual bool isValid();
void write(const LogMessage& message) override;
bool isValid() override;
QString type() const override;
}; };
} }

View File

@ -26,21 +26,13 @@
#include "QsLogDestFile.h" #include "QsLogDestFile.h"
#include <QTextCodec> #include <QTextCodec>
#include <QDateTime> #include <QDateTime>
#include <QString>
#include <QtGlobal> #include <QtGlobal>
#include <iostream> #include <iostream>
const int QsLogging::SizeRotationStrategy::MaxBackupCount = 10; const int QsLogging::SizeRotationStrategy::MaxBackupCount = 10;
QsLogging::RotationStrategy::~RotationStrategy() QsLogging::RotationStrategy::~RotationStrategy() noexcept = default;
{
}
QsLogging::SizeRotationStrategy::SizeRotationStrategy()
: mCurrentSizeInBytes(0)
, mMaxSizeInBytes(0)
, mBackupsCount(0)
{
}
void QsLogging::SizeRotationStrategy::setInitialInfo(const QFile &file) void QsLogging::SizeRotationStrategy::setInitialInfo(const QFile &file)
{ {
@ -48,9 +40,20 @@ void QsLogging::SizeRotationStrategy::setInitialInfo(const QFile &file)
mCurrentSizeInBytes = file.size(); mCurrentSizeInBytes = file.size();
} }
void QsLogging::SizeRotationStrategy::setInitialInfo(const QString &filePath, int fileSize)
{
mFileName = filePath;
mCurrentSizeInBytes = fileSize;
}
void QsLogging::SizeRotationStrategy::includeMessageInCalculation(const QString &message) void QsLogging::SizeRotationStrategy::includeMessageInCalculation(const QString &message)
{ {
mCurrentSizeInBytes += message.toUtf8().size(); includeMessageInCalculation(message.toUtf8());
}
void QsLogging::SizeRotationStrategy::includeMessageInCalculation(const QByteArray &message)
{
mCurrentSizeInBytes += message.size();
} }
bool QsLogging::SizeRotationStrategy::shouldRotate() bool QsLogging::SizeRotationStrategy::shouldRotate()
@ -63,8 +66,9 @@ bool QsLogging::SizeRotationStrategy::shouldRotate()
void QsLogging::SizeRotationStrategy::rotate() void QsLogging::SizeRotationStrategy::rotate()
{ {
if (!mBackupsCount) { if (!mBackupsCount) {
if (!QFile::remove(mFileName)) if (!removeFileAtPath(mFileName)) {
std::cerr << "QsLog: backup delete failed " << qPrintable(mFileName); std::cerr << "QsLog: backup delete failed " << qPrintable(mFileName);
}
return; return;
} }
@ -73,18 +77,19 @@ void QsLogging::SizeRotationStrategy::rotate()
int lastExistingBackupIndex = 0; int lastExistingBackupIndex = 0;
for (int i = 1;i <= mBackupsCount;++i) { for (int i = 1;i <= mBackupsCount;++i) {
const QString backupFileName = logNamePattern.arg(i); const QString backupFileName = logNamePattern.arg(i);
if (QFile::exists(backupFileName)) if (fileExistsAtPath(backupFileName)) {
lastExistingBackupIndex = qMin(i, mBackupsCount - 1); lastExistingBackupIndex = qMin(i, mBackupsCount - 1);
else } else {
break; break;
}
} }
// 2. shift up // 2. shift up
for (int i = lastExistingBackupIndex;i >= 1;--i) { for (int i = lastExistingBackupIndex;i >= 1;--i) {
const QString oldName = logNamePattern.arg(i); const QString oldName = logNamePattern.arg(i);
const QString newName = logNamePattern.arg(i + 1); const QString newName = logNamePattern.arg(i + 1);
QFile::remove(newName); removeFileAtPath(newName);
const bool renamed = QFile::rename(oldName, newName); const bool renamed = renameFileFromTo(oldName, newName);
if (!renamed) { if (!renamed) {
std::cerr << "QsLog: could not rename backup " << qPrintable(oldName) std::cerr << "QsLog: could not rename backup " << qPrintable(oldName)
<< " to " << qPrintable(newName); << " to " << qPrintable(newName);
@ -93,9 +98,10 @@ void QsLogging::SizeRotationStrategy::rotate()
// 3. rename current log file // 3. rename current log file
const QString newName = logNamePattern.arg(1); const QString newName = logNamePattern.arg(1);
if (QFile::exists(newName)) if (fileExistsAtPath(newName)) {
QFile::remove(newName); removeFileAtPath(newName);
if (!QFile::rename(mFileName, newName)) { }
if (!renameFileFromTo(mFileName, newName)) {
std::cerr << "QsLog: could not rename log " << qPrintable(mFileName) std::cerr << "QsLog: could not rename log " << qPrintable(mFileName)
<< " to " << qPrintable(newName); << " to " << qPrintable(newName);
} }
@ -118,33 +124,53 @@ void QsLogging::SizeRotationStrategy::setBackupCount(int backups)
mBackupsCount = qMin(backups, SizeRotationStrategy::MaxBackupCount); mBackupsCount = qMin(backups, SizeRotationStrategy::MaxBackupCount);
} }
bool QsLogging::SizeRotationStrategy::removeFileAtPath(const QString &path)
{
return QFile::remove(path);
}
QsLogging::FileDestination::FileDestination(const QString& filePath, RotationStrategyPtr rotationStrategy) bool QsLogging::SizeRotationStrategy::fileExistsAtPath(const QString &path)
: mRotationStrategy(rotationStrategy) {
return QFile::exists(path);
}
bool QsLogging::SizeRotationStrategy::renameFileFromTo(const QString &from, const QString &to)
{
return QFile::rename(from, to);
}
const char* const QsLogging::FileDestination::Type = "file";
QsLogging::FileDestination::FileDestination(const QString& filePath, RotationStrategyPtrU&& rotationStrategy)
: mRotationStrategy(std::move(rotationStrategy))
{ {
mFile.setFileName(filePath); mFile.setFileName(filePath);
if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) {
std::cerr << "QsLog: could not open log file " << qPrintable(filePath); std::cerr << "QsLog: could not open log file " << qPrintable(filePath);
}
mOutputStream.setDevice(&mFile); mOutputStream.setDevice(&mFile);
mOutputStream.setCodec(QTextCodec::codecForName("UTF-8")); mOutputStream.setCodec(QTextCodec::codecForName("UTF-8"));
mRotationStrategy->setInitialInfo(mFile); mRotationStrategy->setInitialInfo(mFile);
} }
void QsLogging::FileDestination::write(const QString& message, Level) void QsLogging::FileDestination::write(const LogMessage& message)
{ {
mRotationStrategy->includeMessageInCalculation(message); const QByteArray utf8Message = message.formatted.toUtf8();
mRotationStrategy->includeMessageInCalculation(utf8Message);
if (mRotationStrategy->shouldRotate()) { if (mRotationStrategy->shouldRotate()) {
mOutputStream.setDevice(NULL); mOutputStream.setDevice(nullptr);
mFile.close(); mFile.close();
mRotationStrategy->rotate(); mRotationStrategy->rotate();
if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) {
std::cerr << "QsLog: could not reopen log file " << qPrintable(mFile.fileName()); std::cerr << "QsLog: could not reopen log file " << qPrintable(mFile.fileName());
}
mRotationStrategy->setInitialInfo(mFile); mRotationStrategy->setInitialInfo(mFile);
mOutputStream.setDevice(&mFile); mOutputStream.setDevice(&mFile);
mOutputStream.setCodec(QTextCodec::codecForName("UTF-8"));
} }
mOutputStream << message << endl; mOutputStream << utf8Message << endl;
mOutputStream.flush(); mOutputStream.flush();
} }
@ -153,3 +179,8 @@ bool QsLogging::FileDestination::isValid()
return mFile.isOpen(); return mFile.isOpen();
} }
QString QsLogging::FileDestination::type() const
{
return QString::fromLatin1(Type);
}

View File

@ -29,71 +29,92 @@
#include "QsLogDest.h" #include "QsLogDest.h"
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
#include <QString>
#include <QByteArray>
#include <QtGlobal> #include <QtGlobal>
#include <QSharedPointer> #include <memory>
namespace QsLogging namespace QsLogging
{ {
class RotationStrategy class QSLOG_SHARED_OBJECT RotationStrategy
{ {
public: public:
virtual ~RotationStrategy(); virtual ~RotationStrategy() noexcept;
virtual void setInitialInfo(const QFile &file) = 0; virtual void setInitialInfo(const QFile &file) = 0;
virtual void includeMessageInCalculation(const QString &message) = 0; virtual void includeMessageInCalculation(const QString &message) = 0;
virtual void includeMessageInCalculation(const QByteArray &message) = 0;
virtual bool shouldRotate() = 0; virtual bool shouldRotate() = 0;
virtual void rotate() = 0; virtual void rotate() = 0;
virtual QIODevice::OpenMode recommendedOpenModeFlag() = 0; virtual QIODevice::OpenMode recommendedOpenModeFlag() = 0;
}; };
// Never rotates file, overwrites existing file. // Never rotates file, overwrites existing file.
class NullRotationStrategy : public RotationStrategy class QSLOG_SHARED_OBJECT NullRotationStrategy : public RotationStrategy
{ {
public: public:
virtual void setInitialInfo(const QFile &) {} void setInitialInfo(const QFile &) override {}
virtual void includeMessageInCalculation(const QString &) {} void includeMessageInCalculation(const QString &) override {}
virtual bool shouldRotate() { return false; } void includeMessageInCalculation(const QByteArray &) override {}
virtual void rotate() {} bool shouldRotate() override
virtual QIODevice::OpenMode recommendedOpenModeFlag() { return QIODevice::Truncate; } {
return false;
}
void rotate() override {}
QIODevice::OpenMode recommendedOpenModeFlag() override
{
return QIODevice::Truncate;
}
}; };
// Rotates after a size is reached, keeps a number of <= 10 backups, appends to existing file. // Rotates after a size is reached, keeps a number of <= 10 backups, appends to existing file.
class SizeRotationStrategy : public RotationStrategy class QSLOG_SHARED_OBJECT SizeRotationStrategy : public RotationStrategy
{ {
public: public:
SizeRotationStrategy(); SizeRotationStrategy() = default;
static const int MaxBackupCount; static const int MaxBackupCount;
virtual void setInitialInfo(const QFile &file); void setInitialInfo(const QFile &file) override;
virtual void includeMessageInCalculation(const QString &message); void setInitialInfo(const QString& filePath, int fileSize);
virtual bool shouldRotate(); void includeMessageInCalculation(const QString &message) override;
virtual void rotate(); void includeMessageInCalculation(const QByteArray &message) override;
virtual QIODevice::OpenMode recommendedOpenModeFlag(); bool shouldRotate() override;
void rotate() override;
QIODevice::OpenMode recommendedOpenModeFlag() override;
void setMaximumSizeInBytes(qint64 size); void setMaximumSizeInBytes(qint64 size);
void setBackupCount(int backups); void setBackupCount(int backups);
protected:
// can be overridden for testing
virtual bool removeFileAtPath(const QString& path);
virtual bool fileExistsAtPath(const QString& path);
virtual bool renameFileFromTo(const QString& from, const QString& to);
private: private:
QString mFileName; QString mFileName;
qint64 mCurrentSizeInBytes; qint64 mCurrentSizeInBytes{0};
qint64 mMaxSizeInBytes; qint64 mMaxSizeInBytes{0};
int mBackupsCount; int mBackupsCount{0};
}; };
typedef QSharedPointer<RotationStrategy> RotationStrategyPtr; using RotationStrategyPtrU = std::unique_ptr<RotationStrategy>;
// file message sink // file message sink
class FileDestination : public Destination class QSLOG_SHARED_OBJECT FileDestination : public Destination
{ {
public: public:
FileDestination(const QString& filePath, RotationStrategyPtr rotationStrategy); static const char* const Type;
virtual void write(const QString& message, Level level);
virtual bool isValid(); FileDestination(const QString& filePath, RotationStrategyPtrU &&rotationStrategy);
void write(const LogMessage& message) override;
bool isValid() override;
QString type() const override;
private: private:
QFile mFile; QFile mFile;
QTextStream mOutputStream; QTextStream mOutputStream;
QSharedPointer<RotationStrategy> mRotationStrategy; RotationStrategyPtrU mRotationStrategy;
}; };
} }

View File

@ -27,31 +27,40 @@
#include "QsLogDestFunctor.h" #include "QsLogDestFunctor.h"
#include <cstddef> #include <cstddef>
#include <QtGlobal> #include <QtGlobal>
#include <QString>
const char* const QsLogging::FunctorDestination::Type = "functor";
QsLogging::FunctorDestination::FunctorDestination(LogFunction f) QsLogging::FunctorDestination::FunctorDestination(LogFunction f)
: QObject(NULL) : QObject(nullptr)
, mLogFunction(f) , mLogFunction(f)
{ {
} }
QsLogging::FunctorDestination::FunctorDestination(QObject *receiver, const char *member) QsLogging::FunctorDestination::FunctorDestination(QObject* receiver, const char* member)
: QObject(NULL) : QObject(nullptr)
, mLogFunction(NULL) , mLogFunction(nullptr)
{ {
connect(this, SIGNAL(logMessageReady(QString,int)), receiver, member, Qt::QueuedConnection); connect(this, SIGNAL(logMessageReady(QsLogging::LogMessage)),
receiver, member, Qt::QueuedConnection);
} }
void QsLogging::FunctorDestination::write(const QString &message, QsLogging::Level level) void QsLogging::FunctorDestination::write(const LogMessage& message)
{ {
if (mLogFunction) if (mLogFunction)
mLogFunction(message, level); mLogFunction(message);
if (level > QsLogging::TraceLevel) if (message.level > QsLogging::TraceLevel)
emit logMessageReady(message, static_cast<int>(level)); emit logMessageReady(message);
} }
bool QsLogging::FunctorDestination::isValid() bool QsLogging::FunctorDestination::isValid()
{ {
return true; return true;
} }
QString QsLogging::FunctorDestination::type() const
{
return QString::fromLatin1(Type);
}

View File

@ -37,19 +37,22 @@ namespace QsLogging
// called from a different thread or even a different binary. You should not access QsLog from // called from a different thread or even a different binary. You should not access QsLog from
// inside LogFunction and should not perform any time-consuming operations. // inside LogFunction and should not perform any time-consuming operations.
// logMessageReady is connected through a queued connection and trace messages are not included // logMessageReady is connected through a queued connection and trace messages are not included
class FunctorDestination : public QObject, public Destination class QSLOG_SHARED_OBJECT FunctorDestination : public QObject, public Destination
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit FunctorDestination(LogFunction f); static const char* const Type;
FunctorDestination(QObject *receiver, const char *member);
virtual void write(const QString &message, Level level); explicit FunctorDestination(LogFunction f);
virtual bool isValid(); FunctorDestination(QObject* receiver, const char* member);
void write(const LogMessage& message) override;
bool isValid() override;
QString type() const override;
protected: protected:
// int used to avoid registering a new enum type // int used to avoid registering a new enum type
Q_SIGNAL void logMessageReady(const QString &message, int level); Q_SIGNAL void logMessageReady(const QsLogging::LogMessage& message);
private: private:
LogFunction mLogFunction; LogFunction mLogFunction;

View File

@ -1,9 +1,14 @@
#ifndef QSLOGDISABLEFORTHISFILE_H #ifndef QSLOGDISABLEFORTHISFILE_H
#define QSLOGDISABLEFORTHISFILE_H #define QSLOGDISABLEFORTHISFILE_H
// When included after all includes of QsLog.h (direct and indirect includes through other headers)
// this file will disable logging in that translation unit.
#ifndef QLOG_TRACE
#error "This file must be included after QsLog.h"
#endif
#include <QtDebug> #include <QtDebug>
// When included AFTER QsLog.h, this file will disable logging in that C++ file. When included
// before, it will lead to compiler warnings or errors about macro redefinitions.
#undef QLOG_TRACE #undef QLOG_TRACE
#undef QLOG_DEBUG #undef QLOG_DEBUG

55
third_party/QsLog/QsLogLevel.cpp vendored Normal file
View File

@ -0,0 +1,55 @@
#include "QsLogLevel.h"
#include <QString>
#include <QObject>
#include <cassert>
static const char TraceString[] = "TRACE";
static const char DebugString[] = "DEBUG";
static const char InfoString[] = "INFO ";
static const char WarnString[] = "WARN ";
static const char ErrorString[] = "ERROR";
static const char FatalString[] = "FATAL";
const char* QsLogging::LevelName(QsLogging::Level theLevel)
{
switch (theLevel) {
case QsLogging::TraceLevel:
return TraceString;
case QsLogging::DebugLevel:
return DebugString;
case QsLogging::InfoLevel:
return InfoString;
case QsLogging::WarnLevel:
return WarnString;
case QsLogging::ErrorLevel:
return ErrorString;
case QsLogging::FatalLevel:
return FatalString;
case QsLogging::OffLevel:
return "";
default: {
Q_ASSERT(!"bad log level");
return InfoString;
}
}
}
QString QsLogging::LocalizedLevelName(QsLogging::Level theLevel)
{
switch (theLevel) {
case QsLogging::TraceLevel:
return QObject::tr("Trace");
case QsLogging::DebugLevel:
return QObject::tr("Debug");
case QsLogging::InfoLevel:
return QObject::tr("Info");
case QsLogging::WarnLevel:
return QObject::tr("Warning");
case QsLogging::ErrorLevel:
return QObject::tr("Error");
case QsLogging::FatalLevel:
return QObject::tr("Fatal");
default:
return QString();
}
}

View File

@ -25,10 +25,10 @@
#ifndef QSLOGLEVEL_H #ifndef QSLOGLEVEL_H
#define QSLOGLEVEL_H #define QSLOGLEVEL_H
class QString;
namespace QsLogging namespace QsLogging
{ {
enum Level enum Level
{ {
TraceLevel = 0, TraceLevel = 0,
@ -40,6 +40,9 @@ enum Level
OffLevel OffLevel
}; };
const char* LevelName(Level theLevel);
QString LocalizedLevelName(Level theLevel);
} }
#endif // QSLOGLEVEL_H #endif // QSLOGLEVEL_H

46
third_party/QsLog/QsLogMessage.cpp vendored Normal file
View File

@ -0,0 +1,46 @@
// Copyright (c) 2015, Axel Gembe <axel@gembe.net>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// * The name of the contributors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
#include "QsLogMessage.h"
#include "QsLogLevel.h"
namespace QsLogging
{
// not using Qt::ISODate because we need the milliseconds too
static const char DateTimePattern[] = "yyyy-MM-ddThh:mm:ss.zzz";
LogMessage::LogMessage(const QString& m, const QDateTime& t, const Level l)
: message(m)
, time(t)
, level(l)
, formatted(QString("%1 %2 %3").arg(LevelName(level))
.arg(t.toLocalTime().toString(DateTimePattern))
.arg(message))
{
}
}

57
third_party/QsLog/QsLogMessage.h vendored Normal file
View File

@ -0,0 +1,57 @@
// Copyright (c) 2015, Axel Gembe <axel@gembe.net>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// * The name of the contributors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef QSLOGMESSAGE_H
#define QSLOGMESSAGE_H
#include "QsLogLevel.h"
#include <QString>
#include <QDateTime>
namespace QsLogging
{
struct LogMessage
{
LogMessage() = default; // Needs to be accessible for qRegisterMetaType
LogMessage(const QString& m, const QDateTime& t, const Level l); // Construct and format message
//! Log message
QString message;
//! Time stamp in UTC, use .toLocalTime() to get local time
QDateTime time;
//! Log level
Level level;
//! Formatted log message ([level] [time] [message])
const QString formatted;
};
}
#endif // QSLOGMESSAGE_H

12
third_party/QsLog/QsLogSharedLibrary.h vendored Normal file
View File

@ -0,0 +1,12 @@
#ifndef QSSHAREDLIBRARY_H
#define QSSHAREDLIBRARY_H
#ifdef QSLOG_IS_SHARED_LIBRARY
#define QSLOG_SHARED_OBJECT Q_DECL_EXPORT
#elif QSLOG_IS_SHARED_LIBRARY_IMPORT
#define QSLOG_SHARED_OBJECT Q_DECL_IMPORT
#else
#define QSLOG_SHARED_OBJECT
#endif
#endif // QSSHAREDLIBRARY_H

View File

@ -1,32 +1,43 @@
# This pro file will build QsLog as a shared library # This pro file will build QsLog as a shared library
include(QsLog.pri)
TARGET = QsLog TARGET = QsLog
VERSION = "2.0.0" VERSION = "2.1.0"
QT -= gui QT -= gui
CONFIG -= console CONFIG -= console
CONFIG -= app_bundle CONFIG -= app_bundle
CONFIG += shared CONFIG += shared
CONFIG += c++11
TEMPLATE = lib TEMPLATE = lib
DESTDIR = $$PWD/build-QsLogShared QSLOG_DESTDIR=$$(QSLOG_DESTDIR)
OBJECTS_DIR = $$DESTDIR/obj !isEmpty(QSLOG_DESTDIR) {
MOC_DIR = $$DESTDIR/moc message(Will use $${QSLOG_DESTDIR} as destdir.)
DESTDIR = $${QSLOG_DESTDIR}/bin
OBJECTS_DIR = $${QSLOG_DESTDIR}
MOC_DIR = $${QSLOG_DESTDIR}
UI_DIR = $${QSLOG_DESTDIR}
RCC_DIR = $${QSLOG_DESTDIR}
}
win32 { win32 {
DEFINES += QSLOG_IS_SHARED_LIBRARY DEFINES += QSLOG_IS_SHARED_LIBRARY
} }
include(QsLog.pri)
unix:!macx { unix:!macx {
DISTRO = $$system(uname -a)
# make install will install the shared object in the appropriate folders # make install will install the shared object in the appropriate folders
headers.files = QsLog.h QsLogDest.h QsLogLevel.h headers.files = QsLog.h QsLogDest.h QsLogLevel.h
headers.path = /usr/include/$(QMAKE_TARGET) headers.path = /usr/include/$(QMAKE_TARGET)
other_files.files = *.txt other_files.files = LICENSE.txt QsLogChanges.txt README.md
other_files.path = /usr/local/share/$(QMAKE_TARGET) other_files.path = /usr/local/share/$(QMAKE_TARGET)
contains(DISTRO, .*ARCH): other_files.path = /usr/share/$(QMAKE_TARGET)
contains(QT_ARCH, x86_64) { contains(QT_ARCH, x86_64) {
target.path = /usr/lib64 target.path = /usr/lib64
contains(DISTRO, .*ARCH): target.path = /usr/lib
} else { } else {
target.path = /usr/lib target.path = /usr/lib
} }

277
third_party/QsLog/QsLogWindow.cpp vendored Normal file
View File

@ -0,0 +1,277 @@
// Copyright (c) 2015, Axel Gembe <axel@gembe.net>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// * The name of the contributors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
#include "QsLogWindow.h"
#include "QsLogWindowModel.h"
#include "QsLog.h"
#include "QsLogMessage.h"
#include "ui_QsLogWindow.h"
#include <QIcon>
#include <QSortFilterProxyModel>
#include <QClipboard>
#include <QKeyEvent>
#include <QFileDialog>
#include <QtGlobal>
#include <cstddef>
static const QIcon& pauseIcon()
{
static QIcon icon(QString::fromLatin1(":/QsLogWindow/images/icon-pause-16.png"));
return icon;
}
static const QIcon& playIcon()
{
static QIcon icon(QString::fromLatin1(":/QsLogWindow/images/icon-resume-16.png"));
return icon;
}
class QsLogging::WindowLogFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
WindowLogFilterProxyModel(Level level, QObject* parent = 0)
: QSortFilterProxyModel(parent)
, mLevel(level)
, mLastVisibleRow(0)
{
}
Level level() const
{
return mLevel;
}
void setLevel(const Level level)
{
mLevel = level;
invalidateFilter();
}
void setPaused(bool paused)
{
mLastVisibleRow = paused ? rowCount() : 0;
if (!paused) {
invalidateFilter();
}
}
protected:
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
Q_UNUSED(source_parent);
if (!mLastVisibleRow) {
LogWindowModel* model = dynamic_cast<LogWindowModel*>(sourceModel());
const LogMessage& d = model->at(source_row);
return d.level >= mLevel;
}
return source_row <= mLastVisibleRow;
}
private:
Level mLevel;
int mLastVisibleRow;
};
QsLogging::Window::Window(QWidget* parent)
: QDialog(parent)
, mUi(nullptr)
, mProxyModel(nullptr)
, mIsPaused(false)
, mHasAutoScroll(true)
{
mUi.reset(new Ui::LogWindow());
mUi->setupUi(this);
connect(mUi->toolButtonPause, SIGNAL(clicked()), SLOT(OnPauseClicked()));
connect(mUi->toolButtonSave, SIGNAL(clicked()), SLOT(OnSaveClicked()));
connect(mUi->toolButtonClear, SIGNAL(clicked()), SLOT(OnClearClicked()));
connect(mUi->toolButtonCopy, SIGNAL(clicked()), SLOT(OnCopyClicked()));
connect(mUi->comboBoxLevel, SIGNAL(currentIndexChanged(int)), SLOT(OnLevelChanged(int)));
connect(mUi->checkBoxAutoScroll, SIGNAL(toggled(bool)), SLOT(OnAutoScrollChanged(bool)));
connect(&mModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
SLOT(ModelRowsInserted(const QModelIndex&, int, int)));
// Install the sort / filter model
mProxyModel.reset(new WindowLogFilterProxyModel(InfoLevel, this));
mProxyModel->setSourceModel(&mModel);
mUi->tableViewMessages->setModel(mProxyModel.get());
mUi->tableViewMessages->installEventFilter(this);
mUi->tableViewMessages->setSelectionBehavior(QAbstractItemView::SelectRows);
#if QT_VERSION >= 0x050000
mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::TimeColumn, QHeaderView::ResizeToContents);
mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::LevelNameColumn, QHeaderView::ResizeToContents);
mUi->tableViewMessages->horizontalHeader()->setSectionResizeMode(LogWindowModel::MessageColumn, QHeaderView::Stretch);
mUi->tableViewMessages->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
#else
mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::TimeColumn, QHeaderView::ResizeToContents);
mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::LevelNameColumn, QHeaderView::ResizeToContents);
mUi->tableViewMessages->horizontalHeader()->setResizeMode(LogWindowModel::MessageColumn, QHeaderView::Stretch);
mUi->tableViewMessages->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
#endif
// Initialize log level selection
for (int l = TraceLevel; l < OffLevel; l++) {
const QString ln = LocalizedLevelName(static_cast<Level>(l));
mUi->comboBoxLevel->addItem(ln, l);
}
mUi->comboBoxLevel->setCurrentIndex(InfoLevel);
}
QsLogging::Window::~Window() noexcept = default;
bool QsLogging::Window::eventFilter(QObject *obj, QEvent *event)
{
if (obj == mUi->tableViewMessages) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_C && (keyEvent->modifiers() & Qt::ControlModifier)) {
copySelection();
return true;
}
}
return false;
} else {
return QDialog::eventFilter(obj, event);
}
}
void QsLogging::Window::addLogMessage(const QsLogging::LogMessage &m)
{
mModel.addEntry(m);
}
void QsLogging::Window::OnPauseClicked()
{
mUi->toolButtonPause->setIcon(mIsPaused ? pauseIcon() : playIcon());
mUi->toolButtonPause->setText(mIsPaused ? tr("&Pause") : tr("&Resume"));
mIsPaused = !mIsPaused;
mProxyModel->setPaused(mIsPaused);
}
void QsLogging::Window::OnSaveClicked()
{
saveSelection();
}
void QsLogging::Window::OnClearClicked()
{
mModel.clear();
}
void QsLogging::Window::OnCopyClicked()
{
copySelection();
}
void QsLogging::Window::OnLevelChanged(int value)
{
mProxyModel->setLevel(static_cast<Level>(value));
}
void QsLogging::Window::OnAutoScrollChanged(bool checked)
{
mHasAutoScroll = checked;
}
void QsLogging::Window::ModelRowsInserted(const QModelIndex& parent, int start, int last)
{
Q_UNUSED(parent);
Q_UNUSED(start);
Q_UNUSED(last);
if (mHasAutoScroll && !mIsPaused) {
mUi->tableViewMessages->scrollToBottom();
}
}
void QsLogging::Window::copySelection() const
{
const QString text = getSelectionText();
if (text.isEmpty()) {
return;
}
QApplication::clipboard()->setText(text);
}
void QsLogging::Window::saveSelection()
{
const QString text = getSelectionText();
if (text.isEmpty()) {
return;
}
QFileDialog dialog(this);
dialog.setWindowTitle(tr("Save log"));
dialog.setNameFilter(tr("Log file (*.log)"));
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix("log");
dialog.exec();
const QStringList sel = dialog.selectedFiles();
if (sel.size() < 1) {
return;
}
QFile file(sel.at(0));
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
stream << text;
file.close();
}
}
QString QsLogging::Window::getSelectionText() const
{
QModelIndexList rows = mUi->tableViewMessages->selectionModel()->selectedRows();
std::sort(rows.begin(), rows.end());
QString text;
if (rows.count() == 0) {
for (int i = 0; i < mProxyModel->rowCount(); i++) {
const int srow = mProxyModel->mapToSource(mProxyModel->index(i, 0)).row();
text += mModel.at(srow).formatted + "\n";
}
} else {
for (QModelIndexList::const_iterator i = rows.begin();i != rows.end();++i) {
const int srow = mProxyModel->mapToSource(*i).row();
text += mModel.at(srow).formatted + "\n";
}
}
return text;
}
#include "QsLogWindow.moc"

79
third_party/QsLog/QsLogWindow.h vendored Normal file
View File

@ -0,0 +1,79 @@
// Copyright (c) 2015, Axel Gembe <axel@gembe.net>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// * The name of the contributors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef QSLOGWINDOW_H
#define QSLOGWINDOW_H
#include "QsLogWindowModel.h"
#include <QDialog>
#include <memory>
class QModelIndex;
namespace Ui {
class LogWindow;
class LogMessage;
}
namespace QsLogging
{
class WindowLogFilterProxyModel;
class QSLOG_SHARED_OBJECT Window : public QDialog
{
Q_OBJECT
public:
explicit Window(QWidget* parent = nullptr);
virtual ~Window() noexcept;
virtual bool eventFilter(QObject* obj, QEvent* event);
private slots:
void addLogMessage(const QsLogging::LogMessage& m);
void OnPauseClicked();
void OnSaveClicked();
void OnClearClicked();
void OnCopyClicked();
void OnLevelChanged(int value);
void OnAutoScrollChanged(bool checked);
void ModelRowsInserted(const QModelIndex& parent, int start, int last);
private:
void copySelection() const;
void saveSelection();
QString getSelectionText() const;
LogWindowModel mModel;
std::unique_ptr<Ui::LogWindow> mUi;
std::unique_ptr<WindowLogFilterProxyModel> mProxyModel;
bool mIsPaused;
bool mHasAutoScroll;
};
}
#endif // QSLOGWINDOW_H

9
third_party/QsLog/QsLogWindow.qrc vendored Normal file
View File

@ -0,0 +1,9 @@
<RCC>
<qresource prefix="/QsLogWindow">
<file>images/icon-clear-16.png</file>
<file>images/icon-save-16.png</file>
<file>images/icon-copy-16.png</file>
<file>images/icon-pause-16.png</file>
<file>images/icon-resume-16.png</file>
</qresource>
</RCC>

173
third_party/QsLog/QsLogWindow.ui vendored Normal file
View File

@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LogWindow</class>
<widget class="QDialog" name="LogWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>599</width>
<height>539</height>
</rect>
</property>
<property name="windowTitle">
<string>Log window</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="toolButtonPause">
<property name="text">
<string>&amp;Pause</string>
</property>
<property name="icon">
<iconset resource="QsLogWindow.qrc">
<normaloff>:/QsLogWindow/images/icon-pause-16.png</normaloff>:/QsLogWindow/images/icon-pause-16.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButtonSave">
<property name="text">
<string>&amp;Save</string>
</property>
<property name="icon">
<iconset resource="QsLogWindow.qrc">
<normaloff>:/QsLogWindow/images/icon-save-16.png</normaloff>:/QsLogWindow/images/icon-save-16.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButtonClear">
<property name="text">
<string>C&amp;lear</string>
</property>
<property name="icon">
<iconset resource="QsLogWindow.qrc">
<normaloff>:/QsLogWindow/images/icon-clear-16.png</normaloff>:/QsLogWindow/images/icon-clear-16.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButtonCopy">
<property name="text">
<string>&amp;Copy</string>
</property>
<property name="icon">
<iconset resource="QsLogWindow.qrc">
<normaloff>:/QsLogWindow/images/icon-copy-16.png</normaloff>:/QsLogWindow/images/icon-copy-16.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Level:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxLevel"/>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="tableViewMessages">
<property name="autoScroll">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="checkBoxAutoScroll">
<property name="text">
<string>&amp;Auto scroll</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>toolButtonPause</tabstop>
<tabstop>toolButtonSave</tabstop>
<tabstop>toolButtonClear</tabstop>
<tabstop>toolButtonCopy</tabstop>
<tabstop>comboBoxLevel</tabstop>
<tabstop>tableViewMessages</tabstop>
<tabstop>checkBoxAutoScroll</tabstop>
</tabstops>
<resources>
<include location="QsLogWindow.qrc"/>
</resources>
<connections/>
</ui>

152
third_party/QsLog/QsLogWindowModel.cpp vendored Normal file
View File

@ -0,0 +1,152 @@
// Copyright (c) 2015, Axel Gembe <axel@gembe.net>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// * The name of the contributors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
#include "QsLogWindowModel.h"
#include "QsLog.h"
#include <QColor>
QsLogging::LogWindowModel::LogWindowModel(size_t max_items) :
mMaxItems(max_items)
{
}
QsLogging::LogWindowModel::~LogWindowModel() noexcept = default;
void QsLogging::LogWindowModel::addEntry(const LogMessage& message)
{
const int next_idx = static_cast<int>(mLogMessages.size());
beginInsertRows(QModelIndex(), next_idx, next_idx);
{
QWriteLocker lock(&mMessagesLock);
mLogMessages.push_back(message);
}
endInsertRows();
if (mMaxItems < std::numeric_limits<size_t>::max() && mLogMessages.size() > mMaxItems) {
{
QWriteLocker lock(&mMessagesLock);
mLogMessages.pop_front();
}
// Every item changed
const QModelIndex idx1 = index(0, 0);
const QModelIndex idx2 = index(static_cast<int>(mLogMessages.size()), rowCount());
emit dataChanged(idx1, idx2);
}
}
void QsLogging::LogWindowModel::clear()
{
beginResetModel();
{
QWriteLocker lock(&mMessagesLock);
mLogMessages.clear();
}
endResetModel();
}
QsLogging::LogMessage QsLogging::LogWindowModel::at(size_t index) const
{
return mLogMessages[index];
}
int QsLogging::LogWindowModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return 3;
}
int QsLogging::LogWindowModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
QReadLocker lock(&mMessagesLock);
return static_cast<int>(mLogMessages.size());
}
QVariant QsLogging::LogWindowModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DisplayRole) {
QReadLocker lock(&mMessagesLock);
const LogMessage& item = mLogMessages.at(index.row());
switch (index.column()) {
case TimeColumn:
return item.time.toLocalTime().toString();
case LevelNameColumn:
return LocalizedLevelName(item.level);
case MessageColumn:
return item.message;
case FormattedMessageColumn:
return item.formatted;
default:
return QVariant();
}
return QString();
}
if (role == Qt::BackgroundColorRole) {
QReadLocker lock(&mMessagesLock);
const LogMessage& item = mLogMessages.at(index.row());
switch (item.level)
{
case QsLogging::WarnLevel:
return QVariant(QColor(255, 255, 128));
case QsLogging::ErrorLevel:
return QVariant(QColor(255, 128, 128));
case QsLogging::FatalLevel:
return QVariant(QColor(255, 0, 0));
default:
break;
}
}
return QVariant();
}
QVariant QsLogging::LogWindowModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case TimeColumn:
return tr("Time");
case LevelNameColumn:
return tr("Level");
case MessageColumn:
return tr("Message");
default:
break;
}
}
return QVariant();
}

72
third_party/QsLog/QsLogWindowModel.h vendored Normal file
View File

@ -0,0 +1,72 @@
// Copyright (c) 2015, Axel Gembe <axel@gembe.net>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this
// list of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
// * The name of the contributors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef QSLOGWINDOWMODEL_H
#define QSLOGWINDOWMODEL_H
#include "QsLogSharedLibrary.h"
#include "QsLogMessage.h"
#include <QAbstractTableModel>
#include <QReadWriteLock>
#include <limits>
#include <deque>
namespace QsLogging
{
class QSLOG_SHARED_OBJECT LogWindowModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum Column
{
TimeColumn = 0,
LevelNameColumn = 1,
MessageColumn = 2,
FormattedMessageColumn = 100
};
explicit LogWindowModel(size_t max_items = std::numeric_limits<size_t>::max());
virtual ~LogWindowModel() noexcept;
void addEntry(const LogMessage& message);
void clear();
LogMessage at(size_t index) const;
// QAbstractTableModel overrides
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
private:
std::deque<LogMessage> mLogMessages;
mutable QReadWriteLock mMessagesLock;
size_t mMaxItems;
};
}
#endif // QSLOGWINDOWMODEL_H

57
third_party/QsLog/README.md vendored Normal file
View File

@ -0,0 +1,57 @@
## QsLog - the simple Qt logger ##
QsLog is an easy to use logger that is based on Qt's QDebug class. QsLog is released as open source, under the BSD license.
###Contribution policy###
Bug fixes are welcome, larger changes however are not encouraged at this point due to the lack of time on my side for reviewing and integrating them. Your best bet in this case would be to open a ticket for your change or forking the project and implementing your change there, with the possibility of having it integrated in the future.
All contributions will be credited, license of the contributions should be 3-clause BSD.
### Features ###
* Six logging levels (from trace to fatal)
* Logging level threshold configurable at runtime.
* Minimum overhead when logging is turned off.
* Supports multiple destinations, comes with file and debug destinations.
* Thread-safe
* Supports logging of common Qt types out of the box.
* Small dependency: just drop it in your project directly.
### Usage ###
Directly including the QsLog source code in a project:
1. Include QsLog.pri in your pro file
2. Include QsLog.h in your C++ files. Include QsLogDest.h only where you create/add destinations.
3. Get the instance of the logger by calling QsLogging::Logger::instance();
4. Optionally set the logging level. Info is default.
5. Create as many destinations as you want by using the QsLogging::DestinationFactory.
6. Add the destinations to the logger instance by calling addDestination.
7. Start logging!
Linking to QsLog dynamically:
1. Build QsLog using the QsLogSharedLibrary.pro.
2. Add the QsLog shared library to your LIBS project dependencies.
3. Follow the steps in "directly including QsLog in your project" starting with step 2.
Note: when you want to use QsLog both from an executable and a shared library you have to
link QsLog dynamically due to a limitation with static variables.
### Configuration ###
QsLog has several configurable parameters in its .pri file:
* defining QS_LOG_LINE_NUMBERS in the .pri file enables writing the file and line number
automatically for each logging call
* defining QS_LOG_SEPARATE_THREAD will route all log messages to a separate thread.
* defining QS_LOG_WIN_PRINTF_CONSOLE will use fprintf instead of OutputDebugString on Windows
Sometimes it's necessary to turn off logging. This can be done in several ways:
* globally, at compile time, by enabling the QS_LOG_DISABLE macro in the supplied .pri file.
* globally, at run time, by setting the log level to "OffLevel".
* per file, at compile time, by including QsLogDisableForThisFile.h in the target file.
### Thread safety ###
The Qt docs say:
A **thread-safe** function can be called simultaneously from multiple threads, even when the invocations use shared data, because all references to the shared data are serialized.
A **reentrant** function can also be called simultaneously from multiple threads, but only if each invocation uses its own data.
Since sending the log message to the destinations is protected by a mutex, the logging macros are thread-safe provided that the log has been initialized - i.e: instance() has been called. Adding and removing destinations and the instance function are likewise thread-safe.
The setup functions (e.g: setLoggingLevel) are NOT thread-safe and are NOT reentrant, they must be called at program startup.

View File

@ -0,0 +1,165 @@
#-------------------------------------------------------------------
# This file is part of the CMake build system for OGRE
# (Object-oriented Graphics Rendering Engine)
# For the latest info, see http://www.ogre3d.org/
#
# The contents of this file are placed in the public domain. Feel
# free to make use of it in any way you like.
#-------------------------------------------------------------------
##################################################################
# Provides some common functionality for the FindPackage modules
##################################################################
# Begin processing of package
macro(findpkg_begin PREFIX)
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS "Looking for ${PREFIX}...")
endif ()
endmacro(findpkg_begin)
# Display a status message unless FIND_QUIETLY is set
macro(pkg_message PREFIX)
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS ${ARGN})
endif ()
endmacro(pkg_message)
# Get environment variable, define it as ENV_$var and make sure backslashes are converted to forward slashes
macro(getenv_path VAR)
set(ENV_${VAR} $ENV{${VAR}})
# replace won't work if var is blank
if (ENV_${VAR})
string( REGEX REPLACE "\\\\" "/" ENV_${VAR} ${ENV_${VAR}} )
endif ()
endmacro(getenv_path)
# Construct search paths for includes and libraries from a PREFIX_PATH
macro(create_search_paths PREFIX)
foreach(dir ${${PREFIX}_PREFIX_PATH})
set(${PREFIX}_INC_SEARCH_PATH ${${PREFIX}_INC_SEARCH_PATH}
${dir}/include ${dir}/Include ${dir}/include/${PREFIX} ${dir}/Headers)
set(${PREFIX}_LIB_SEARCH_PATH ${${PREFIX}_LIB_SEARCH_PATH}
${dir}/lib ${dir}/Lib ${dir}/lib/${PREFIX} ${dir}/Libs)
set(${PREFIX}_BIN_SEARCH_PATH ${${PREFIX}_BIN_SEARCH_PATH}
${dir}/bin)
endforeach(dir)
if(ANDROID)
set(${PREFIX}_LIB_SEARCH_PATH ${${PREFIX}_LIB_SEARCH_PATH} ${OGRE_DEPENDENCIES_DIR}/lib/${ANDROID_ABI})
endif()
set(${PREFIX}_FRAMEWORK_SEARCH_PATH ${${PREFIX}_PREFIX_PATH})
endmacro(create_search_paths)
# clear cache variables if a certain variable changed
macro(clear_if_changed TESTVAR)
# test against internal check variable
# HACK: Apparently, adding a variable to the cache cleans up the list
# a bit. We need to also remove any empty strings from the list, but
# at the same time ensure that we are actually dealing with a list.
list(APPEND ${TESTVAR} "")
list(REMOVE_ITEM ${TESTVAR} "")
if (NOT "${${TESTVAR}}" STREQUAL "${${TESTVAR}_INT_CHECK}")
message(STATUS "${TESTVAR} changed.")
foreach(var ${ARGN})
set(${var} "NOTFOUND" CACHE STRING "x" FORCE)
endforeach(var)
endif ()
set(${TESTVAR}_INT_CHECK ${${TESTVAR}} CACHE INTERNAL "x" FORCE)
endmacro(clear_if_changed)
# Try to get some hints from pkg-config, if available
macro(use_pkgconfig PREFIX PKGNAME)
if(NOT ANDROID)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(${PREFIX} ${PKGNAME})
endif ()
endif()
endmacro (use_pkgconfig)
# Couple a set of release AND debug libraries (or frameworks)
macro(make_library_set PREFIX)
if (${PREFIX}_FWK)
set(${PREFIX} ${${PREFIX}_FWK})
elseif (${PREFIX}_REL AND ${PREFIX}_DBG)
set(${PREFIX} optimized ${${PREFIX}_REL} debug ${${PREFIX}_DBG})
elseif (${PREFIX}_REL)
set(${PREFIX} ${${PREFIX}_REL})
elseif (${PREFIX}_DBG)
set(${PREFIX} ${${PREFIX}_DBG})
endif ()
endmacro(make_library_set)
# Generate debug names from given release names
macro(get_debug_names PREFIX)
foreach(i ${${PREFIX}})
set(${PREFIX}_DBG ${${PREFIX}_DBG} ${i}d ${i}D ${i}_d ${i}_D ${i}_debug ${i})
endforeach(i)
endmacro(get_debug_names)
# Add the parent dir from DIR to VAR
macro(add_parent_dir VAR DIR)
get_filename_component(${DIR}_TEMP "${${DIR}}/.." ABSOLUTE)
set(${VAR} ${${VAR}} ${${DIR}_TEMP})
endmacro(add_parent_dir)
# Do the final processing for the package find.
macro(findpkg_finish PREFIX)
# skip if already processed during this run
if (NOT ${PREFIX}_FOUND)
if (${PREFIX}_INCLUDE_DIR AND ${PREFIX}_LIBRARY)
set(${PREFIX}_FOUND TRUE)
set(${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIR})
set(${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARY})
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS "Found ${PREFIX}: ${${PREFIX}_LIBRARIES}")
endif ()
else ()
if (NOT ${PREFIX}_FIND_QUIETLY)
message(STATUS "Could not locate ${PREFIX}")
endif ()
if (${PREFIX}_FIND_REQUIRED)
message(FATAL_ERROR "Required library ${PREFIX} not found! Install the library (including dev packages) and try again. If the library is already installed, set the missing variables manually in cmake.")
endif ()
endif ()
mark_as_advanced(${PREFIX}_INCLUDE_DIR ${PREFIX}_LIBRARY ${PREFIX}_LIBRARY_REL ${PREFIX}_LIBRARY_DBG ${PREFIX}_LIBRARY_FWK)
endif ()
endmacro(findpkg_finish)
# Slightly customised framework finder
macro(findpkg_framework fwk)
if(APPLE)
set(${fwk}_FRAMEWORK_PATH
${${fwk}_FRAMEWORK_SEARCH_PATH}
${CMAKE_FRAMEWORK_PATH}
~/Library/Frameworks
/Library/Frameworks
/System/Library/Frameworks
/Network/Library/Frameworks
${CMAKE_CURRENT_SOURCE_DIR}/lib/macosx/Release
${CMAKE_CURRENT_SOURCE_DIR}/lib/macosx/Debug
)
# These could be arrays of paths, add each individually to the search paths
foreach(i ${OGRE_PREFIX_PATH})
set(${fwk}_FRAMEWORK_PATH ${${fwk}_FRAMEWORK_PATH} ${i}/lib/macosx/Release ${i}/lib/macosx/Debug)
endforeach(i)
foreach(i ${OGRE_PREFIX_BUILD})
set(${fwk}_FRAMEWORK_PATH ${${fwk}_FRAMEWORK_PATH} ${i}/lib/macosx/Release ${i}/lib/macosx/Debug)
endforeach(i)
foreach(dir ${${fwk}_FRAMEWORK_PATH})
set(fwkpath ${dir}/${fwk}.framework)
if(EXISTS ${fwkpath})
set(${fwk}_FRAMEWORK_INCLUDES ${${fwk}_FRAMEWORK_INCLUDES}
${fwkpath}/Headers ${fwkpath}/PrivateHeaders)
set(${fwk}_FRAMEWORK_PATH ${dir})
if (NOT ${fwk}_LIBRARY_FWK)
set(${fwk}_LIBRARY_FWK "-framework ${fwk}")
endif ()
endif(EXISTS ${fwkpath})
endforeach(dir)
endif(APPLE)
endmacro(findpkg_framework)

50
third_party/QsLog/cmake/FindQsLog.cmake vendored Normal file
View File

@ -0,0 +1,50 @@
#-------------------------------------------------------------------
# CMake build support courtesy of A.Gembe
# The contents of this file are placed in the public domain. Feel
# free to make use of it in any way you like.
#-------------------------------------------------------------------
# - Try to find QsLog
# Once done, this will define
#
# QsLog_FOUND - system has QsLog
# QsLog_INCLUDE_DIRS - the QsLog include directories
# QsLog_LIBRARIES - link these to use QsLog
# QsLog_BINARY_REL
# QsLog_BINARY_DBG
include(FindPkgMacros)
findpkg_begin(QsLog)
# Get path, convert backslashes as ${ENV_${var}}
getenv_path(QsLog_HOME)
# construct search paths
set(QsLog_PREFIX_PATH ${QsLog_HOME} ${ENV_QsLog_HOME})
create_search_paths(QsLog)
# redo search if prefix path changed
clear_if_changed(
QsLog_PREFIX_PATH
QsLog_LIBRARY_REL
QsLog_LIBRARY_DBG
QsLog_BINARY_REL
QsLog_BINARY_DBG
QsLog_INCLUDE_DIR
)
set(QsLog_LIBRARY_NAMES QsLog)
get_debug_names(QsLog_LIBRARY_NAMES)
use_pkgconfig(QsLog_PKGC QsLog)
find_path(QsLog_INCLUDE_DIR NAMES QsLog.h HINTS ${QsLog_INC_SEARCH_PATH} ${QsLog_PKGC_INCLUDE_DIRS} PATH_SUFFIXES QsLog)
find_library(QsLog_LIBRARY_REL NAMES ${QsLog_LIBRARY_NAMES} HINTS ${QsLog_LIB_SEARCH_PATH} ${QsLog_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel)
find_library(QsLog_LIBRARY_DBG NAMES ${QsLog_LIBRARY_NAMES_DBG} HINTS ${QsLog_LIB_SEARCH_PATH} ${QsLog_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug)
find_file(QsLog_BINARY_REL NAMES QsLog.dll HINTS ${QsLog_PREFIX_PATH}/bin PATH_SUFFIXES "" release relwithdebinfo minsizerel)
find_file(QsLog_BINARY_DBG NAMES QsLog_d.dll HINTS ${QsLog_PREFIX_PATH}/bin PATH_SUFFIXES "" debug)
make_library_set(QsLog_LIBRARY)
findpkg_finish(QsLog)
add_parent_dir(QsLog_INCLUDE_DIRS QsLog_INCLUDE_DIR)

View File

@ -0,0 +1,52 @@
# CMake build support courtesy of A.Gembe
if (WIN32)
set(QSLOG_RELEASE_PATH "/Release")
set(QSLOG_RELWDBG_PATH "/RelWithDebInfo")
set(QSLOG_MINSIZE_PATH "/MinSizeRel")
set(QSLOG_DEBUG_PATH "/Debug")
set(QSLOG_LIB_RELEASE_PATH "/Release")
set(QSLOG_LIB_RELWDBG_PATH "/RelWithDebInfo")
set(QSLOG_LIB_MINSIZE_PATH "/MinSizeRel")
set(QSLOG_LIB_DEBUG_PATH "/Debug")
elseif (UNIX)
set(QSLOG_RELEASE_PATH "")
set(QSLOG_RELWDBG_PATH "")
set(QSLOG_MINSIZE_PATH "")
set(QSLOG_DEBUG_PATH "/debug")
set(QSLOG_LIB_RELEASE_PATH "")
set(QSLOG_LIB_RELWDBG_PATH "")
set(QSLOG_LIB_MINSIZE_PATH "")
set(QSLOG_LIB_DEBUG_PATH "")
endif ()
if (APPLE)
set(QSLOG_FRAMEWORK_PATH /Library/Frameworks)
endif ()
# install targets according to current build type
function(QsLog_install_target TARGETNAME SUFFIX)
install(TARGETS ${TARGETNAME}
RUNTIME DESTINATION "bin${QSLOG_RELEASE_PATH}" CONFIGURATIONS Release None ""
LIBRARY DESTINATION "lib${QSLOG_LIB_RELEASE_PATH}${SUFFIX}" CONFIGURATIONS Release None ""
ARCHIVE DESTINATION "lib${QSLOG_LIB_RELEASE_PATH}${SUFFIX}" CONFIGURATIONS Release None ""
FRAMEWORK DESTINATION "${QSLOG_FRAMEWORK_PATH}" CONFIGURATIONS Release None ""
)
install(TARGETS ${TARGETNAME}
RUNTIME DESTINATION "bin${QSLOG_RELWDBG_PATH}" CONFIGURATIONS RelWithDebInfo
LIBRARY DESTINATION "lib${QSLOG_LIB_RELWDBG_PATH}${SUFFIX}" CONFIGURATIONS RelWithDebInfo
ARCHIVE DESTINATION "lib${QSLOG_LIB_RELWDBG_PATH}${SUFFIX}" CONFIGURATIONS RelWithDebInfo
FRAMEWORK DESTINATION "${QSLOG_FRAMEWORK_PATH}" CONFIGURATIONS RelWithDebInfo
)
install(TARGETS ${TARGETNAME}
RUNTIME DESTINATION "bin${QSLOG_MINSIZE_PATH}" CONFIGURATIONS MinSizeRel
LIBRARY DESTINATION "lib${QSLOG_LIB_MINSIZE_PATH}${SUFFIX}" CONFIGURATIONS MinSizeRel
ARCHIVE DESTINATION "lib${QSLOG_LIB_MINSIZE_PATH}${SUFFIX}" CONFIGURATIONS MinSizeRel
FRAMEWORK DESTINATION "${QSLOG_FRAMEWORK_PATH}" CONFIGURATIONS MinSizeRel
)
install(TARGETS ${TARGETNAME}
RUNTIME DESTINATION "bin${QSLOG_DEBUG_PATH}" CONFIGURATIONS Debug
LIBRARY DESTINATION "lib${QSLOG_LIB_DEBUG_PATH}${SUFFIX}" CONFIGURATIONS Debug
ARCHIVE DESTINATION "lib${QSLOG_LIB_DEBUG_PATH}${SUFFIX}" CONFIGURATIONS Debug
FRAMEWORK DESTINATION "${QSLOG_FRAMEWORK_PATH}" CONFIGURATIONS Debug
)
endfunction(QsLog_install_target)

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

View File

@ -0,0 +1,29 @@
set(PROJECTNAME QsLogUnitTest)
project(${PROJECTNAME})
find_package(Qt5Test REQUIRED)
include_directories(${QsLogUnitTest_SOURCE_DIR} ${QsLog_SOURCE_DIR})
if(QS_LOG_IS_SHARED_LIBRARY)
# Ugh, ugly hack
add_definitions(-UQSLOG_IS_SHARED_LIBRARY)
add_definitions(-DQSLOG_IS_SHARED_LIBRARY_IMPORT)
endif()
set(HEADER_FILES
TestLog.h
QtTestUtil/QtTestUtil.h
QtTestUtil/TestRegistration.h
QtTestUtil/TestRegistry.h
)
set(SOURCE_FILES
TestLog.cpp
QtTestUtil/SimpleChecker.cpp
QtTestUtil/TestRegistry.cpp
)
add_executable(${PROJECTNAME} ${SOURCE_FILES} ${HEADER_FILES})
target_link_libraries(${PROJECTNAME} QsLog Qt5::Core Qt5::Test)

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2008 Remko Troncon
* Licensed under the MIT license.
* See COPYING for license details.
*/
#ifndef QtTestUtil_H
#define QtTestUtil_H
#include <QObject>
#include <QtTest/QtTest>
#include "QtTestUtil/TestRegistration.h"
/**
* A macro to register a test class.
*
* This macro will create a static variable which registers the
* testclass with the TestRegistry, and creates an instance of the
* test class.
*
* Execute this macro in the body of your unit test's .cpp file, e.g.
* class MyTest {
* ...
* };
*
* QTTESTUTIL_REGISTER_TEST(MyTest)
*/
#define QTTESTUTIL_REGISTER_TEST(TestClass) \
static QtTestUtil::TestRegistration<TestClass> TestClass##Registration
#endif

View File

@ -0,0 +1,17 @@
/*
* Copyright (C) 2008 Remko Troncon
* Licensed under the MIT license.
* See COPYING for license details.
*/
#include <QCoreApplication>
#include "QtTestUtil/TestRegistry.h"
/**
* Runs all tests registered with the QtTestUtil registry.
*/
int main(int argc, char* argv[]) {
QCoreApplication application(argc, argv);
return QtTestUtil::TestRegistry::getInstance()->runTests(argc, argv);
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2008 Remko Troncon
* Licensed under the MIT license.
* See COPYING for license details.
*/
#ifndef QtTestUtil_TestRegistration_H
#define QtTestUtil_TestRegistration_H
#include "QtTestUtil/TestRegistry.h"
namespace QtTestUtil {
/**
* A wrapper class around a test to manage registration and static
* creation of an instance of the test class.
* This class is used by QTTESTUTIL_REGISTER_TEST(), and you should not
* use this class directly.
*/
template<typename TestClass>
class TestRegistration {
public:
TestRegistration() {
test_ = new TestClass();
TestRegistry::getInstance()->registerTest(test_);
}
~TestRegistration() {
delete test_;
}
private:
TestClass* test_;
};
}
#endif

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2008 Remko Troncon
* Licensed under the MIT license.
* See COPYING for license details.
*/
#include "QtTestUtil/TestRegistry.h"
#include <QtTest/QtTest>
namespace QtTestUtil {
TestRegistry* TestRegistry::getInstance() {
static TestRegistry registry;
return &registry;
}
void TestRegistry::registerTest(QObject* test) {
tests_ += test;
}
int TestRegistry::runTests(int argc, char* argv[]) {
int result = 0;
foreach(QObject* test, tests_) {
result |= QTest::qExec(test, argc, argv);
}
return result;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2008 Remko Troncon
* Licensed under the MIT license.
* See COPYING for license details.
*/
#ifndef QtTestUtil_TestRegistry_H
#define QtTestUtil_TestRegistry_H
#include <QList>
class QObject;
namespace QtTestUtil {
/**
* A registry of QtTest test classes.
* All test classes registered with QTTESTUTIL_REGISTER_TEST add
* themselves to this registry. All registered tests can then be run at
* once using runTests().
*/
class TestRegistry {
public:
/**
* Retrieve the single instance of the registry.
*/
static TestRegistry* getInstance();
/**
* Register a QtTest test.
* This method is called by QTTESTUTIL_REGISTER_TEST, and you should
* not use this method directly.
*/
void registerTest(QObject*);
/**
* Run all registered tests using QTest::qExec()
*/
int runTests(int argc, char* argv[]);
private:
TestRegistry() {}
private:
QList<QObject*> tests_;
};
}
#endif

307
third_party/QsLog/unittest/TestLog.cpp vendored Normal file
View File

@ -0,0 +1,307 @@
#include "TestLog.h"
#include "QtTestUtil/QtTestUtil.h"
#include "QsLog.h"
#include "QsLogDest.h"
#include "QsLogDestFile.h"
#include "QsLogDestConsole.h"
#include "QsLogDestFile.h"
#include "QsLogDestFunctor.h"
#include <QTest>
#include <QSharedPointer>
#include <QtGlobal>
const QLatin1String destinationName1("mock1");
const QLatin1String destinationName2("mock2");
using MockDestinationPtrU = std::unique_ptr<MockDestination>;
// Needed to convert from removeDestination return value to the type that we initially added.
MockDestinationPtrU unique_cast(QsLogging::DestinationPtrU&& d)
{
MockDestinationPtrU md(dynamic_cast<MockDestination*>(d.release()));
Q_ASSERT(md.get());
return md;
}
void DummyLogFunction(const QsLogging::LogMessage&)
{
}
// Autotests for QsLog.
// These tests are based on using a mock destination to check what was logged. Destinations are
// owned by the log, that's why the add/remove ping-pong is needed in the tests.
class TestLog : public QObject
{
Q_OBJECT
public:
TestLog()
{
}
private slots:
void initTestCase();
void testAllLevels();
void testMessageText();
void testLevelChanges();
void testLevelParsing();
void testDestinationRemove();
void testWillRotate();
void testRotation_data();
void testRotation();
void testRotationNoBackup();
void testDestinationType();
private:
};
void TestLog::initTestCase()
{
using namespace QsLogging;
QCOMPARE(Logger::instance().loggingLevel(), InfoLevel);
Logger::instance().setLoggingLevel(TraceLevel);
QCOMPARE(Logger::instance().loggingLevel(), TraceLevel);
}
void TestLog::testAllLevels()
{
using namespace QsLogging;
MockDestinationPtrU mockDest(new MockDestination(destinationName1));
Logger::instance().addDestination(std::move(mockDest));
QLOG_TRACE() << "trace level";
QLOG_DEBUG() << "debug level";
QLOG_INFO() << "info level";
QLOG_WARN() << "warn level";
QLOG_ERROR() << "error level";
QLOG_FATAL() << "fatal level";
mockDest = unique_cast(Logger::instance().removeDestination(destinationName1));
QCOMPARE(mockDest->messageCount(), 6);
QCOMPARE(mockDest->messageCountForLevel(TraceLevel), 1);
QCOMPARE(mockDest->messageCountForLevel(DebugLevel), 1);
QCOMPARE(mockDest->messageCountForLevel(InfoLevel), 1);
QCOMPARE(mockDest->messageCountForLevel(WarnLevel), 1);
QCOMPARE(mockDest->messageCountForLevel(ErrorLevel), 1);
QCOMPARE(mockDest->messageCountForLevel(FatalLevel), 1);
}
void TestLog::testMessageText()
{
using namespace QsLogging;
MockDestinationPtrU mockDest(new MockDestination(destinationName1));
Logger::instance().addDestination(std::move(mockDest));
QLOG_DEBUG() << "foobar";
QLOG_WARN() << "eiszeit";
QLOG_FATAL() << "ruh-roh!";
mockDest = unique_cast(Logger::instance().removeDestination(destinationName1));
QVERIFY(mockDest->hasMessage("foobar", DebugLevel));
QVERIFY(mockDest->hasMessage("eiszeit", WarnLevel));
QVERIFY(mockDest->hasMessage("ruh-roh!", FatalLevel));
QCOMPARE(mockDest->messageCount(), 3);
}
void TestLog::testLevelChanges()
{
using namespace QsLogging;
MockDestinationPtrU mockDest1(new MockDestination(destinationName1));
MockDestinationPtrU mockDest2(new MockDestination(destinationName2));
Logger::instance().addDestination(std::move(mockDest1));
Logger::instance().addDestination(std::move(mockDest2));
using namespace QsLogging;
Logger::instance().setLoggingLevel(WarnLevel);
QCOMPARE(Logger::instance().loggingLevel(), WarnLevel);
QLOG_TRACE() << "one";
QLOG_DEBUG() << "two";
QLOG_INFO() << "three";
mockDest1 = unique_cast(Logger::instance().removeDestination(destinationName1));
mockDest2 = unique_cast(Logger::instance().removeDestination(destinationName2));
QCOMPARE(mockDest1->messageCount(), 0);
QCOMPARE(mockDest2->messageCount(), 0);
Logger::instance().addDestination(std::move(mockDest1));
Logger::instance().addDestination(std::move(mockDest2));
QLOG_WARN() << "warning";
QLOG_ERROR() << "error";
QLOG_FATAL() << "fatal";
mockDest1 = unique_cast(Logger::instance().removeDestination(destinationName1));
mockDest2 = unique_cast(Logger::instance().removeDestination(destinationName2));
QCOMPARE(mockDest1->messageCountForLevel(WarnLevel), 1);
QCOMPARE(mockDest1->messageCountForLevel(ErrorLevel), 1);
QCOMPARE(mockDest1->messageCountForLevel(FatalLevel), 1);
QCOMPARE(mockDest1->messageCount(), 3);
QCOMPARE(mockDest2->messageCountForLevel(WarnLevel), 1);
QCOMPARE(mockDest2->messageCountForLevel(ErrorLevel), 1);
QCOMPARE(mockDest2->messageCountForLevel(FatalLevel), 1);
QCOMPARE(mockDest2->messageCount(), 3);
}
void TestLog::testLevelParsing()
{
using namespace QsLogging;
MockDestinationPtrU mockDest(new MockDestination(destinationName1));
Logger::instance().addDestination(std::move(mockDest));
QLOG_TRACE() << "one";
QLOG_DEBUG() << "two";
QLOG_INFO() << "three";
QLOG_WARN() << "warning";
QLOG_ERROR() << "error";
QLOG_FATAL() << "fatal";
mockDest = unique_cast(Logger::instance().removeDestination(destinationName1));
using namespace QsLogging;
for(int i = 0;i < mockDest->messageCount();++i) {
bool conversionOk = false;
const MockDestination::Message& m = mockDest->messageAt(i);
QCOMPARE(Logger::levelFromLogMessage(m.text, &conversionOk), m.level);
QCOMPARE(Logger::levelFromLogMessage(m.text), m.level);
QCOMPARE(conversionOk, true);
}
}
void TestLog::testDestinationRemove()
{
using namespace QsLogging;
MockDestinationPtrU mockDest(new MockDestination(destinationName1));
MockDestinationPtrU toAddAndRemove(new MockDestination(destinationName2));
Logger::instance().addDestination(std::move(mockDest));
Logger::instance().addDestination(std::move(toAddAndRemove));
Logger::instance().setLoggingLevel(DebugLevel);
QLOG_INFO() << "one for all";
mockDest = unique_cast(Logger::instance().removeDestination(destinationName1));
toAddAndRemove = unique_cast(Logger::instance().removeDestination(destinationName2));
QCOMPARE(mockDest->messageCount(), 1);
QCOMPARE(toAddAndRemove->messageCount(), 1);
Logger::instance().addDestination(std::move(mockDest));
QLOG_INFO() << "one for (almost) all";
mockDest = unique_cast(Logger::instance().removeDestination(destinationName1));
QCOMPARE(mockDest->messageCount(), 2);
QCOMPARE(toAddAndRemove->messageCount(), 1);
QCOMPARE(toAddAndRemove->messageCountForLevel(InfoLevel), 1);
Logger::instance().addDestination(std::move(mockDest));
Logger::instance().addDestination(std::move(toAddAndRemove));
QLOG_INFO() << "another one for all";
mockDest = unique_cast(Logger::instance().removeDestination(destinationName1));
toAddAndRemove = unique_cast(Logger::instance().removeDestination(destinationName2));
QCOMPARE(mockDest->messageCount(), 3);
QCOMPARE(toAddAndRemove->messageCount(), 2);
QCOMPARE(toAddAndRemove->messageCountForLevel(InfoLevel), 2);
}
void TestLog::testWillRotate()
{
using namespace QsLogging;
MockSizeRotationStrategy rotationStrategy;
rotationStrategy.setBackupCount(1);
rotationStrategy.setMaximumSizeInBytes(10);
QCOMPARE(rotationStrategy.shouldRotate(), false);
rotationStrategy.includeMessageInCalculation(QLatin1String("12345"));
QCOMPARE(rotationStrategy.shouldRotate(), false);
rotationStrategy.includeMessageInCalculation(QLatin1String("12345"));
QCOMPARE(rotationStrategy.shouldRotate(), false);
rotationStrategy.includeMessageInCalculation(QLatin1String("1"));
QCOMPARE(rotationStrategy.shouldRotate(), true);
}
void TestLog::testRotation_data()
{
QTest::addColumn<int>("backupCount");
QTest::newRow("one backup") << 1;
QTest::newRow("three backups") << 3;
QTest::newRow("five backups") << 5;
QTest::newRow("ten backups") << 10;
}
void TestLog::testRotation()
{
using namespace QsLogging;
QFETCH(int, backupCount);
MockSizeRotationStrategy rotationStrategy;
rotationStrategy.setBackupCount(backupCount);
for (int i = 0;i < backupCount;++i) {
rotationStrategy.rotate();
QCOMPARE(rotationStrategy.filesList().size(), 1 + i + 1); // 1 log + "rotation count" backups
}
rotationStrategy.rotate();
QCOMPARE(rotationStrategy.filesList().size(), backupCount + 1); // 1 log + backup count
}
void TestLog::testRotationNoBackup()
{
using namespace QsLogging;
MockSizeRotationStrategy rotationStrategy;
rotationStrategy.setBackupCount(0);
rotationStrategy.setMaximumSizeInBytes(10);
rotationStrategy.rotate();
QCOMPARE(rotationStrategy.filesList().size(), 1); // log
}
void TestLog::testDestinationType()
{
using namespace QsLogging;
DestinationPtrU console = DestinationFactory::MakeDebugOutputDestination();
DestinationPtrU file = DestinationFactory::MakeFileDestination("test.log",
LogRotationOption::DisableLogRotation, MaxSizeBytes(5000), MaxOldLogCount(1));
DestinationPtrU func = DestinationFactory::MakeFunctorDestination(&DummyLogFunction);
QCOMPARE(Logger::instance().hasDestinationOfType(DebugOutputDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FileDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FunctorDestination::Type), false);
Logger::instance().addDestination(std::move(console));
QCOMPARE(Logger::instance().hasDestinationOfType(DebugOutputDestination::Type), true);
QCOMPARE(Logger::instance().hasDestinationOfType(FileDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FunctorDestination::Type), false);
Logger::instance().addDestination(std::move(file));
QCOMPARE(Logger::instance().hasDestinationOfType(DebugOutputDestination::Type), true);
QCOMPARE(Logger::instance().hasDestinationOfType(FileDestination::Type), true);
QCOMPARE(Logger::instance().hasDestinationOfType(FunctorDestination::Type), false);
Logger::instance().addDestination(std::move(func));
QCOMPARE(Logger::instance().hasDestinationOfType(DebugOutputDestination::Type), true);
QCOMPARE(Logger::instance().hasDestinationOfType(FileDestination::Type), true);
QCOMPARE(Logger::instance().hasDestinationOfType(FunctorDestination::Type), true);
Logger::instance().removeDestination(DebugOutputDestination::Type);
QCOMPARE(Logger::instance().hasDestinationOfType(DebugOutputDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FileDestination::Type), true);
QCOMPARE(Logger::instance().hasDestinationOfType(FunctorDestination::Type), true);
Logger::instance().removeDestination(FileDestination::Type);
QCOMPARE(Logger::instance().hasDestinationOfType(DebugOutputDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FileDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FunctorDestination::Type), true);
Logger::instance().removeDestination(FunctorDestination::Type);
QCOMPARE(Logger::instance().hasDestinationOfType(DebugOutputDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FileDestination::Type), false);
QCOMPARE(Logger::instance().hasDestinationOfType(FunctorDestination::Type), false);
}
QTTESTUTIL_REGISTER_TEST(TestLog);
#include "TestLog.moc"

158
third_party/QsLog/unittest/TestLog.h vendored Normal file
View File

@ -0,0 +1,158 @@
#ifndef TESTLOG_H
#define TESTLOG_H
#include "QsLogDest.h"
#include "QsLogDestFile.h"
#include <QHash>
#include <QList>
#include <QString>
#include <QStringList>
#include <QDir>
// A destination that tracks log messages
class MockDestination : public QsLogging::Destination
{
public:
struct Message
{
Message() : level(QsLogging::TraceLevel) {}
QString text;
QsLogging::Level level;
};
public:
explicit MockDestination(const QLatin1String& myType = QLatin1String("mock"))
: mMyType(myType)
{
}
void write(const QsLogging::LogMessage& message) override
{
Message m;
m.text = message.formatted;
m.level = message.level;
mMessages.push_back(m);
++mCountByLevel[message.level];
}
bool isValid() override
{
return true;
}
QString type() const override
{
return mMyType;
}
void clear()
{
mMessages.clear();
mCountByLevel.clear();
}
int messageCount() const
{
return mMessages.count();
}
int messageCountForLevel(QsLogging::Level level) const
{
return mCountByLevel.value(level);
}
bool hasMessage(const QString &messageContent, QsLogging::Level level) const
{
Q_FOREACH (const Message &m, mMessages) {
if (m.level == level && m.text.contains(messageContent))
return true;
}
return false;
}
const Message& messageAt(int index)
{
Q_ASSERT(index >= 0 && index < messageCount());
return mMessages.at(index);
}
private:
QHash<QsLogging::Level,int> mCountByLevel;
QList<Message> mMessages;
QLatin1String mMyType;
};
// A rotation strategy that simulates file rotations.
// Stores a "file system" inside a string list and can perform some operations on it.
class MockSizeRotationStrategy : public QsLogging::SizeRotationStrategy
{
public:
MockSizeRotationStrategy()
: fakePath("FAKEPATH")
, logName("QsLog.txt")
{
setInitialInfo(QDir(fakePath).filePath(logName), 0);
files.append(logName);
}
const QStringList& filesList() {
return files;
}
void rotate() override {
SizeRotationStrategy::rotate();
files.append(logName);
}
protected:
bool removeFileAtPath(const QString& path) override {
QString editedPath = path;
if (editedPath.startsWith(fakePath)) {
editedPath.remove(0, fakePath.size() + 1);
const int indexOfPath = files.indexOf(editedPath);
if (indexOfPath != -1) {
files.removeAt(indexOfPath);
return true;
}
}
return false;
}
bool fileExistsAtPath(const QString& path) override {
QString editedPath = path;
if (editedPath.startsWith(fakePath)) {
editedPath.remove(0, fakePath.size() + 1);
return files.indexOf(editedPath) != -1;
}
return false;
}
bool renameFileFromTo(const QString& from, const QString& to) override {
QString editedFromPath = from;
QString editedToPath = to;
if (editedFromPath.startsWith(fakePath) && editedToPath.startsWith(fakePath)) {
editedFromPath.remove(0, fakePath.size() + 1);
editedToPath.remove(0, fakePath.size() + 1);
const int ind = files.indexOf(editedFromPath);
if (ind != -1) {
files.removeAll(editedFromPath);
files.append(editedToPath);
return true;
}
}
return false;
}
private:
QStringList files;
const QString fakePath;
const QString logName;
};
#endif // TESTLOG_H

29
third_party/QsLog/unittest/unittest.pro vendored Normal file
View File

@ -0,0 +1,29 @@
QT += core
TARGET = QsLogUnitTest
CONFIG += console qtestlib c++11
CONFIG -= app_bundle
TEMPLATE = app
# optionally enable address sanitizer
linux-g++|macx {
QMAKE_CXXFLAGS += -fsanitize=address
QMAKE_LFLAGS += -fsanitize=address
}
# test-case sources
SOURCES += TestLog.cpp
HEADERS += TestLog.h
# component sources
include(../QsLog.pri)
SOURCES += \
./QtTestUtil/TestRegistry.cpp \
./QtTestUtil/SimpleChecker.cpp
HEADERS += \
./QtTestUtil/TestRegistry.h \
./QtTestUtil/TestRegistration.h \
./QtTestUtil/QtTestUtil.h