feat: use spdlog reimplement dlog

reimplement dlog
use `DLogHelper` instead of `CuteMessageLogger` `LoggerTimingHelper`
keep CuteMessage symbol for compatibility (fix undefined symbol: _ZNK3Dtk4Core17CuteMessageLogger5writeEPKcz)
This commit is contained in:
ck 2023-08-04 17:30:13 +08:00 committed by mike
parent a4eba32e2b
commit 24a29a0f9f
21 changed files with 751 additions and 437 deletions

View File

@ -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')

2
debian/control vendored
View File

@ -5,7 +5,7 @@ Maintainer: Deepin Packages Builder <packages@deepin.com>
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

View File

@ -6,3 +6,4 @@
#include "AbstractStringAppender.h"
#include "AbstractAppender.h"
#include "JournalAppender.h"
#include "dloghelper.h"

View File

@ -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<DLogManagerPrivate> d_ptr;
Q_DECLARE_PRIVATE(DLogManager)
};
DCORE_END_NAMESPACE

View File

@ -7,7 +7,6 @@
#include <QString>
#include <QDebug>
#include <QDateTime>
#include <QElapsedTimer>
#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

View File

@ -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;

View File

@ -5,52 +5,38 @@
#define DLOGGER_DEFINE_H
#include "dtkcore_global.h"
#include <QLoggingCategory>
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

37
include/log/dloghelper.h Normal file
View File

@ -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 <dtkcore_global.h>
#include "Logger.h"
#include <QString>
#include <QDebug>
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

View File

@ -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}
)

View File

@ -3,8 +3,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Local
#include "ConsoleAppender.h"
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
// STL
#include <iostream>
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

View File

@ -4,10 +4,18 @@
#include "FileAppender.h"
#include <QFileInfo>
#include "rollingfilesink_p.h"
#include <iostream>
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<rolling_file_sink_mt>(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 << "<FileAppender::append> 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

View File

@ -9,6 +9,8 @@
#include <JournalAppender.h>
#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

View File

@ -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"

View File

@ -5,6 +5,9 @@
// Local
#include "win32/OutputDebugAppender.h"
#include <spdlog/sinks/msvc_sink.h>
#include <spdlog/spdlog.h>
// STL
#include <windows.h>
@ -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<spdlog::sinks::msvc_sink_mt>("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

View File

@ -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;

View File

@ -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<QDateTime, QString> 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<QString> 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<rolling_file_sink_st>(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<rolling_file_sink_st>(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<rolling_file_sink_st>(loggerName(fileName()))){
return fs->set_max_size(std::size_t(limit));
}
}
qint64 RollingFileAppender::logSizeLimit() const

158
src/log/dloghelper.cpp Normal file
View File

@ -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 <QCoreApplication>
#include <QElapsedTimer>
#include <private/qobject_p.h>
#include <iostream>
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

View File

@ -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)

307
src/log/rollingfilesink_p.h Normal file
View File

@ -0,0 +1,307 @@
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/details/file_helper.h>
#include <spdlog/details/null_mutex.h>
#include <spdlog/fmt/fmt.h>
#include <spdlog/sinks/base_sink.h>
#include <spdlog/details/os.h>
#include <QDir>
#include <chrono>
#include <cstdio>
#include <ctime>
#include <mutex>
#include <string>
#include <dtkcore_global.h>
#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<std::is_same<filename_t::value_type, char>::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<typename Mutex, typename FileNameCalc = rolling_filename_calculator>
class rolling_file_sink final : public sinks::base_sink<Mutex>
{
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<Mutex> lock(sinks::base_sink<Mutex>::mutex_);
return file_helper_.filename();
}
size_t filesize()
{
std::lock_guard<Mutex> lock(sinks::base_sink<Mutex>::mutex_);
return file_helper_.size();
}
void set_max_files(std::size_t max_files)
{
std::lock_guard<Mutex> lock(sinks::base_sink<Mutex>::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<Mutex> lock(sinks::base_sink<Mutex>::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<Mutex> lock(sinks::base_sink<Mutex>::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<Mutex>::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<filename_t> filenames_q_;
};
using rolling_file_sink_mt = rolling_file_sink<std::mutex>;
using rolling_file_sink_st = rolling_file_sink<details::null_mutex>;
//
// factory functions
//
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> 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<rolling_file_sink_mt>(logger_name, filename, max_file_size, max_files, rolling_on_open, interval);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> 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<rolling_file_sink_st>(logger_name, filename, max_file_size, max_files, rolling_on_open, interval);
}
template<typename Sink>
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<Sink *>(s_ptr.get());
return sink;
}
DCORE_END_NAMESPACE

View File

@ -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);
}

View File

@ -5,13 +5,23 @@
#include <gtest/gtest.h>
#include <QFile>
#include <QLocale>
#include <QTemporaryFile>
#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)