diff --git a/QsLog/QsLog.cpp b/QsLog/QsLog.cpp new file mode 100644 index 00000000..8f281bb6 --- /dev/null +++ b/QsLog/QsLog.cpp @@ -0,0 +1,248 @@ +// Copyright (c) 2013, 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. + +#include "QsLog.h" +#include "QsLogDest.h" +#ifdef QS_LOG_SEPARATE_THREAD +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +namespace QsLogging +{ +typedef QVector DestinationList; + +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 +class LogWriterRunnable : public QRunnable +{ +public: + LogWriterRunnable(QString message, Level level); + virtual void run(); + +private: + QString mMessage; + Level mLevel; +}; +#endif + +class LoggerImpl +{ +public: + LoggerImpl(); + +#ifdef QS_LOG_SEPARATE_THREAD + QThreadPool threadPool; +#endif + QMutex logMutex; + Level level; + 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() + : level(InfoLevel) +{ + // assume at least file + console + destList.reserve(2); +#ifdef QS_LOG_SEPARATE_THREAD + threadPool.setMaxThreadCount(1); + threadPool.setExpiryTimeout(-1); +#endif +} + + +Logger::Logger() + : d(new LoggerImpl) +{ +} + +Logger& Logger::instance() +{ + if (!sInstance) + sInstance = new Logger; + + return *sInstance; +} + +void Logger::destroyInstance() +{ + delete sInstance; +} + +// tries to extract the level from a string log message. If available, conversionSucceeded will +// contain the conversion result. +Level Logger::levelFromLogMessage(const QString& logMessage, bool* conversionSucceeded) +{ + if (conversionSucceeded) + *conversionSucceeded = true; + + if (logMessage.startsWith(QLatin1String(TraceString))) + return TraceLevel; + if (logMessage.startsWith(QLatin1String(DebugString))) + return DebugLevel; + if (logMessage.startsWith(QLatin1String(InfoString))) + return InfoLevel; + if (logMessage.startsWith(QLatin1String(WarnString))) + return WarnLevel; + if (logMessage.startsWith(QLatin1String(ErrorString))) + return ErrorLevel; + if (logMessage.startsWith(QLatin1String(FatalString))) + return FatalLevel; + + if (conversionSucceeded) + *conversionSucceeded = false; + return OffLevel; +} + +Logger::~Logger() +{ +#ifdef QS_LOG_SEPARATE_THREAD + d->threadPool.waitForDone(); +#endif + delete d; + d = 0; +} + +void Logger::addDestination(DestinationPtr destination) +{ + assert(destination.data()); + d->destList.push_back(destination); +} + +void Logger::setLoggingLevel(Level newLevel) +{ + d->level = newLevel; +} + +Level Logger::loggingLevel() const +{ + return d->level; +} + +//! creates the complete log message and passes it to the logger +void Logger::Helper::writeToLog() +{ + const char* const levelName = LevelToText(level); + const QString completeMessage(QString("%1 %2 %3") + .arg(levelName) + .arg(QDateTime::currentDateTime().toString(fmtDateTime)) + .arg(buffer) + ); + + Logger::instance().enqueueWrite(completeMessage, level); +} + +Logger::Helper::~Helper() +{ + try { + writeToLog(); + } + catch(std::exception&) { + // you shouldn't throw exceptions from a sink + assert(!"exception in logger helper destructor"); + throw; + } +} + +//! directs the message to the task queue or writes it directly +void Logger::enqueueWrite(const QString& message, Level level) +{ +#ifdef QS_LOG_SEPARATE_THREAD + LogWriterRunnable *r = new LogWriterRunnable(message, level); + d->threadPool.start(r); +#else + write(message, level); +#endif +} + +//! Sends the message to all the destinations. The level for this message is passed in case +//! it's useful for processing in the destination. +void Logger::write(const QString& message, Level level) +{ + QMutexLocker lock(&d->logMutex); + for (DestinationList::iterator it = d->destList.begin(), + endIt = d->destList.end();it != endIt;++it) { + (*it)->write(message, level); + } +} + +} // end namespace diff --git a/QsLog/QsLog.h b/QsLog/QsLog.h new file mode 100644 index 00000000..38663a13 --- /dev/null +++ b/QsLog/QsLog.h @@ -0,0 +1,137 @@ +// Copyright (c) 2013, 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. + +#ifndef QSLOG_H +#define QSLOG_H + +#include "QsLogLevel.h" +#include "QsLogDest.h" +#include +#include + +#define QS_LOG_VERSION "2.0b1" + +namespace QsLogging +{ +class Destination; +class LoggerImpl; // d pointer + +class QSLOG_SHARED_OBJECT Logger +{ +public: + static Logger& instance(); + static void destroyInstance(); + static Level levelFromLogMessage(const QString& logMessage, bool* conversionSucceeded = 0); + + ~Logger(); + + //! Adds a log message destination. Don't add null destinations. + void addDestination(DestinationPtr destination); + //! Logging at a level < 'newLevel' will be ignored + void setLoggingLevel(Level newLevel); + //! The default level is INFO + Level loggingLevel() const; + + //! The helper forwards the streaming to QDebug and builds the final + //! log message. + class Helper + { + public: + explicit Helper(Level logLevel) : + level(logLevel), + qtDebug(&buffer) {} + ~Helper(); + QDebug& stream(){ return qtDebug; } + + private: + void writeToLog(); + + Level level; + QString buffer; + QDebug qtDebug; + }; + +private: + Logger(); + Logger(const Logger&); // not available + Logger& operator=(const Logger&); // not available + + void enqueueWrite(const QString& message, Level level); + void write(const QString& message, Level level); + + LoggerImpl* d; + + friend class LogWriterRunnable; +}; + +} // end namespace + +//! Logging macros: define QS_LOG_LINE_NUMBERS to get the file and line number +//! in the log output. +#ifndef QS_LOG_LINE_NUMBERS +#define QLOG_TRACE() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::TraceLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::TraceLevel).stream() +#define QLOG_DEBUG() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::DebugLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::DebugLevel).stream() +#define QLOG_INFO() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::InfoLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::InfoLevel).stream() +#define QLOG_WARN() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::WarnLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::WarnLevel).stream() +#define QLOG_ERROR() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::ErrorLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::ErrorLevel).stream() +#define QLOG_FATAL() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::FatalLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::FatalLevel).stream() +#else +#define QLOG_TRACE() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::TraceLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::TraceLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_DEBUG() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::DebugLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::DebugLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_INFO() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::InfoLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::InfoLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_WARN() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::WarnLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::WarnLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_ERROR() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::ErrorLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::ErrorLevel).stream() << __FILE__ << '@' << __LINE__ +#define QLOG_FATAL() \ + if (QsLogging::Logger::instance().loggingLevel() > QsLogging::FatalLevel) {} \ + else QsLogging::Logger::Helper(QsLogging::FatalLevel).stream() << __FILE__ << '@' << __LINE__ +#endif + +#ifdef QS_LOG_DISABLE +#include "QsLogDisableForThisFile.h" +#endif + +#endif // QSLOG_H diff --git a/QsLog/QsLog.pri b/QsLog/QsLog.pri new file mode 100644 index 00000000..01ddcbfa --- /dev/null +++ b/QsLog/QsLog.pri @@ -0,0 +1,19 @@ +INCLUDEPATH += $$PWD +#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_SEPARATE_THREAD # messages are queued and written from a separate thread +SOURCES += $$PWD/QsLogDest.cpp \ + $$PWD/QsLog.cpp \ + $$PWD/QsLogDestConsole.cpp \ + $$PWD/QsLogDestFile.cpp + +HEADERS += $$PWD/QSLogDest.h \ + $$PWD/QsLog.h \ + $$PWD/QsLogDestConsole.h \ + $$PWD/QsLogLevel.h \ + $$PWD/QsLogDestFile.h \ + $$PWD/QsLogDisableForThisFile.h + +OTHER_FILES += \ + $$PWD/QsLogChanges.txt \ + $$PWD/QsLogReadme.txt diff --git a/QsLog/QsLogDest.cpp b/QsLog/QsLogDest.cpp new file mode 100644 index 00000000..f2387278 --- /dev/null +++ b/QsLog/QsLogDest.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2013, 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. + +#include "QsLogDest.h" +#include "QsLogDestConsole.h" +#include "QsLogDestFile.h" +#include + +namespace QsLogging +{ + +//! destination factory +DestinationPtr DestinationFactory::MakeFileDestination(const QString& filePath, + LogRotationOption rotation, const MaxSizeBytes &sizeInBytesToRotateAfter, + const MaxOldLogCount &oldLogsToKeep) +{ + if (EnableLogRotation == rotation) { + QScopedPointer logRotation(new SizeRotationStrategy); + logRotation->setMaximumSizeInBytes(sizeInBytesToRotateAfter.size); + logRotation->setBackupCount(oldLogsToKeep.count); + + return DestinationPtr(new FileDestination(filePath, RotationStrategyPtr(logRotation.take()))); + } + + return DestinationPtr(new FileDestination(filePath, RotationStrategyPtr(new NullRotationStrategy))); +} + +DestinationPtr DestinationFactory::MakeDebugOutputDestination() +{ + return DestinationPtr(new DebugOutputDestination); +} + +} // end namespace diff --git a/QsLog/QsLogDest.h b/QsLog/QsLogDest.h new file mode 100644 index 00000000..063f9ad7 --- /dev/null +++ b/QsLog/QsLogDest.h @@ -0,0 +1,89 @@ +// Copyright (c) 2013, 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. + +#ifndef QSLOGDEST_H +#define QSLOGDEST_H + +#include "QsLogLevel.h" +#include +#include +class QString; + +#ifdef QSLOG_IS_SHARED_LIBRARY +#define QSLOG_SHARED_OBJECT Q_DECL_EXPORT +#else +#define QSLOG_SHARED_OBJECT /*Q_DECL_IMPORT*/ +#endif + +namespace QsLogging +{ + +class QSLOG_SHARED_OBJECT Destination +{ +public: + virtual ~Destination(){} + virtual void write(const QString& message, Level level) = 0; + virtual bool isValid() = 0; // returns whether the destination was created correctly +}; +typedef QSharedPointer DestinationPtr; + + +// a series of "named" paramaters, to make the file destination creation more readable +enum LogRotationOption +{ + DisableLogRotation = 0, + EnableLogRotation = 1 +}; + +struct QSLOG_SHARED_OBJECT MaxSizeBytes +{ + MaxSizeBytes() : size(0) {} + MaxSizeBytes(qint64 size_) : size(size_) {} + qint64 size; +}; + +struct QSLOG_SHARED_OBJECT MaxOldLogCount +{ + MaxOldLogCount() : count(0) {} + MaxOldLogCount(int count_) : count(count_) {} + int count; +}; + + +//! Creates logging destinations/sinks. The caller shares ownership of the destinations with the logger. +//! After being added to a logger, the caller can discard the pointers. +class QSLOG_SHARED_OBJECT DestinationFactory +{ +public: + static DestinationPtr MakeFileDestination(const QString& filePath, + LogRotationOption rotation = DisableLogRotation, + const MaxSizeBytes &sizeInBytesToRotateAfter = MaxSizeBytes(), + const MaxOldLogCount &oldLogsToKeep = MaxOldLogCount()); + static DestinationPtr MakeDebugOutputDestination(); +}; + +} // end namespace + +#endif // QSLOGDEST_H diff --git a/QsLog/QsLogDestConsole.cpp b/QsLog/QsLogDestConsole.cpp new file mode 100644 index 00000000..ed7fc53b --- /dev/null +++ b/QsLog/QsLogDestConsole.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2013, 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. + +#include "QsLogDestConsole.h" +#include +#include + +#if defined(Q_OS_WIN) +#define WIN32_LEAN_AND_MEAN +#include +void QsDebugOutput::output( const QString& message ) +{ + OutputDebugStringW(reinterpret_cast(message.utf16())); + OutputDebugStringW(L"\n"); +} +#elif defined(Q_OS_UNIX) +#include +void QsDebugOutput::output( const QString& message ) +{ + fprintf(stderr, "%s\n", qPrintable(message)); + fflush(stderr); +} +#endif + +void QsLogging::DebugOutputDestination::write(const QString& message, Level) +{ + QsDebugOutput::output(message); +} + +bool QsLogging::DebugOutputDestination::isValid() +{ + return true; +} diff --git a/QsLog/QsLogDestConsole.h b/QsLog/QsLogDestConsole.h new file mode 100644 index 00000000..f80f490e --- /dev/null +++ b/QsLog/QsLogDestConsole.h @@ -0,0 +1,52 @@ +// Copyright (c) 2013, 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. + +#ifndef QSLOGDESTCONSOLE_H +#define QSLOGDESTCONSOLE_H + +#include "QsLogDest.h" + +class QString; + +class QsDebugOutput +{ +public: + static void output(const QString& a_message); +}; + +namespace QsLogging +{ + +// debugger sink +class DebugOutputDestination : public Destination +{ +public: + virtual void write(const QString& message, Level level); + virtual bool isValid(); +}; + +} + +#endif // QSLOGDESTCONSOLE_H diff --git a/QsLog/QsLogDestFile.cpp b/QsLog/QsLogDestFile.cpp new file mode 100644 index 00000000..0f8f8048 --- /dev/null +++ b/QsLog/QsLogDestFile.cpp @@ -0,0 +1,155 @@ +// Copyright (c) 2013, 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. + +#include "QsLogDestFile.h" +#include +#include +#include +#include + +const int QsLogging::SizeRotationStrategy::MaxBackupCount = 10; + +QsLogging::RotationStrategy::~RotationStrategy() +{ +} + +QsLogging::SizeRotationStrategy::SizeRotationStrategy() + : mCurrentSizeInBytes(0) + , mMaxSizeInBytes(0) + , mBackupsCount(0) +{ +} + +void QsLogging::SizeRotationStrategy::setInitialInfo(const QFile &file) +{ + mFileName = file.fileName(); + mCurrentSizeInBytes = file.size(); +} + +void QsLogging::SizeRotationStrategy::includeMessageInCalculation(const QString &message) +{ + mCurrentSizeInBytes += message.toUtf8().size(); +} + +bool QsLogging::SizeRotationStrategy::shouldRotate() +{ + return mCurrentSizeInBytes > mMaxSizeInBytes; +} + +// Algorithm assumes backups will be named filename.X, where 1 <= X <= mBackupsCount. +// All X's will be shifted up. +void QsLogging::SizeRotationStrategy::rotate() +{ + if (!mBackupsCount) { + if (!QFile::remove(mFileName)) + std::cerr << "QsLog: backup delete failed " << qPrintable(mFileName); + return; + } + + // 1. find the last existing backup than can be shifted up + const QString logNamePattern = mFileName + QString::fromUtf8(".%1"); + int lastExistingBackupIndex = 0; + for (int i = 1;i <= mBackupsCount;++i) { + const QString backupFileName = logNamePattern.arg(i); + if (QFile::exists(backupFileName)) + lastExistingBackupIndex = qMin(i, mBackupsCount - 1); + else + break; + } + + // 2. shift up + for (int i = lastExistingBackupIndex;i >= 1;--i) { + const QString oldName = logNamePattern.arg(i); + const QString newName = logNamePattern.arg(i + 1); + QFile::remove(newName); + const bool renamed = QFile::rename(oldName, newName); + if (!renamed) { + std::cerr << "QsLog: could not rename backup " << qPrintable(oldName) + << " to " << qPrintable(newName); + } + } + + // 3. rename current log file + const QString newName = logNamePattern.arg(1); + if (QFile::exists(newName)) + QFile::remove(newName); + if (!QFile::rename(mFileName, newName)) { + std::cerr << "QsLog: could not rename log " << qPrintable(mFileName) + << " to " << qPrintable(newName); + } +} + +QIODevice::OpenMode QsLogging::SizeRotationStrategy::recommendedOpenModeFlag() +{ + return QIODevice::Append; +} + +void QsLogging::SizeRotationStrategy::setMaximumSizeInBytes(qint64 size) +{ + Q_ASSERT(size >= 0); + mMaxSizeInBytes = size; +} + +void QsLogging::SizeRotationStrategy::setBackupCount(int backups) +{ + Q_ASSERT(backups >= 0); + mBackupsCount = qMin(backups, SizeRotationStrategy::MaxBackupCount); +} + + +QsLogging::FileDestination::FileDestination(const QString& filePath, RotationStrategyPtr rotationStrategy) + : mRotationStrategy(rotationStrategy) +{ + mFile.setFileName(filePath); + if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) + std::cerr << "QsLog: could not open log file " << qPrintable(filePath); + mOutputStream.setDevice(&mFile); + mOutputStream.setCodec(QTextCodec::codecForName("UTF-8")); + + mRotationStrategy->setInitialInfo(mFile); +} + +void QsLogging::FileDestination::write(const QString& message, Level) +{ + mRotationStrategy->includeMessageInCalculation(message); + if (mRotationStrategy->shouldRotate()) { + mOutputStream.setDevice(NULL); + mFile.close(); + mRotationStrategy->rotate(); + if (!mFile.open(QFile::WriteOnly | QFile::Text | mRotationStrategy->recommendedOpenModeFlag())) + std::cerr << "QsLog: could not reopen log file " << qPrintable(mFile.fileName()); + mRotationStrategy->setInitialInfo(mFile); + mOutputStream.setDevice(&mFile); + } + + mOutputStream << message << endl; + mOutputStream.flush(); +} + +bool QsLogging::FileDestination::isValid() +{ + return mFile.isOpen(); +} + diff --git a/QsLog/QsLogDestFile.h b/QsLog/QsLogDestFile.h new file mode 100644 index 00000000..ee7b5232 --- /dev/null +++ b/QsLog/QsLogDestFile.h @@ -0,0 +1,101 @@ +// Copyright (c) 2013, 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. + +#ifndef QSLOGDESTFILE_H +#define QSLOGDESTFILE_H + +#include "QsLogDest.h" +#include +#include +#include +#include + +namespace QsLogging +{ +class RotationStrategy +{ +public: + virtual ~RotationStrategy(); + + virtual void setInitialInfo(const QFile &file) = 0; + virtual void includeMessageInCalculation(const QString &message) = 0; + virtual bool shouldRotate() = 0; + virtual void rotate() = 0; + virtual QIODevice::OpenMode recommendedOpenModeFlag() = 0; +}; + +// Never rotates file, overwrites existing file. +class NullRotationStrategy : public RotationStrategy +{ +public: + virtual void setInitialInfo(const QFile &) {} + virtual void includeMessageInCalculation(const QString &) {} + virtual bool shouldRotate() { return false; } + virtual void rotate() {} + virtual QIODevice::OpenMode recommendedOpenModeFlag() { return QIODevice::Truncate; } +}; + +// Rotates after a size is reached, keeps a number of <= 10 backups, appends to existing file. +class SizeRotationStrategy : public RotationStrategy +{ +public: + SizeRotationStrategy(); + static const int MaxBackupCount; + + virtual void setInitialInfo(const QFile &file); + virtual void includeMessageInCalculation(const QString &message); + virtual bool shouldRotate(); + virtual void rotate(); + virtual QIODevice::OpenMode recommendedOpenModeFlag(); + + void setMaximumSizeInBytes(qint64 size); + void setBackupCount(int backups); + +private: + QString mFileName; + qint64 mCurrentSizeInBytes; + qint64 mMaxSizeInBytes; + int mBackupsCount; +}; + +typedef QSharedPointer RotationStrategyPtr; + +// file message sink +class FileDestination : public Destination +{ +public: + FileDestination(const QString& filePath, RotationStrategyPtr rotationStrategy); + virtual void write(const QString& message, Level level); + virtual bool isValid(); + +private: + QFile mFile; + QTextStream mOutputStream; + QSharedPointer mRotationStrategy; +}; + +} + +#endif // QSLOGDESTFILE_H diff --git a/QsLog/QsLogDisableForThisFile.h b/QsLog/QsLogDisableForThisFile.h new file mode 100644 index 00000000..c70af103 --- /dev/null +++ b/QsLog/QsLogDisableForThisFile.h @@ -0,0 +1,22 @@ +#ifndef QSLOGDISABLEFORTHISFILE_H +#define QSLOGDISABLEFORTHISFILE_H + +#include +// 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_DEBUG +#undef QLOG_INFO +#undef QLOG_WARN +#undef QLOG_ERROR +#undef QLOG_FATAL + +#define QLOG_TRACE() if (1) {} else qDebug() +#define QLOG_DEBUG() if (1) {} else qDebug() +#define QLOG_INFO() if (1) {} else qDebug() +#define QLOG_WARN() if (1) {} else qDebug() +#define QLOG_ERROR() if (1) {} else qDebug() +#define QLOG_FATAL() if (1) {} else qDebug() + +#endif // QSLOGDISABLEFORTHISFILE_H diff --git a/QsLog/QsLogLevel.h b/QsLog/QsLogLevel.h new file mode 100644 index 00000000..62984732 --- /dev/null +++ b/QsLog/QsLogLevel.h @@ -0,0 +1,45 @@ +// Copyright (c) 2013, 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. + +#ifndef QSLOGLEVEL_H +#define QSLOGLEVEL_H + +namespace QsLogging +{ + +enum Level +{ + TraceLevel = 0, + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + FatalLevel, + OffLevel +}; + +} + +#endif // QSLOGLEVEL_H diff --git a/QsLog/QsLogSharedLibrary.pro b/QsLog/QsLogSharedLibrary.pro new file mode 100644 index 00000000..2cf52acc --- /dev/null +++ b/QsLog/QsLogSharedLibrary.pro @@ -0,0 +1,30 @@ +# This pro file will build QsLog as a shared library +include(QsLog.pri) + +TARGET = QsLog +VERSION = "2.0.0" +QT -= gui +CONFIG -= console +CONFIG -= app_bundle +CONFIG += shared +TEMPLATE = lib + +DESTDIR = $$PWD/build-QsLogShared +OBJECTS_DIR = $$DESTDIR/obj + +unix:!macx { + # make install will install the shared object in the appropriate folders + headers.files = QsLog.h QsLogDest.h QsLogLevel.h + headers.path = /usr/include/$(QMAKE_TARGET) + + other_files.files = *.txt + other_files.path = /usr/local/share/$(QMAKE_TARGET) + + contains(QT_ARCH, x86_64) { + target.path = /usr/lib64 + } else { + target.path = /usr/lib + } + + INSTALLS += headers target other_files +} diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro index a94e1a2b..8eda42f7 100644 --- a/YACReaderLibrary/YACReaderLibrary.pro +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -125,6 +125,7 @@ include(./server/server.pri) include(../custom_widgets/custom_widgets_yacreaderlibrary.pri) include(../compressed_archive/wrapper.pri) include(./comic_vine/comic_vine.pri) +include(../QsLog/QsLog.pri) RESOURCES += images.qrc files.qrc win32:RESOURCES += images_win.qrc diff --git a/YACReaderLibrary/main.cpp b/YACReaderLibrary/main.cpp index 33b1a97b..1b227c4c 100644 --- a/YACReaderLibrary/main.cpp +++ b/YACReaderLibrary/main.cpp @@ -4,99 +4,54 @@ #include #include #include -#include -#include #include -#include - #if QT_VERSION >= 0x050000 -#include + #include #else -#include + #include #endif - #include "yacreader_global.h" #include "startup.h" #include "yacreader_local_server.h" #include "comic_db.h" +#include "QsLog.h" +#include "QsLogDest.h" + #define PICTUREFLOW_QT4 1 -#if QT_VERSION >= 0x050000 -void yacreaderMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - - QString txt; - - switch (type) { - case QtDebugMsg: - txt = QString("Debug: %1").arg(msg); - break; - case QtWarningMsg: - txt = QString("Warning: %1").arg(msg); - break; - case QtCriticalMsg: - txt = QString("Critical: %1").arg(msg); - break; - case QtFatalMsg: - txt = QString("Fatal: %1").arg(msg); - break; - } - - QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); - - QFile outFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)+"/yacreaderlibrary.log"); - - outFile.open(QIODevice::WriteOnly | QIODevice::Append); - QTextStream ts(&outFile); - ts << QDateTime::currentDateTime().toString() << " - " << txt << endl; -} -#else -void yacreaderMessageHandler(QtMsgType type, const char * msg) -{ - QString txt; - - switch (type) { - case QtDebugMsg: - txt = QString("Debug: %1").arg(msg); - break; - case QtWarningMsg: - txt = QString("Warning: %1").arg(msg); - break; - case QtCriticalMsg: - txt = QString("Critical: %1").arg(msg); - break; - case QtFatalMsg: - txt = QString("Fatal: %1").arg(msg); - break; - } - - QDir().mkpath(QDesktopServices::storageLocation(QDesktopServices::DataLocation)); - - QFile outFile(QDesktopServices::storageLocation(QDesktopServices::DataLocation)+"/yacreaderlibrary.log"); - - outFile.open(QIODevice::WriteOnly | QIODevice::Append); - QTextStream ts(&outFile); - ts << QDateTime::currentDateTime().toString() << " - " << txt << endl; -} -#endif - - //interfaz al servidor Startup * s; +using namespace QsLogging; + int main( int argc, char ** argv ) { QApplication app( argc, argv ); + app.setApplicationName("YACReaderLibrary"); + app.setOrganizationName("YACReader"); + #if QT_VERSION >= 0x050000 - qInstallMessageHandler(yacreaderMessageHandler); + QString destLog = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)+"/yacreaderlibrary.log"; + QString destErr = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)+"/yacreaderlibrary.err"; + QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); + #else - qInstallMsgHandler(yacreaderMessageHandler); + QString destLog = QDesktopServices::storageLocation(QDesktopServices::DataLocation)+"/yacreaderlibrary.log"; + QString destErr = QDesktopServices::storageLocation(QDesktopServices::DataLocation)+"/yacreaderlibrary.err"; + QDir().mkpath(QDesktopServices::storageLocation(QDesktopServices::DataLocation)); #endif - qDebug() << "YACReaderLibrary started" << endl; + Logger& logger = Logger::instance(); + logger.setLoggingLevel(QsLogging::TraceLevel); + + DestinationPtr fileDestination(DestinationFactory::MakeFileDestination( + destLog, EnableLogRotation, MaxSizeBytes(2048), MaxOldLogCount(2))); + DestinationPtr debugDestination(DestinationFactory::MakeDebugOutputDestination()); + logger.addDestination(debugDestination); + logger.addDestination(fileDestination); QTranslator translator; QString sufix = QLocale::system().name(); @@ -122,9 +77,14 @@ int main( int argc, char ** argv ) s->start(); } #endif - + QLOG_INFO() << "YACReaderLibrary attempting to start"; if(YACReaderLocalServer::isRunning()) //sólo se permite una instancia de YACReaderLibrary - return 0; + { + QLOG_WARN() << "another instance of YACReaderLibrary is running"; + QsLogging::Logger::destroyInstance(); + return 0; + } + QLOG_INFO() << "YACReaderLibrary starting"; YACReaderLocalServer * localServer = new YACReaderLocalServer(); @@ -135,8 +95,6 @@ int main( int argc, char ** argv ) //connections to localServer mw->show(); - /*mw->resize(800,480); - mw->showMaximized();*/ int ret = app.exec(); @@ -144,7 +102,9 @@ int main( int argc, char ** argv ) s->stop(); delete s; - qDebug() << "YACReaderLibrary closed" << endl; + QLOG_INFO() << "YACReaderLibrary closed"; + + QsLogging::Logger::destroyInstance(); return ret; }