From 24a29a0f9f60a393e0b34f8b2b5d0ec9ea5bdcd1 Mon Sep 17 00:00:00 2001 From: ck Date: Fri, 4 Aug 2023 17:30:13 +0800 Subject: [PATCH] feat: use spdlog reimplement dlog reimplement dlog use `DLogHelper` instead of `CuteMessageLogger` `LoggerTimingHelper` keep CuteMessage symbol for compatibility (fix undefined symbol: _ZNK3Dtk4Core17CuteMessageLogger5writeEPKcz) --- archlinux/PKGBUILD | 2 +- debian/control | 2 +- include/DtkCore/DLog | 1 + include/log/LogManager.h | 18 +- include/log/Logger.h | 84 -------- include/log/RollingFileAppender.h | 4 +- include/log/dloggerdefs.h | 54 ++---- include/log/dloghelper.h | 37 ++++ src/CMakeLists.txt | 4 + src/log/ConsoleAppender.cpp | 21 +- src/log/FileAppender.cpp | 57 +++--- src/log/LogManager.cpp | 62 ++++-- src/log/Logger.cpp | 155 +++++++-------- src/log/OutputDebugAppender.cpp | 15 +- src/log/README.md | 4 +- src/log/RollingFileAppender.cpp | 165 ++-------------- src/log/dloghelper.cpp | 158 +++++++++++++++ src/log/log.cmake | 2 + src/log/rollingfilesink_p.h | 307 ++++++++++++++++++++++++++++++ tests/ut_dlog.cpp | 7 - tests/ut_logger.cpp | 29 ++- 21 files changed, 751 insertions(+), 437 deletions(-) create mode 100644 include/log/dloghelper.h create mode 100644 src/log/dloghelper.cpp create mode 100644 src/log/rollingfilesink_p.h diff --git a/archlinux/PKGBUILD b/archlinux/PKGBUILD index f333bc8..ca665d3 100644 --- a/archlinux/PKGBUILD +++ b/archlinux/PKGBUILD @@ -9,7 +9,7 @@ pkgdesc='DTK core modules' arch=('x86_64' 'aarch64') url="https://github.com/linuxdeepin/dtkcore" license=('LGPL3') -depends=('deepin-desktop-base-git' 'gsettings-qt' 'dtkcommon-git' 'lshw' 'uchardet' 'icu' 'libsystemd') +depends=('deepin-desktop-base-git' 'gsettings-qt' 'dtkcommon-git' 'lshw' 'uchardet' 'icu' 'libsystemd' 'spdlog') makedepends=('git' 'qt5-tools' 'ninja' 'cmake' 'doxygen') conflicts=('dtkcore') provides=('dtkcore') diff --git a/debian/control b/debian/control index 688085f..19846cc 100644 --- a/debian/control +++ b/debian/control @@ -5,7 +5,7 @@ Maintainer: Deepin Packages Builder Build-Depends: debhelper-compat ( =12), pkg-config, qttools5-dev-tools, qtbase5-private-dev, doxygen, libgsettings-qt-dev, libgtest-dev, libdtkcommon-dev, cmake, - libuchardet-dev, libicu-dev, libsystemd-dev + libuchardet-dev, libicu-dev, libsystemd-dev, libspdlog-dev Standards-Version: 3.9.8 Package: libdtkcore5 diff --git a/include/DtkCore/DLog b/include/DtkCore/DLog index 0871dfa..7888357 100644 --- a/include/DtkCore/DLog +++ b/include/DtkCore/DLog @@ -6,3 +6,4 @@ #include "AbstractStringAppender.h" #include "AbstractAppender.h" #include "JournalAppender.h" +#include "dloghelper.h" diff --git a/include/log/LogManager.h b/include/log/LogManager.h index 7f40606..b109818 100644 --- a/include/log/LogManager.h +++ b/include/log/LogManager.h @@ -12,12 +12,10 @@ DCORE_BEGIN_NAMESPACE -class ConsoleAppender; -class RollingFileAppender; -class JournalAppender; - +class DLogManagerPrivate; class LIBDTKCORESHARED_EXPORT DLogManager { + Q_DISABLE_COPY(DLogManager) public: static void registerConsoleAppender(); static void registerFileAppender(); @@ -34,13 +32,6 @@ public: static void setLogFormat(const QString &format); private: -//TODO: move it to private class (DTK6) - QString m_format; - QString m_logPath; - ConsoleAppender* m_consoleAppender; - RollingFileAppender* m_rollingFileAppender; - JournalAppender* m_journalAppender; - void initConsoleAppender(); void initRollingFileAppender(); void initJournalAppender(); @@ -52,8 +43,9 @@ private: } explicit DLogManager(); ~DLogManager(); - DLogManager(const DLogManager &); - DLogManager & operator = (const DLogManager &); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(DLogManager) }; DCORE_END_NAMESPACE diff --git a/include/log/Logger.h b/include/log/Logger.h index 85ce81f..a2888d1 100644 --- a/include/log/Logger.h +++ b/include/log/Logger.h @@ -7,7 +7,6 @@ #include #include #include -#include #include "dloggerdefs.h" @@ -63,88 +62,5 @@ private: LoggerPrivate *d_ptr; }; - -class LIBDTKCORESHARED_EXPORT CuteMessageLogger -{ - Q_DISABLE_COPY(CuteMessageLogger) - -public: - Q_DECL_CONSTEXPR CuteMessageLogger(Logger *l, Logger::LogLevel level, - const char *file, int line, const char *func) - : m_l(l), - m_level(level), - m_file(file), - m_line(line), - m_function(func), - m_category(nullptr) - {} - - Q_DECL_CONSTEXPR CuteMessageLogger(Logger *l, Logger::LogLevel level, const char *file, - int line, const char *func, const char *category) - : m_l(l), - m_level(level), - m_file(file), - m_line(line), - m_function(func), - m_category(category) - {} - - void write(const char *msg, ...) const -#if defined(Q_CC_GNU) && !defined(__INSURE__) - #if defined(Q_CC_MINGW) && !defined(Q_CC_CLANG) - __attribute__((format(gnu_printf, 2, 3))); - #else - __attribute__((format(printf, 2, 3))); - #endif -#endif - - void write(const QString &msg) const; - QDebug write() const; - -private: - Logger *m_l; - Logger::LogLevel m_level; - const char *m_file; - int m_line; - const char *m_function; - const char *m_category; -}; - -class LIBDTKCORESHARED_EXPORT LoggerTimingHelper -{ - Q_DISABLE_COPY(LoggerTimingHelper) -public: - inline explicit LoggerTimingHelper(Logger *l, Logger::LogLevel level, - const char *file, int line, const char *func) - : m_logger(l), - m_logLevel(level), - m_file(file), - m_line(line), - m_function(func) - {} - - void start(const char *msg, ...) -#if defined(Q_CC_GNU) && !defined(__INSURE__) - #if defined(Q_CC_MINGW) && !defined(Q_CC_CLANG) - __attribute__((format(gnu_printf, 2, 3))); - #else - __attribute__((format(printf, 2, 3))); - #endif -#endif - - void start(const QString &msg = QString()); - - ~LoggerTimingHelper(); - -private: - Logger *m_logger; - QElapsedTimer m_time; - Logger::LogLevel m_logLevel; - const char *m_file; - int m_line; - const char *m_function; - QString m_block; -}; - DCORE_END_NAMESPACE #endif // LOGGER_H diff --git a/include/log/RollingFileAppender.h b/include/log/RollingFileAppender.h index f87143b..044ed1d 100644 --- a/include/log/RollingFileAppender.h +++ b/include/log/RollingFileAppender.h @@ -35,9 +35,9 @@ class LIBDTKCORESHARED_EXPORT RollingFileAppender : public FileAppender DatePattern datePattern() const; void setDatePattern(DatePattern datePattern); - void setDatePattern(const QString &datePattern); + QT_DEPRECATED_X("use setDatePattern(DatePattern)") void setDatePattern(const QString &datePattern); - QString datePatternString() const; + QT_DEPRECATED_X("use datePattern(DatePattern)") QString datePatternString() const; void setLogFilesLimit(int limit); int logFilesLimit() const; diff --git a/include/log/dloggerdefs.h b/include/log/dloggerdefs.h index 8395103..cce2654 100644 --- a/include/log/dloggerdefs.h +++ b/include/log/dloggerdefs.h @@ -5,52 +5,38 @@ #define DLOGGER_DEFINE_H #include "dtkcore_global.h" +#include DCORE_BEGIN_NAMESPACE class Logger; -class CuteMessageLogger; -class LoggerTimingHelper; +class DLogHelper; LIBDTKCORESHARED_EXPORT Logger *loggerInstance(); -#define logger loggerInstance() +#define dlogger loggerInstance() -#define dTrace CuteMessageLogger(loggerInstance(), Logger::Trace, __FILE__, __LINE__, Q_FUNC_INFO).write -#define dDebug CuteMessageLogger(loggerInstance(), Logger::Debug, __FILE__, __LINE__, Q_FUNC_INFO).write -#define dInfo CuteMessageLogger(loggerInstance(), Logger::Info, __FILE__, __LINE__, Q_FUNC_INFO).write -#define dWarning CuteMessageLogger(loggerInstance(), Logger::Warning, __FILE__, __LINE__, Q_FUNC_INFO).write -#define dError CuteMessageLogger(loggerInstance(), Logger::Error, __FILE__, __LINE__, Q_FUNC_INFO).write -#define dFatal CuteMessageLogger(loggerInstance(), Logger::Fatal, __FILE__, __LINE__, Q_FUNC_INFO).write +#define DLOG_CTX(category) QMessageLogContext(__FILE__, __LINE__, Q_FUNC_INFO, category) -#define dCDebug(category) CuteMessageLogger(loggerInstance(), Logger::Debug, __FILE__, __LINE__, Q_FUNC_INFO, category).write() -#define dCInfo(category) CuteMessageLogger(loggerInstance(), Logger::Info, __FILE__, __LINE__, Q_FUNC_INFO, category).write() -#define dCWarning(category) CuteMessageLogger(loggerInstance(), Logger::Warning, __FILE__, __LINE__, Q_FUNC_INFO, category).write() -#define dCError(category) CuteMessageLogger(loggerInstance(), Logger::Error, __FILE__, __LINE__, Q_FUNC_INFO, category).write() -#define dCFatal(category) CuteMessageLogger(loggerInstance(), Logger::Fatal, __FILE__, __LINE__, Q_FUNC_INFO, category).write() +// include DLog or dloghelper.h +#define dTrace DLogHelper(Logger::Trace, DLOG_CTX("default")).write +#define dDebug DLogHelper(Logger::Debug, DLOG_CTX("default")).write +#define dInfo DLogHelper(Logger::Info, DLOG_CTX("default")).write +#define dWarning DLogHelper(Logger::Warning, DLOG_CTX("default")).write +#define dError DLogHelper(Logger::Error, DLOG_CTX("default")).write +#define dFatal DLogHelper(Logger::Fatal, DLOG_CTX("default")).write -#define dTraceTime LoggerTimingHelper loggerTimingHelper(loggerInstance(), Logger::Trace, __FILE__, __LINE__, Q_FUNC_INFO); loggerTimingHelper.start -#define dDebugTime LoggerTimingHelper loggerTimingHelper(loggerInstance(), Logger::Debug, __FILE__, __LINE__, Q_FUNC_INFO); loggerTimingHelper.start -#define dInfoTime LoggerTimingHelper loggerTimingHelper(loggerInstance(), Logger::Info, __FILE__, __LINE__, Q_FUNC_INFO); loggerTimingHelper.start +#define dCDebug(category) DLogHelper(Logger::Debug, DLOG_CTX(category)).write() +#define dCInfo(category) DLogHelper(Logger::Info, DLOG_CTX(category)).write() +#define dCWarning(category) DLogHelper(Logger::Warning, DLOG_CTX(category)).write() +#define dCError(category) DLogHelper(Logger::Error, DLOG_CTX(category)).write() +#define dCFatal(category) DLogHelper(Logger::Fatal, DLOG_CTX(category)).write() + +#define dTraceTime DLogHelper helper(Logger::Trace, DLOG_CTX("default")); helper.timing +#define dDebugTime DLogHelper helper(Logger::Debug, DLOG_CTX("default")); helper.timing +#define dInfoTime DLogHelper helper(Logger::Info, DLOG_CTX("default")); helper.timing #define dAssert(cond) ((!(cond)) ? loggerInstance()->writeAssert(__FILE__, __LINE__, Q_FUNC_INFO, #cond) : qt_noop()) #define dAssertX(cond, msg) ((!(cond)) ? loggerInstance()->writeAssert(__FILE__, __LINE__, Q_FUNC_INFO, msg) : qt_noop()) -#define dCategory(category) \ - private:\ - Logger* loggerInstance()\ - {\ - static Logger customLoggerInstance(category);\ - return &customLoggerInstance;\ - }\ - -#define dGlobalCategory(category) \ - private:\ - Logger* loggerInstance()\ - {\ - static Logger customLoggerInstance(category);\ - customLoggerInstance.logToGlobalInstance(category, true);\ - return &customLoggerInstance;\ - }\ - DCORE_END_NAMESPACE #endif // DLOGGER_DEFINE_H diff --git a/include/log/dloghelper.h b/include/log/dloghelper.h new file mode 100644 index 0000000..cb3ecd6 --- /dev/null +++ b/include/log/dloghelper.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DLOGHELPER_H +#define DLOGHELPER_H + +#include +#include "Logger.h" + +#include +#include + +DCORE_BEGIN_NAMESPACE +class DLogHelperPrivate; +class LIBDTKCORESHARED_EXPORT DLogHelper : public QObject +{ + Q_DISABLE_COPY(DLogHelper) +public: + DLogHelper(Logger::LogLevel level, const QMessageLogContext &context, QObject *parent = nullptr); + ~DLogHelper(); + + void write(const char* msg, ...) Q_ATTRIBUTE_FORMAT_PRINTF(2, 3); + void write(const QString& msg); + QDebug write(); + + void timing(const QString& msg, QObject *context = nullptr); + + static Logger::LogLevel levelFromQtMsgType(QtMsgType mt); + static QtMsgType qtMsgTypeFromLogLevel(Logger::LogLevel lvl); + + Q_DECLARE_PRIVATE(DLogHelper) +}; + +DCORE_END_NAMESPACE + +#endif // DLOGHELPER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d80ed24..734ee07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,7 @@ include(filesystem/filesystem.cmake) #end filesystem # start log include(log/log.cmake) +find_package(spdlog REQUIRED) #end log # start settings include(settings/settings.cmake) @@ -73,6 +74,7 @@ if(LINUX) ICU::uc Qt${QT_VERSION_MAJOR}::CorePrivate uchardet + spdlog::spdlog ) if("${QT_VERSION_MAJOR}" STREQUAL "5") target_link_libraries(${LIB_NAME} PRIVATE @@ -105,6 +107,7 @@ else() ICU::uc Qt${QT_VERSION_MAJOR}::CorePrivate uchardet + spdlog::spdlog ) if("${QT_VERSION_MAJOR}" STREQUAL "5") target_link_libraries(${LIB_NAME} PRIVATE @@ -117,6 +120,7 @@ set_target_properties(${LIB_NAME} PROPERTIES SOVERSION ${CMAKE_PROJECT_VERSION_MAJOR} EXPORT_NAME Core ) + target_include_directories(${LIB_NAME} PRIVATE ${uchardet_INCLUDE_DIRS} ) diff --git a/src/log/ConsoleAppender.cpp b/src/log/ConsoleAppender.cpp index 2872c32..b60536e 100644 --- a/src/log/ConsoleAppender.cpp +++ b/src/log/ConsoleAppender.cpp @@ -3,8 +3,12 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // Local + #include "ConsoleAppender.h" +#include +#include + // STL #include extern "C" { @@ -33,20 +37,23 @@ ConsoleAppender::ConsoleAppender() : AbstractStringAppender() ,m_ignoreEnvPattern(false) { - setFormat("[%{type:-7}] <%{function}> %{message}\n"); + if (!spdlog::get("console")) { + auto clogger = spdlog::stdout_color_mt("console"); + clogger->set_level(spdlog::level::level_enum(detailsLevel())); + } } QString ConsoleAppender::format() const { const QString envPattern = QString::fromLocal8Bit(qgetenv("QT_MESSAGE_PATTERN")); - return (m_ignoreEnvPattern || envPattern.isEmpty()) ? AbstractStringAppender::format() : (envPattern + "\n"); + return (m_ignoreEnvPattern || envPattern.isEmpty()) ? AbstractStringAppender::format() : (envPattern + "\n"); } void ConsoleAppender::ignoreEnvironmentPattern(bool ignore) { - m_ignoreEnvPattern = ignore; + m_ignoreEnvPattern = ignore; } /*! @@ -67,8 +74,12 @@ void ConsoleAppender::ignoreEnvironmentPattern(bool ignore) void ConsoleAppender::append(const QDateTime &time, Logger::LogLevel level, const char *file, int line, const char *func, const QString &category, const QString &msg) { - bool isAtty = isatty(STDERR_FILENO); - std::cerr << qPrintable(formattedString(time, level, file, line, func, category, msg, isAtty)); + auto clogger = spdlog::get("console"); + Q_ASSERT(clogger); + clogger->set_level(spdlog::level::level_enum(detailsLevel())); + + const auto &formatted = formattedString(time, level, file, line, func, category, msg, true); + clogger->log(spdlog::level::level_enum(level), formatted.toStdString()); } DCORE_END_NAMESPACE diff --git a/src/log/FileAppender.cpp b/src/log/FileAppender.cpp index 8fb38ae..030e111 100644 --- a/src/log/FileAppender.cpp +++ b/src/log/FileAppender.cpp @@ -4,10 +4,18 @@ #include "FileAppender.h" +#include + +#include "rollingfilesink_p.h" + #include DCORE_BEGIN_NAMESPACE +std::string loggerName(const QFile &logFile) +{ + return QFileInfo(logFile).fileName().toStdString(); +} /*! @~english @class Dtk::Core::FileAppender @@ -52,29 +60,36 @@ QString FileAppender::fileName() const void FileAppender::setFileName(const QString &s) { QMutexLocker locker(&m_logFileMutex); - if (m_logFile.isOpen()) - m_logFile.close(); + + if (s == m_logFile.fileName()) + return; + + closeFile(); m_logFile.setFileName(s); + + if (!spdlog::get(loggerName(s))) + rolling_logger_mt(loggerName(s), + m_logFile.fileName().toStdString(), + 1024 * 1024 * 20, 0); } qint64 FileAppender::size() const { + QMutexLocker locker(&m_logFileMutex); + + if (auto *bs = get_sink(loggerName(m_logFile))){ + return qint64(bs->filesize()); + } + return m_logFile.size(); } - bool FileAppender::openFile() { - bool isOpen = m_logFile.isOpen(); - if (!isOpen) { - isOpen = m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); - if (isOpen) - m_logStream.setDevice(&m_logFile); - else - std::cerr << " Cannot open the log file " << qPrintable(m_logFile.fileName()) << std::endl; - } - return isOpen; + auto fl = spdlog::get(loggerName(m_logFile)); + + return fl.get(); } /*! @@ -96,21 +111,19 @@ bool FileAppender::openFile() void FileAppender::append(const QDateTime &time, Logger::LogLevel level, const char *file, int line, const char *func, const QString &category, const QString &msg) { - QMutexLocker locker(&m_logFileMutex); - if (openFile()) - { - m_logStream << formattedString(time, level, file, line, func, category, msg); - m_logStream.flush(); - m_logFile.flush(); - } + if (!openFile()) + return; + + auto fl = spdlog::get(loggerName(m_logFile)); + const auto &formatted = formattedString(time, level, file, line, func, category, msg); + fl->log(spdlog::level::level_enum(level), formatted.toStdString()); + fl->flush(); } - void FileAppender::closeFile() { - QMutexLocker locker(&m_logFileMutex); - m_logFile.close(); + spdlog::drop(loggerName(m_logFile)); } DCORE_END_NAMESPACE diff --git a/src/log/LogManager.cpp b/src/log/LogManager.cpp index 277a6ad..dc75bee 100644 --- a/src/log/LogManager.cpp +++ b/src/log/LogManager.cpp @@ -9,6 +9,8 @@ #include #include "dstandardpaths.h" +#include "spdlog/spdlog.h" + DCORE_BEGIN_NAMESPACE // Courtesy qstandardpaths_unix.cpp @@ -26,6 +28,26 @@ static void appendOrganizationAndApp(QString &path) #endif } +#define DEFAULT_FMT "%{time}{yyyy-MM-dd, HH:mm:ss.zzz} [%{type:-7}] [%{file:-20} %{function:-35} %{line}] %{message}" + +class DLogManagerPrivate { +public: + explicit DLogManagerPrivate(DLogManager *q) + : m_format(DEFAULT_FMT) + , q_ptr(q) + { + } + + QString m_format; + QString m_logPath; + ConsoleAppender* m_consoleAppender = nullptr; + RollingFileAppender* m_rollingFileAppender = nullptr; + JournalAppender* m_journalAppender = nullptr; + + DLogManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(DLogManager) + +}; /*! @~english \class Dtk::Core::DLogManager @@ -35,30 +57,34 @@ static void appendOrganizationAndApp(QString &path) */ DLogManager::DLogManager() + :d_ptr(new DLogManagerPrivate(this)) { - m_format = "%{time}{yyyy-MM-dd, HH:mm:ss.zzz} [%{type:-7}] [%{file:-20} %{function:-35} %{line}] %{message}\n"; + spdlog::set_automatic_registration(true); + spdlog::set_pattern("%v"); } void DLogManager::initConsoleAppender(){ - m_consoleAppender = new ConsoleAppender; - m_consoleAppender->setFormat(m_format); - logger->registerAppender(m_consoleAppender); + Q_D(DLogManager); + d->m_consoleAppender = new ConsoleAppender; + d->m_consoleAppender->setFormat(d->m_format); + dlogger->registerAppender(d->m_consoleAppender); } void DLogManager::initRollingFileAppender(){ - m_rollingFileAppender = new RollingFileAppender(getlogFilePath()); - m_rollingFileAppender->setFormat(m_format); - m_rollingFileAppender->setLogFilesLimit(5); - m_rollingFileAppender->setDatePattern(RollingFileAppender::DailyRollover); - logger->registerAppender(m_rollingFileAppender); + Q_D(DLogManager); + d->m_rollingFileAppender = new RollingFileAppender(getlogFilePath()); + d->m_rollingFileAppender->setFormat(d->m_format); + d->m_rollingFileAppender->setLogFilesLimit(5); + d->m_rollingFileAppender->setDatePattern(RollingFileAppender::DailyRollover); + dlogger->registerAppender(d->m_rollingFileAppender); } - void DLogManager::initJournalAppender() { #if (defined BUILD_WITH_SYSTEMD && defined Q_OS_LINUX) - m_journalAppender = new JournalAppender(); - logger->registerAppender(m_journalAppender); + Q_D(DLogManager); + d->m_journalAppender = new JournalAppender(); + dlogger->registerAppender(d->m_journalAppender); #endif } @@ -101,7 +127,7 @@ QString DLogManager::getlogFilePath() { //No longer set the default log path (and mkdir) when constructing now, instead set the default value if it's empty when getlogFilePath is called. //Fix the problem that the log file is still created in the default path when the log path is set manually. - if (DLogManager::instance()->m_logPath.isEmpty()) { + if (DLogManager::instance()->d_func()->m_logPath.isEmpty()) { if (DStandardPaths::homePath().isEmpty()) { qWarning() << "Unable to locate the cache directory, cannot acquire home directory, and the log will not be written to file.."; return QString(); @@ -113,10 +139,10 @@ QString DLogManager::getlogFilePath() if (!QDir(cachePath).exists()) { QDir(cachePath).mkpath(cachePath); } - DLogManager::instance()->m_logPath = DLogManager::instance()->joinPath(cachePath, QString("%1.log").arg(qApp->applicationName())); + instance()->d_func()->m_logPath = instance()->joinPath(cachePath, QString("%1.log").arg(qApp->applicationName())); } - return QDir::toNativeSeparators(DLogManager::instance()->m_logPath); + return QDir::toNativeSeparators(DLogManager::instance()->d_func()->m_logPath); } /*! @@ -131,13 +157,13 @@ void DLogManager::setlogFilePath(const QString &logFilePath) if (info.exists() && !info.isFile()) qWarning() << "invalid file path:" << logFilePath << " is not a file"; else - DLogManager::instance()->m_logPath = logFilePath; + DLogManager::instance()->d_func()->m_logPath = logFilePath; } void DLogManager::setLogFormat(const QString &format) { //m_format = "%{time}{yyyy-MM-dd, HH:mm:ss.zzz} [%{type:-7}] [%{file:-20} %{function:-35} %{line}] %{message}\n"; - DLogManager::instance()->m_format = format; + DLogManager::instance()->d_func()->m_format = format; } QString DLogManager::joinPath(const QString &path, const QString &fileName){ @@ -147,7 +173,7 @@ QString DLogManager::joinPath(const QString &path, const QString &fileName){ DLogManager::~DLogManager() { - + spdlog::shutdown(); } DCORE_END_NAMESPACE diff --git a/src/log/Logger.cpp b/src/log/Logger.cpp index fbe1b56..ee4af30 100644 --- a/src/log/Logger.cpp +++ b/src/log/Logger.cpp @@ -26,9 +26,9 @@ DCORE_BEGIN_NAMESPACE /*! @~english - \macro Dtk::Core::logger + \macro Dtk::Core::dlogger \relates Dtk::Core::Logger - \keyword Dtk::Core::logger + \keyword Dtk::Core::dlogger @brief Macro returning the current instance of Logger object @@ -38,7 +38,7 @@ DCORE_BEGIN_NAMESPACE Example: \code ConsoleAppender* consoleAppender = new ConsoleAppender; - logger->registerAppender(consoleAppender); + dlogger->registerAppender(consoleAppender); \endcode @sa Dtk::Core::Logger::globalInstance() @@ -248,7 +248,7 @@ DCORE_BEGIN_NAMESPACE \relates Dtk::Core::Logger \keyword Dtk::Core::dCategory() - @brief Create logger instance inside your custom class to log all messages to the specified \a category + @brief Create dlogger instance inside your custom class to log all messages to the specified \a category This macro is used to pass all log messages inside your custom class to the specific \a category. You must include this macro inside your class declaration (similarly to the Q_OBJECT macro). @@ -258,8 +258,8 @@ DCORE_BEGIN_NAMESPACE Thus, any call to loggerInstance() (for example, inside dTrace() macro) will return the local Logger object, so any logging message will be directed to the default category. - @note This macro does not register any appender to the newly created logger instance. You should register - logger appenders manually, inside your class. + @note This macro does not register any appender to the newly created dlogger instance. You should register + dlogger appenders manually, inside your class. Usage example: \code @@ -272,7 +272,7 @@ DCORE_BEGIN_NAMESPACE CustomClass::CustomClass(QObject* parent) : QObject(parent) { - logger->registerAppender(new FileAppender("custom_category_log")); + dlogger->registerAppender(new FileAppender("custom_category_log")); dTrace() << "Trace to the custom category log"; } \endcode @@ -289,13 +289,13 @@ DCORE_BEGIN_NAMESPACE \relates Dtk::Core::Logger \keyword Dtk::Core::dGlobalCategory() - @brief Create logger instance inside your custom class to log all messages both to the specified \a category and to - the global logger instance. + @brief Create dlogger instance inside your custom class to log all messages both to the specified \a category and to + the global dlogger instance. - This macro is similar to dCategory(), but also passes all log messages to the global logger instance appenders. - It is equal to defining the local \a category logger using dCategory macro and calling: + This macro is similar to dCategory(), but also passes all log messages to the global dlogger instance appenders. + It is equal to defining the local \a category dlogger using dCategory macro and calling: \code - logger->logToGlobalInstance(logger->defaultCategory(), true); + dlogger->logToGlobalInstance(dlogger->defaultCategory(), true); \endcode @sa Dtk::Core::dCategory @@ -423,13 +423,13 @@ DCORE_BEGIN_NAMESPACE @brief Very simple but rather powerful component which may be used for logging your application activities. - Global logger instance created on a first access to it (e.g. registering appenders, calling a dDebug() macro + Global dlogger instance created on a first access to it (e.g. registering appenders, calling a dDebug() macro etc.) registers itself as a Qt default message handler and captures all the qDebug/dWarning/qCritical output. @note Qt 4 qDebug set of macro doesn't support capturing source function name, file name or line number so we recommend to use dDebug() and other Logger macros instead. - @sa Dtk::Core::logger + @sa Dtk::Core::dlogger */ class LogDevice : public QIODevice @@ -483,21 +483,12 @@ private: const char *m_category; }; -// Forward declarations -//static void cleanupLoggerGlobalInstance(); - -//#if QT_VERSION >= 0x050000 -//static void qtLoggerMessageHandler(QtMsgType, const QMessageLogContext& context, const QString &msg); -//#else -//static void qtLoggerMessageHandler(QtMsgType type, const char *msg); -//#endif - /*! @~english \internal LoggerPrivate class implements the Singleton pattern in a thread-safe way. It contains a static pointer to the - global logger instance protected by QReadWriteLock + global dlogger instance protected by QReadWriteLock */ class LoggerPrivate { @@ -582,8 +573,8 @@ static void qtLoggerMessageHandler(QtMsgType type, const char *msg) @~english @brief Construct the instance of Logger. - If you're only using one global instance of logger you wouldn't probably need to use this constructor manually. - Consider using [logger](@ref logger) macro instead to access the logger instance + If you're only using one global instance of dlogger you wouldn't probably need to use this constructor manually. + Consider using [dlogger](@ref dlogger) macro instead to access the dlogger instance */ Logger::Logger() : d_ptr(new LoggerPrivate) @@ -594,10 +585,10 @@ Logger::Logger() /*! @~english - @brief Construct the instance of Logger and set logger default category. + @brief Construct the instance of Logger and set dlogger default category. - If you're only using one global instance of logger you wouldn't probably need to use this constructor manually. - Consider using logger macro instead to access the logger instance and call setDefaultCategory method. + If you're only using one global instance of dlogger you wouldn't probably need to use this constructor manually. + Consider using dlogger macro instead to access the dlogger instance and call setDefaultCategory method. @sa Logger() @sa setDefaultCategory() @@ -612,7 +603,7 @@ Logger::Logger(const QString &defaultCategory) @~english @brief Destroy the instance of Logger. - You probably wouldn't need to use this function directly. Global instance of logger will be destroyed automatically + You probably wouldn't need to use this function directly. Global instance of dlogger will be destroyed automatically at the end of your QCoreApplication execution */ Logger::~Logger() @@ -642,9 +633,9 @@ Logger::~Logger() @~english @brief Returns the global instance of Logger. - In a most cases you shouldn't use this function directly. Consider using [logger](@ref logger) macro instead. + In a most cases you shouldn't use this function directly. Consider using [dlogger](@ref dlogger) macro instead. - @sa Dtk::Core::logger + @sa Dtk::Core::dlogger */ Logger *Logger::globalInstance() { @@ -773,7 +764,7 @@ void Logger::registerAppender(AbstractAppender *appender) writing to the default category, or using special dCDebug(), dCWarning() etc. macros), Logger writes the log message only to the list of registered category appenders. - You can call logToGlobalInstance() to pass all category log messages to the global logger instance appenders + You can call logToGlobalInstance() to pass all category log messages to the global dlogger instance appenders (registered using registerAppender()). If no category appenders with specific name was registered to the Logger, it falls back to logging into the \c std::cerr STL stream, both with simple warning message. @@ -805,16 +796,16 @@ void Logger::registerCategoryAppender(const QString &category, AbstractAppender #if DTK_VERSION < DTK_VERSION_CHECK(6, 0, 0, 0) /*! @~english - @brief Links some logging category with the global logger instance appenders. + @brief Links some logging category with the global dlogger instance appenders. - If set to true, all log messages to the specified category appenders will also be written to the global logger instance appenders, + If set to true, all log messages to the specified category appenders will also be written to the global dlogger instance appenders, registered using registerAppender(). By default, all messages to the specific category are written only to the specific category appenders (registered using registerCategoryAppender()). @param category Category name - @param logToGlobal Link or onlink the category from global logger instance appender + @param logToGlobal Link or onlink the category from global dlogger instance appender @sa globalInstance @sa registerAppender @@ -831,7 +822,7 @@ void Logger::logToGlobalInstance(const QString &category, bool logToGlobal) @~english @brief Sets default logging category. - All log messages to this category appenders will also be written to general logger instance appenders (registered + All log messages to this category appenders will also be written to general dlogger instance appenders (registered using [registerAppender](@ref registerAppender) method), and vice versa. In particular, any calls to the dDebug() macro will be treated as category logging, so you needn't to specify category name using dCDebug() macro. @@ -1001,13 +992,13 @@ void Logger::write(const QDateTime &time, Logger::LogLevel level, const char *fi } else { static bool noAppendersWarningShown = false; if (!noAppendersWarningShown) { - std::cerr << "No appenders registered with logger" << std::endl; + std::cerr << "No appenders registered with dlogger" << std::endl; noAppendersWarningShown = true; } } } - // local logger instances send category messages to the global instance + // local dlogger instances send category messages to the global instance if (!logCategory.isNull() && !isGlobalInstance) globalInstance()->write(time, level, file, line, func, logCategory.toLatin1(), msg, true); @@ -1023,61 +1014,45 @@ void Logger::write(const QDateTime &time, Logger::LogLevel level, const char *fi } } -void CuteMessageLogger::write(const char *msg, ...) const -{ - va_list va; - va_start(va, msg); - m_l->write(m_level, m_file, m_line, m_function, m_category, QString::vasprintf(msg, va)); - va_end(va); -} - -void CuteMessageLogger::write(const QString &msg) const -{ - m_l->write(m_level, m_file, m_line, m_function, m_category, msg); -} - -QDebug CuteMessageLogger::write() const -{ - return m_l->write(m_level, m_file, m_line, m_function, m_category); -} - -void LoggerTimingHelper::start(const char *msg, ...) -{ - va_list va; - va_start(va, msg); - m_block = QString::vasprintf(msg, va); - va_end(va); - - m_time.start(); -} - -void LoggerTimingHelper::start(const QString &msg) -{ - m_block = msg; - m_time.start(); -} - -LoggerTimingHelper::~LoggerTimingHelper() -{ - QString message; - if (m_block.isEmpty()) - message = QString(QLatin1String("Function %1 finished in ")).arg(AbstractStringAppender::stripFunctionName(m_function)); - else - message = QString(QLatin1String("\"%1\" finished in ")).arg(m_block); - - int elapsed = m_time.elapsed(); - if (elapsed >= 10000) - message += QString(QLatin1String("%1 s.")).arg(elapsed / 1000); - else - message += QString(QLatin1String("%1 ms.")).arg(elapsed); - - m_logger->write(m_logLevel, m_file, m_line, m_function, nullptr, message); -} - Logger* loggerInstance() { return Logger::globalInstance(); } +class D_DECL_DEPRECATED CuteMessageLogger +{ + Q_DISABLE_COPY(CuteMessageLogger) +public: + Q_DECL_CONSTEXPR CuteMessageLogger(Logger *, Logger::LogLevel , + const char *, int , const char *) + { + } + + + Q_DECL_CONSTEXPR CuteMessageLogger(Logger *l, Logger::LogLevel, const char *, + int, const char *, const char *) + { + } + void write(const char *msg, ...) const; + void write(const QString &msg) const; + QDebug write() const; + +}; + +void CuteMessageLogger::write(const char */*msg*/, ...) const +{ + write(); +} + +void CuteMessageLogger::write(const QString &/*msg*/) const +{ + write(); +} + +QDebug CuteMessageLogger::write() const +{ + return QDebug(QtWarningMsg) << "DEPRECATED! rebuild your application with lastest DtkCore"; +} + DCORE_END_NAMESPACE #include "Logger.moc" diff --git a/src/log/OutputDebugAppender.cpp b/src/log/OutputDebugAppender.cpp index f22f9c2..7c87fe9 100644 --- a/src/log/OutputDebugAppender.cpp +++ b/src/log/OutputDebugAppender.cpp @@ -5,6 +5,9 @@ // Local #include "win32/OutputDebugAppender.h" +#include +#include + // STL #include @@ -41,8 +44,16 @@ void OutputDebugAppender::append(const QDateTime &time, const QString &category, const QString &msg) { - QString s = formattedString(time, level, file, line, function, category, msg); - OutputDebugStringW((LPCWSTR) s.utf16()); + auto msvclogger = spdlog::get("msvc"); + if (!msvclogger) + msvclogger = spdlog::create("msvc", true); + + Q_ASSERT(msvclogger); + + msvclogger->set_level(spdlog::level::level_enum(detailsLevel())); + const auto &formatted = formattedString(time, level, file, line, func, category, msg); + msvclogger->log(spdlog::level::level_enum(level), formatted.toStdString()); + msvclogger->flush(); } DCORE_END_NAMESPACE diff --git a/src/log/README.md b/src/log/README.md index 2f5840f..b13407b 100644 --- a/src/log/README.md +++ b/src/log/README.md @@ -47,10 +47,10 @@ int main(int argc, char* argv[]) // 1.3 log to stdout and file // DLogManager::registerFileAppender(); // DLogManager::registerConsoleAppender(); - // 2 Register your own logger format; + // 2 Register your own dlogger format; // ConsoleAppender* consoleAppender = new ConsoleAppender; // consoleAppender->setFormat("[%{type:-7}] <%{Function}> %{message}\n"); - // logger->registerAppender(consoleAppender); + // dlogger->registerAppender(consoleAppender); dInfo("Starting the application"); int result = 1; dWarning() << "Something went wrong." << "Result code is" << result; diff --git a/src/log/RollingFileAppender.cpp b/src/log/RollingFileAppender.cpp index d9c18e4..4c8c383 100644 --- a/src/log/RollingFileAppender.cpp +++ b/src/log/RollingFileAppender.cpp @@ -8,8 +8,10 @@ #include "RollingFileAppender.h" -DCORE_BEGIN_NAMESPACE +#include "rollingfilesink_p.h" +DCORE_BEGIN_NAMESPACE +extern std::string loggerName(const QFile &logFile); /*! @~english @class Dtk::Core::RollingFileAppender @@ -28,23 +30,15 @@ DCORE_BEGIN_NAMESPACE */ RollingFileAppender::RollingFileAppender(const QString &fileName) - : FileAppender(fileName), - m_logFilesLimit(0), - m_logSizeLimit(1024 * 1024 * 20) + : FileAppender(fileName) { - + setLogFilesLimit(1); + setLogSizeLimit(1024 * 1024 * 20); } void RollingFileAppender::append(const QDateTime &time, Logger::LogLevel level, const char *file, int line, const char *func, const QString &category, const QString &msg) { - - if (!m_rollOverTime.isNull() && QDateTime::currentDateTime() > m_rollOverTime) - rollOver(); - - if (size()> m_logSizeLimit) - rollOver(); - FileAppender::append(time, level, file, line , func, category, msg); } @@ -62,177 +56,52 @@ QString RollingFileAppender::datePatternString() const void RollingFileAppender::setDatePattern(DatePattern datePattern) { - setDatePatternString(QLatin1String("'.'yyyy-MM-dd-hh-mm-ss-zzz")); - QMutexLocker locker(&m_rollingMutex); m_frequency = datePattern; computeRollOverTime(); } -void RollingFileAppender::setDatePattern(const QString &datePattern) +void RollingFileAppender::setDatePattern(const QString &/*datePattern*/) { - setDatePatternString(datePattern); - computeFrequency(); - computeRollOverTime(); } -void RollingFileAppender::setDatePatternString(const QString &datePatternString) +void RollingFileAppender::setDatePatternString(const QString &/*datePatternString*/) { - QMutexLocker locker(&m_rollingMutex); - m_datePatternString = datePatternString; + } void RollingFileAppender::computeFrequency() { - QMutexLocker locker(&m_rollingMutex); - const QDateTime startTime(QDate(1999, 1, 1), QTime(0, 0)); - const QString startString = startTime.toString(m_datePatternString); - - if (startString != startTime.addSecs(60).toString(m_datePatternString)) - m_frequency = MinutelyRollover; - else if (startString != startTime.addSecs(60 * 60).toString(m_datePatternString)) - m_frequency = HourlyRollover; - else if (startString != startTime.addSecs(60 * 60 * 12).toString(m_datePatternString)) - m_frequency = HalfDailyRollover; - else if (startString != startTime.addDays(1).toString(m_datePatternString)) - m_frequency = DailyRollover; - else if (startString != startTime.addDays(7).toString(m_datePatternString)) - m_frequency = WeeklyRollover; - else if (startString != startTime.addMonths(1).toString(m_datePatternString)) - m_frequency = MonthlyRollover; - else - { - Q_ASSERT_X(false, "DailyRollingFileAppender::computeFrequency", "The pattern '%1' does not specify a frequency"); - return; - } } void RollingFileAppender::removeOldFiles() { - if (m_logFilesLimit <= 1) - return; - QFileInfo fileInfo(fileName()); - QDir logDirectory(fileInfo.absoluteDir()); - logDirectory.setFilter(QDir::Files); - logDirectory.setNameFilters(QStringList() << fileInfo.fileName() + "*"); - QFileInfoList logFiles = logDirectory.entryInfoList(); - - QMap fileDates; - for (int i = 0; i < logFiles.length(); ++i) - { - QString name = logFiles[i].fileName(); - QString suffix = name.mid(name.indexOf(fileInfo.fileName()) + fileInfo.fileName().length()); - QDateTime fileDateTime = QDateTime::fromString(suffix, datePatternString()); - - if (fileDateTime.isValid()) - fileDates.insert(fileDateTime, logFiles[i].absoluteFilePath()); - } - - QList fileDateNames = fileDates.values(); - for (int i = 0; i < fileDateNames.length() - m_logFilesLimit + 1; ++i) - QFile::remove(fileDateNames[i]); } void RollingFileAppender::computeRollOverTime() { - Q_ASSERT_X(!m_datePatternString.isEmpty(), "DailyRollingFileAppender::computeRollOverTime()", "No active date pattern"); - - QDateTime now = QDateTime::currentDateTime(); - QDate nowDate = now.date(); - QTime nowTime = now.time(); - QDateTime start; - - switch (m_frequency) - { - case MinutelyRollover: - { - start = QDateTime(nowDate, nowTime); - m_rollOverTime = start.addSecs(60); + if (auto *fs = get_sink(loggerName(fileName()))){ + return fs->set_interval(RollingInterval(m_frequency)); } - break; - case HourlyRollover: - { - start = QDateTime(nowDate, nowTime); - m_rollOverTime = start.addSecs(60*60); - } - break; - case HalfDailyRollover: - { - int hour = nowTime.hour(); - if (hour >= 12) - hour = 12; - else - hour = 0; - start = QDateTime(nowDate, nowTime); - m_rollOverTime = start.addSecs(60*60*12); - } - break; - case DailyRollover: - { - start = QDateTime(nowDate, nowTime); - m_rollOverTime = start.addDays(1); - } - break; - case WeeklyRollover: - { - // Qt numbers the week days 1..7. The week starts on Monday. - // Change it to being numbered 0..6, starting with Sunday. - int day = nowDate.dayOfWeek(); - if (day == Qt::Sunday) - day = 0; - start = QDateTime(nowDate, nowTime).addDays(-1 * day); - m_rollOverTime = start.addDays(7); - } - break; - case MonthlyRollover: - { - start = QDateTime(QDate(nowDate.year(), nowDate.month(), 1), nowTime); - m_rollOverTime = start.addMonths(1); - } - break; - default: - Q_ASSERT_X(false, "DailyRollingFileAppender::computeInterval()", "Invalid datePattern constant"); - m_rollOverTime = QDateTime::fromSecsSinceEpoch(0); - } - - m_rollOverSuffix = start.toString(m_datePatternString); - Q_ASSERT_X(now.toString(m_datePatternString) == m_rollOverSuffix, - "DailyRollingFileAppender::computeRollOverTime()", "File name changes within interval"); - Q_ASSERT_X(m_rollOverSuffix != m_rollOverTime.toString(m_datePatternString), - "DailyRollingFileAppender::computeRollOverTime()", "File name does not change with rollover"); } void RollingFileAppender::rollOver() { - Q_ASSERT_X(!m_datePatternString.isEmpty(), "DailyRollingFileAppender::rollOver()", "No active date pattern"); - QString rollOverSuffix = m_rollOverSuffix; - computeRollOverTime(); - if (rollOverSuffix == m_rollOverSuffix) - return; - - closeFile(); - - QString targetFileName = fileName() + rollOverSuffix; - QFile f(targetFileName); - if (f.exists() && !f.remove()) - return; - f.setFileName(fileName()); - if (!f.rename(targetFileName)) - return; - - openFile(); - removeOldFiles(); } void RollingFileAppender::setLogFilesLimit(int limit) { QMutexLocker locker(&m_rollingMutex); m_logFilesLimit = limit; + + if (auto *fs = get_sink(loggerName(fileName()))){ + return fs->set_max_files(std::size_t(limit)); + } } int RollingFileAppender::logFilesLimit() const @@ -245,6 +114,10 @@ void RollingFileAppender::setLogSizeLimit(int limit) { QMutexLocker locker(&m_rollingMutex); m_logSizeLimit = limit; + + if (auto *fs = get_sink(loggerName(fileName()))){ + return fs->set_max_size(std::size_t(limit)); + } } qint64 RollingFileAppender::logSizeLimit() const diff --git a/src/log/dloghelper.cpp b/src/log/dloghelper.cpp new file mode 100644 index 0000000..c7ac691 --- /dev/null +++ b/src/log/dloghelper.cpp @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "dloghelper.h" +#include "Logger.h" + +#include +#include +#include + +#include + + +DCORE_BEGIN_NAMESPACE + +class DLogHelperPrivate : public QObjectPrivate { +public: + // Functions. + explicit DLogHelperPrivate(int version = QObjectPrivateVersion) + : QObjectPrivate(version) { + + } + ~DLogHelperPrivate() { + + } + + void setContext(const QMessageLogContext &ctx) { + file = ctx.file; + line = ctx.line; + function = ctx.function; + category = ctx.category; + } + void setLevel(Logger::LogLevel lvl) { + level = lvl; + } + + const char *file = nullptr; + const char *function = nullptr; + const char *category = nullptr; + int line = 0; + Logger::LogLevel level = Logger::LogLevel::Debug; +}; + +DLogHelper::DLogHelper(Logger::LogLevel level, const QMessageLogContext &context, QObject *parent) + : QObject(*new DLogHelperPrivate, parent) +{ + d_func()->setContext(context); + d_func()->setLevel(level); +} + +DLogHelper::~DLogHelper() +{ +} + +void DLogHelper::write(const char* msg, ...) +{ + QString message; + va_list va; + va_start(va, msg); + message = QString::vasprintf(msg, va); + va_end(va); + + write(message); +} + +void DLogHelper::write(const QString &msg) +{ + Q_D(DLogHelper); + Logger::globalInstance()->write(d->level, d->file, d->line, + d->function, d->category, msg); +} + +QDebug DLogHelper::write() +{ + Q_D(DLogHelper); + return Logger::globalInstance()->write(d->level, d->file, d->line, + d->function, d->category); +} + +void DLogHelper::timing(const QString &msg, QObject *context/* = nullptr*/) +{ + if (!context) + context = this; + + QElapsedTimer *elapsedTimer = new QElapsedTimer; + elapsedTimer->start(); + QObject::connect(context, &QObject::destroyed, [elapsedTimer, msg, this](){ + QString message; + message = msg + (QLatin1String(" finished in ")); + + qint64 elapsed = elapsedTimer->elapsed(); + delete elapsedTimer; + + if (elapsed >= 10000) + message += QString::number(elapsed / 1000) + QLatin1String("s."); + else + message += QString::number(elapsed) + QLatin1String("ms."); + + write(message); + }); +} + +Logger::LogLevel DLogHelper::levelFromQtMsgType(QtMsgType mt) +{ + Logger::LogLevel level; + switch (mt) + { + case QtDebugMsg: + level = Logger::Debug; + break; +#if QT_VERSION >= 0x050500 + case QtInfoMsg: + level = Logger::Info; + break; +#endif + case QtWarningMsg: + level = Logger::Warning; + break; + case QtCriticalMsg: + level = Logger::Error; + break; + case QtFatalMsg: + level = Logger::Fatal; + break; + } + return level; +} + +QtMsgType DLogHelper::qtMsgTypeFromLogLevel(Logger::LogLevel lvl) +{ + QtMsgType mt; + switch (lvl) + { + case Logger::Debug: + mt = QtDebugMsg; + break; +#if QT_VERSION >= 0x050500 + case Logger::Info: + mt = QtInfoMsg; + break; +#endif + case Logger::Warning: + mt = QtWarningMsg; + break; + case Logger::Error: + mt = QtCriticalMsg; + break; + case Logger::Fatal: + mt = QtFatalMsg; + break; + default: + mt = QtWarningMsg; + } + return mt; +} + +DCORE_END_NAMESPACE diff --git a/src/log/log.cmake b/src/log/log.cmake index 236be45..7f23e1d 100644 --- a/src/log/log.cmake +++ b/src/log/log.cmake @@ -9,6 +9,8 @@ set(LOG_SOURCE ${CMAKE_CURRENT_LIST_DIR}/AbstractStringAppender.cpp ${CMAKE_CURRENT_LIST_DIR}/AbstractAppender.cpp ${CMAKE_CURRENT_LIST_DIR}/LogManager.cpp + ${CMAKE_CURRENT_LIST_DIR}/dloghelper.cpp + ${CMAKE_CURRENT_LIST_DIR}/rollingfilesink_p.h ) if(BUILD_WITH_SYSTEMD AND UNIX AND NOT APPLE) diff --git a/src/log/rollingfilesink_p.h b/src/log/rollingfilesink_p.h new file mode 100644 index 0000000..8a89a5b --- /dev/null +++ b/src/log/rollingfilesink_p.h @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#ifndef SPDLOG_VERSION_CHECK +#define SPDLOG_VERSION_CHECK(major, minor, patch) (major * 10000 + minor * 100 + patch) +#endif +DCORE_BEGIN_NAMESPACE + +using namespace spdlog; + +/* + * Generator of Hourly log file names in format basename.ext.YYYY-MM-DD-HH-MM-SS + */ +struct rolling_filename_calculator +{ + // Create filename for the form basename.ext.YYYY-MM-DD-HH-MM-SS + static filename_t calc_filename(const filename_t &filename, const tm &now_tm) + { +#if SPDLOG_VERSION < SPDLOG_VERSION_CHECK(1,9,0) + std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + fmt::format_to(w, SPDLOG_FILENAME_T("{}.{:04d}-{:02d}-{:02d}-{:02d}-{:02d}-{:02d}"), filename, + now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, + now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec); + return fmt::to_string(w); +#else + return fmt_lib::format(SPDLOG_FILENAME_T("{}.{:04d}-{:02d}-{:02d}-{:02d}-{:02d}-{:02d}"), filename, + now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, + now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec); +#endif + } +}; + +enum RollingInterval +{ + RI_Minutely = 0, + RI_Hourly, + RI_HalfDaily, + RI_Daily, + RI_Weekly, + RI_Monthly +}; + +/* + * Rolling file sink based on time and file size. + * If max_files > 0, retain only the last max_files and delete previous. + */ +template +class rolling_file_sink final : public sinks::base_sink +{ +public: + // create rolling file sink which rollings on given interval + rolling_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, + bool rolling_on_open = false, RollingInterval interval = RI_Daily) + : base_filename_(std::move(base_filename)) + , rolling_on_open_(rolling_on_open) + , interval_(interval) + , filenames_q_() + { + set_max_size(max_size); + set_max_files(max_files); + + auto filename = base_filename_; + file_helper_.open(filename, false); + current_size_ = file_helper_.size(); // expensive. called only once + rotation_tp_ = next_rotation_tp_(); + if (rolling_on_open && current_size_ > 0) + { + rolling_(); + current_size_ = 0; + } + } + + filename_t filename() + { + std::lock_guard lock(sinks::base_sink::mutex_); + return file_helper_.filename(); + } + size_t filesize() + { + std::lock_guard lock(sinks::base_sink::mutex_); + return file_helper_.size(); + } + void set_max_files(std::size_t max_files) + { + std::lock_guard lock(sinks::base_sink::mutex_); + if (max_files > 200000) + { + throw spdlog_ex("rolling sink constructor: max_files arg cannot exceed 200000"); + } + max_files_ = max_files; + if (max_files > 0) + init_filenames_q_(); + } + void set_max_size(std::size_t max_size) + { + std::lock_guard lock(sinks::base_sink::mutex_); + if (max_size == 0) + { + throw spdlog_ex("rolling sink constructor: max_size arg cannot be zero"); + } + max_size_ = max_size; + } + void set_interval(RollingInterval interval) + { + std::lock_guard lock(sinks::base_sink::mutex_); + interval_ = interval; + rotation_tp_ = next_rotation_tp_(); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { +#if SPDLOG_VERSION < SPDLOG_VERSION_CHECK(1,4,0) + fmt::memory_buffer formatted; +#else + memory_buf_t formatted; +#endif + sinks::base_sink::formatter_->format(msg, formatted); + auto new_size = current_size_ + formatted.size(); + + auto time = msg.time; + bool should_rolling = time >= rotation_tp_ || new_size > max_size_; + if (should_rolling) + { + file_helper_.flush(); + if (file_helper_.size() > 0) + { + rolling_(); + new_size = formatted.size(); + } + } + + file_helper_.write(formatted); + current_size_ = new_size; + + // Do the cleaning only at the end because it might throw on failure. + if (should_rolling && max_files_ > 0) + { + delete_old_(); + } + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + void init_filenames_q_() + { + filenames_q_.clear(); + QDir dir(QString::fromStdString(base_filename_)); + dir.cdUp(); + + auto namefilter = QFileInfo(base_filename_.c_str()).fileName().append("*"); + + auto fileInfos = dir.entryInfoList({ namefilter }, QDir::NoDotAndDotDot | QDir::Files, QDir::Name); + for (const auto &fi : fileInfos) { + if (fi.filePath().compare(base_filename_.c_str())) + filenames_q_.push_back(std::move(fi.filePath().toStdString())); + } + } + + tm now_tm(log_clock::time_point tp) + { + time_t tnow = log_clock::to_time_t(tp); + return spdlog::details::os::localtime(tnow); + } + + log_clock::time_point next_rotation_tp_() + { + auto now = log_clock::now(); + tm date = now_tm(now); + auto rotation_time = log_clock::from_time_t(std::mktime(&date)); + + switch (interval_) { + case RI_Minutely: + date.tm_min += 1; + break; + case RI_Hourly: + date.tm_hour += 1; + break; + case RI_HalfDaily: + date.tm_hour += 12; + break; + case RI_Daily: + date.tm_mday += 1; + break; + case RI_Weekly: + date.tm_mday += 7; + break; + case RI_Monthly: + date.tm_mon += 1; + break; + } + + rotation_time = log_clock::from_time_t(std::mktime(&date)); + return rotation_time; + } + + // Delete the file N rotations ago. + // Throw spdlog_ex on failure to delete the old file. + void delete_old_() + { + using details::os::filename_to_str; + using details::os::remove; + + // base_filename_ not in filenames_q_ + while (filenames_q_.size() > max_files_ - 1) + { + auto old_filename = std::move(filenames_q_.front()); + filenames_q_.pop_front(); + bool ok = remove(old_filename) == 0; + if (!ok) + { + filenames_q_.push_back(std::move(old_filename)); + throw(spdlog_ex("Failed removing file " + filename_to_str(old_filename), errno)); + } + } + } + + void rolling_() + { + using details::os::filename_to_str; + + file_helper_.close(); + // xxx.log == > xxx.log.YYYY-MM-DD-HH-MM-SS + auto backupName = FileNameCalc::calc_filename(base_filename_, now_tm(log_clock::now())); + if (details::os::rename(base_filename_, backupName)) + { + file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! + current_size_ = 0; + throw spdlog_ex("rolling_file_sink: failed renaming " + filename_to_str(base_filename_) + " to " + filename_to_str(backupName), errno); + } + + filenames_q_.push_back(std::move(backupName)); + rotation_tp_ = next_rotation_tp_(); + + file_helper_.reopen(true); + } + + filename_t base_filename_; + log_clock::time_point rotation_tp_; + details::file_helper file_helper_; + bool rolling_on_open_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + RollingInterval interval_; + std::list filenames_q_; +}; + +using rolling_file_sink_mt = rolling_file_sink; +using rolling_file_sink_st = rolling_file_sink; + + +// +// factory functions +// +template +inline std::shared_ptr rolling_logger_mt(const std::string &logger_name, const filename_t &filename, std::size_t max_file_size, + std::size_t max_files, bool rolling_on_open = false, RollingInterval interval = RI_Daily) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files, rolling_on_open, interval); +} + +template +inline std::shared_ptr rolling_logger_st(const std::string &logger_name, const filename_t &filename, std::size_t max_file_size, + std::size_t max_files, bool rolling_on_open = false, RollingInterval interval = RI_Daily) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files, rolling_on_open, interval); +} + +template +Sink *get_sink(const std::string &logger_name) +{ + Sink *sink = nullptr; + + auto fl = spdlog::get(logger_name); + if (!fl) + return sink; + spdlog::sink_ptr s_ptr = fl->sinks()[0]; + sink = dynamic_cast(s_ptr.get()); + + return sink; +} + +DCORE_END_NAMESPACE diff --git a/tests/ut_dlog.cpp b/tests/ut_dlog.cpp index de4dd86..58c1fde 100644 --- a/tests/ut_dlog.cpp +++ b/tests/ut_dlog.cpp @@ -37,10 +37,3 @@ TEST(ut_DLogManager, testSetInvalidLogPath) // set log file path to a dir is not supported ASSERT_NE(DLogManager::getlogFilePath(), tmp); } - -TEST(ut_DLogManager, format) -{ - QString fmt = "%{time}{yyyy-MM-dd, HH:mm:ss.zzz} %{message}\n"; - DLogManager::setLogFormat(fmt); - ASSERT_EQ(DLogManager::instance()->m_format, fmt); -} diff --git a/tests/ut_logger.cpp b/tests/ut_logger.cpp index f66889d..d23c055 100644 --- a/tests/ut_logger.cpp +++ b/tests/ut_logger.cpp @@ -5,13 +5,23 @@ #include #include #include +#include + #include "log/Logger.h" +#include "log/dloghelper.h" #include "log/FileAppender.h" #include "log/ConsoleAppender.h" #include "log/RollingFileAppender.h" DCORE_USE_NAMESPACE +static QString tmpFileName() +{ + QTemporaryFile tmp; + tmp.open(); + return tmp.fileName(); +} + class ut_Logger: public testing::Test { protected: @@ -87,8 +97,7 @@ TEST_F(ut_Logger, testGlobalInstance) TEST_F(ut_Logger, testRegisterAppender) { Logger* gLogger = Logger::globalInstance(); - - FileAppender *fileAppener = new FileAppender("/tmp/log"); + FileAppender *fileAppener = new FileAppender(tmpFileName()); if (fileAppener->detailsLevel() > Logger::Trace) fileAppener->setDetailsLevel(Logger::Trace); gLogger->registerAppender(fileAppener); @@ -109,8 +118,8 @@ TEST_F(ut_Logger, testRegisterAppender) if (rollingFileAppender->detailsLevel() > Logger::Trace) rollingFileAppender->setDetailsLevel(Logger::Trace); gLogger->registerAppender(rollingFileAppender); - rollingFileAppender->setDatePattern("'.'yyyy-MM-dd-hh-mm"); - ASSERT_TRUE(rollingFileAppender->datePatternString() == "'.'yyyy-MM-dd-hh-mm"); +// rollingFileAppender->setDatePattern("'.'yyyy-MM-dd-hh-mm"); +// ASSERT_TRUE(rollingFileAppender->datePatternString() == "'.'yyyy-MM-dd-hh-mm"); rollingFileAppender->setLogFilesLimit(2); ASSERT_TRUE(rollingFileAppender->logFilesLimit() == 2); rollingFileAppender->setDatePattern(RollingFileAppender::MinutelyRollover); @@ -127,19 +136,19 @@ TEST_F(ut_Logger, testRegisterAppender) TEST_F(ut_Logger, testRegisterCategoryAppender) { Logger* gLogger = Logger::globalInstance(); - FileAppender *fileAppener = new FileAppender("/tmp/log"); + FileAppender *fileAppener = new FileAppender(tmpFileName()); if (fileAppener->detailsLevel() > Logger::Trace) fileAppener->setDetailsLevel(Logger::Trace); - gLogger->registerCategoryAppender("test", fileAppener); + gLogger->registerCategoryAppender("testRegisterCategoryAppender", fileAppener); ASSERT_TRUE(fileAppener->size() == 0); - dCDebug("test") << "testRegisterAppender"; + dCDebug("testRegisterCategoryAppender") << "testRegisterCategoryAppender"; ASSERT_TRUE(fileAppener->size() != 0); } TEST_F(ut_Logger, testLogToGlobalInstance) { Logger* gLogger = Logger::globalInstance(); - FileAppender *fileAppener = new FileAppender("/tmp/log"); + FileAppender *fileAppener = new FileAppender(tmpFileName()); if (fileAppener->detailsLevel() > Logger::Trace) fileAppener->setDetailsLevel(Logger::Trace); gLogger->registerAppender(fileAppener); @@ -151,8 +160,8 @@ TEST_F(ut_Logger, testLogToGlobalInstance) TEST_F(ut_Logger, testSetDefaultCategory) { - m_logger->setDefaultCategory("test"); - ASSERT_EQ(m_logger->defaultCategory(), "test"); + m_logger->setDefaultCategory("testSetDefaultCategory"); + ASSERT_EQ(m_logger->defaultCategory(), "testSetDefaultCategory"); } TEST_F(ut_Logger, testDefaultCategory)