日志是一个优秀系统不可或缺的组成部分,利用它我们可以记录系统中所产生的所有行为。本文围绕 Log4Qt,探索 C++ 中的 Log4j 技术。快速了解 Log4j 内部工作机制,并能熟练使用其各个衍生品 - Log4cpp、log4cplus、log4cxx、Log4Qt。本教程的源码下载地址:项目源代码 。
基本介绍 Log4Qt 是 Apache Log4j 的 Qt 移植版,并且保持了 API 上的一致,主要用于记录日志。
通过使用 Log4,我们可以:
控制日志的输出格式;
通过定义日志信息的级别,来更加细致地控制日志的生成过程;
控制日志信息的输出位置,例如:文件、控制台、数据库等;
……
最不可思议的是,这些都可以通过配置文件来灵活地控制,而无需修改源代码。
由于 Log4Qt 是基于 Qt 编写的,所以它也继承了 Qt 的跨平台特性。也就是说,可以将其用于 Windows、Linux、以及 MacOS 平台上。
由于 Log4Qt 的开发在 2009 年就终止了,所以其官网提供的源码仅支持 Qt4:
但值得庆祝的是,有人提供了一个兼容 Qt5 的版本:
这个升级版很棒,不但可以将 Log4Qt 源码添加至项目中,而且还可以将其编译为库,并且它还同时支持 CMake 和 qmake。
最重要的是,它还在持续升级(已逐步迈向 Qt6),并且在老版本(for Qt4)的基础上添加了很多新 Feature。
分层架构 og4Qt API 设计为分层结构,其中每一层提供了执行不同任务的不同对象,这种设计为未来的发展提供了很好的可扩展性。
Log4Qt 对象分为:
核心对象:使用框架必不可少的强制性对象。
支持对象:帮助核心对象完成重要的任务。
核心对象
Logger 对象:处于最上层,负责捕获日志信息。
Appender 对象:负责将日志信息输出到各种目的地,例如:控制台、文件、数据库等。
Layout 对象:用于控制日志输出格式,该层有助于以可读形式记录信息。
支持对象
Level 对象:定义日志信息的优先级:TRACE < DEBUG < INFO < WARN < ERROR < FATAL。
LogManager:负责从配置文件或配置类中读取初始配置参数。
Filter 对象:用于分析日志信息,并进一步决定是否需要记录信息。
ObjectRenderer:用于向传递的各种 Logger 对象提供字符串表示(在 Log4Qt 中,尚未用到)。
编译构建 下载 Log4Qt(for Qt5),进行解压缩。同时支持 CMake 和 qmake。其中,src 是需要特别关注的目录,里面包含了 Log4Qt 的所有源码。
由于代码结构已经组织好了,所以编译 Log4Qt(以 Windows 为例)非常简单:
使用 Qt Creator 打开 log4qt.pro。
执行 qmake -> 构建。
成功之后,在构建目录下会生成 log4qt.lib、log4qt.dll 以及相应的示例程序。
***注意:***如果要启用”日志输出到数据库功能”,需要修改src\log4qt\log4qt.pro,在QT += 中添加sql。
1 QT += core xml network concurrent sql
配置使用 要使用 Log4Qt,有两种方式:将 Log4Qt 源码添加至项目中,或者将其当做第三方库来使用。
将源码添加至项目中 在 Log4Qt-master/src/log4qt 目录中,有一个很重要的文件 - log4qt.pri,通过它可以很容易地将 Log4Qt 的源码添加至项目中。
仅需在 .pro(自己的工程文件)添加如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DEFINES += LOG4QT_LIBRARY LOG4QT_ROOT_PATH = $$PWD /../Log4Qt-master INCLUDEPATH += $$LOG4QT_ROOT_PATH /src \ $$LOG4QT_ROOT_PATH /src/log4qt \ $$LOG4QT_ROOT_PATH /include \ $$LOG4QT_ROOT_PATH /include/log4qt include($$LOG4QT_ROOT_PATH /src/log4qt/log4qt.pri) include($$LOG4QT_ROOT_PATH /build.pri) include($$LOG4QT_ROOT_PATH /g++.pri)
将其作为第三方库使用 上面说过,编译 Log4Qt 后,会生成相应的库。所以如果想将其作为第三方库来使用的话,也很方便。
仅需在 .pro(自己的工程文件)添加如下配置:
1 2 3 4 5 6 7 8 9 10 11 LOG4QT_ROOT_PATH = $$PWD /../Log4Qt-master LIBS += -L$$PWD /../Libs -llog4qt INCLUDEPATH += $$LOG4QT_ROOT_PATH /src \ $$LOG4QT_ROOT_PATH /src/log4qt \ $$LOG4QT_ROOT_PATH /include \ $$LOG4QT_ROOT_PATH /include/log4qt
获取 Log4Qt 中的 logger 在 Log4Qt 中,有一个很重要的类 - Logger,用于提供日志服务。那么,如何获取 logger 呢?
关于这部分,Log4Qt 中有一个简单的描述:
Request a logger by either calling Log4Qt::Logger::logger or using LOG4QT_DECLARE_QCLASS_LOGGER
其实,除了这两种方式外,还有另一种方式 LOG4QT_DECLARE_STATIC_LOGGER
可供选择。
具体参见项目源码:RequestLogger
使用 Log4Qt::Logger::logger 通过基础配置和 root logger,完成 Log4Qt 的一个简单使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <QCoreApplication> #include <log4qt/basicconfigurator.h> #include <log4qt/logger.h> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; Log4Qt::BasicConfigurator::configure (); Log4Qt::Logger* logger = Log4Qt::Logger::rootLogger (); logger->debug ("Hello, Log4Qt!" ); return a.exec (); }
输出如下:
16 [0x0000026c077bf3c0] DEBUG root - Hello, Log4Qt!
使用 LOG4QT_DECLARE_QCLASS_LOGGER 关于 LOG4QT_DECLARE_QCLASS_LOGGER
,先来了解下它是如何定义的(在 logger.h
中):
1 2 3 4 5 6 7 #define LOG4QT_DECLARE_QCLASS_LOGGER \ private : \ mutable Log4Qt::ClassLogger mLog4QtClassLogger; \ public : \ inline Log4Qt::Logger *logger() const \ { return mLog4QtClassLogger.logger(this ); } \ private :
可以看到,该宏声明了一个成员函数,用于检索使用它的类的 Logger。在第一次调用时,通过调用 Logger::logger(const char *pName)
来请求 Logger,指针被存储以在随后的调用中返回。
看一个简单的示例,了解下如何使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #ifndef MY_LOGGER_H #define MY_LOGGER_H #include <QObject> #include <log4qt/logger.h> class MyLogger : public QObject { Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER public : MyLogger () {} void debug (const QString &message) { logger ()->debug (message); } }; #endif
main.cpp 内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <QCoreApplication> #include <log4qt/basicconfigurator.h> #include "my_logger.h" int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; Log4Qt::BasicConfigurator::configure (); MyLogger logger; logger.debug ("Hello, Log4Qt!" ); return a.exec (); }
输出如下:
16 [0x00000167090c2040] DEBUG MyLogger - Hello, Log4Qt!
使用 LOG4QT_DECLARE_STATIC_LOGGER 和 LOG4QT_DECLARE_QCLASS_LOGGER
一样,LOG4QT_DECLARE_STATIC_LOGGER
也被定义在 logger.h
中:
1 2 3 4 5 static Log4Qt::Logger *FUNCTION() \ { \ static Log4Qt::Logger * p_logger(Log4Qt::Logger::logger( return p_logger; \ }
该宏声明了一个静态函数 FUNCTION
,该函数返回一个指向 Logger
的指针。在第一次调用时,通过调用 Logger::logger( #CLASS )
来请求 Logger,指针被存储以在随后的调用中返回。
使用也非常简单,对上面的 MyLogger
稍作修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef MY_LOGGER_H #define MY_LOGGER_H #include <QObject> #include <log4qt/logger.h> LOG4QT_DECLARE_STATIC_LOGGER (logger, MyLogger)class MyLogger : public QObject { Q_OBJECT public : MyLogger () {} void debug (const QString &message) { logger ()->debug (message); } }; #endif
输出如下:
16 [0x0000020574392900] DEBUG MyLogger - Hello, Log4Qt!
深入理解 rootLogger、logLogger、qtLogger 在 Log4Qt 中,你会发现有一系列的 Logger - rootLogger、logLogger、qtLogger。
具体参见项目源码:UnderstandLogger
相互关系 要了解它们之间的关系,最直接的方法就是从源码着手 - 源码面前,了无秘密!
关系链 从 Logger 类开始,查看相关源码:
1 2 3 4 5 6 7 8 9 Logger *Logger::rootLogger () { return LogManager::rootLogger (); } Logger *Logger::logger (const QString &rName) { return LogManager::logger (rName); }
可以看到,它们在内部调用的是 LogManager 类的相关接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Logger *LogManager ::rootLogger (){ return instance ()->mpLoggerRepository ->rootLogger (); } Logger *LogManager ::logger (const QString &rName ){ return instance ()->mpLoggerRepository ->logger (rName ); } inline Logger *LogManager ::logLogger (){ return logger (QLatin1String ("Log4Qt" )); } inline Logger *LogManager ::qtLogger (){ return logger (QLatin1String ("Qt" )); }
而这些最终都是由 Hierarchy 类来决定:
1 2 3 4 5 6 7 8 9 10 11 12 inline Logger *Hierarchy::rootLogger () const { return mpRootLogger; } Logger *Hierarchy::logger (const QString &rName) { QWriteLocker locker (&mObjectGuard) ; return createLogger (rName); }
由于 rootLogger() 返回的是 mpRootLogger,根据 Hierarchy 的构造函数:
1 2 3 4 5 6 7 8 Hierarchy::Hierarchy () : mObjectGuard (QReadWriteLock::Recursive), mLoggers (), mThreshold (Level::NULL_INT), mpRootLogger (logger (QString ())) { // Store root logger to allow rootLogger() to be const }
可以发现,mpRootLogger 最终也是调用了 Hierarchy::logger(const QString &rName)
,只不过传递的是一个空字符串而已。
到这里,可以很直观地得到这样一个调用关系:
Logger::rootLogger() -> LogManager::rootLogger() -> Hierarchy::logger(QString())
LogManager::logLogger() -> LogManager::logger("Log4Qt") -> Hierarchy::logger("Log4Qt")
LogManager::qtLogger() -> LogManager::logger("Qt") -> Hierarchy::logger("Qt")
等价调用 通过上述调用关系,最终定位 Hierarchy::logger(const QString &rName)
,而它内部则调用了 Hierarchy::createLogger(const QString &orgName)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Logger *Hierarchy::createLogger (const QString &orgName) { static const char binaryIndicator[] = "@@binary@@" ; QString rName (OptionConverter::classNameJavaToCpp(orgName)) ; bool needBinaryLogger = orgName.contains (binaryIndicator); if (needBinaryLogger) rName.remove (binaryIndicator); const QString name_separator = QLatin1String ("::" ); Logger *p_logger = mLoggers.value (rName, nullptr ); if (p_logger != nullptr ) return p_logger; if (rName.isEmpty ()) { p_logger = new Logger (this , Level::DEBUG_INT, QLatin1String ("root" ), nullptr ); mLoggers.insert (QString (), p_logger); return p_logger; } QString parent_name; int index = rName.lastIndexOf (name_separator); if (index >= 0 ) parent_name = rName.left (index); if (needBinaryLogger) p_logger = new BinaryLogger (this , Level::NULL_INT, rName, createLogger (parent_name)); else p_logger = new Logger (this , Level::NULL_INT, rName, createLogger (parent_name)); mLoggers.insert (rName, p_logger); return p_logger; }
根据源码可知,Logger 的创建是由形参 rName 的值来决定的:
空字符串:用于创建 rootLogger,其 name 被设置为了“root”,而 mLoggers(QHash 类型)中对应的 key 为 QString()。
非空字符串:用于创建其他 Logger(包括:logLogger、qtLogger),并将其 parentLogger 设置为 rootLogger(注意构造时的递归调用 createLogger(parent_name)
)。
所以,最终得出以下结论:
rootLogger()
:等价于 logger(QString())
,其 name()
和 objectName()
都是“root”。
logLogger()
:等价于 logger(“Log4Qt”)
,其 name()
和 objectName()
都是“Log4Qt”。
qtLogger()
:等价于 logger(“Qt”)
,其 name()
和 objectName()
都是“Qt”。
rootLogger 是根 Logger,而其他 Logger(包括:logLogger、qtLogger)的 parentLogger 是 rootLogger。
验证 实践求证,对各个 Logger 分类,并进行输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <QCoreApplication> #include <qDebug> #include <log4qt/basicconfigurator.h> #include <log4qt/logger.h> #include <log4qt/logmanager.h> int main(int argc, char *argv[]){ QCoreApplication a(argc, argv); Log4Qt::BasicConfigurator::configure(); Log4Qt::Logger *rootLogger = Log4Qt::Logger::rootLogger(); Log4Qt::Logger *rootLogger2 = Log4Qt::LogManager::rootLogger(); Log4Qt::Logger *rootLogger3 = Log4Qt::Logger::logger("" ); Log4Qt::Logger *rootLogger4 = Log4Qt::LogManager::logger("" ); Log4Qt::Logger *rootParentLogger = rootLogger->parentLogger(); qDebug() << "********** rootLogger **********" ; qDebug() << rootLogger << rootLogger2 << rootLogger3 << rootLogger4; qDebug() << "name:" << rootLogger->name() << "object name:" << rootLogger->objectName(); qDebug() << "parent logger:" << rootParentLogger; Log4Qt::Logger *logLogger = Log4Qt::LogManager::logLogger(); Log4Qt::Logger *logLogger2 = Log4Qt::Logger::logger("Log4Qt" ); Log4Qt::Logger *logLogger3 = Log4Qt::LogManager::logger("Log4Qt" ); Log4Qt::Logger *logParentLogger = logLogger->parentLogger(); qDebug() << "********** logLogger **********" ; qDebug() << logLogger << logLogger2 << logLogger3; qDebug() << "name:" << logLogger->name() << "object name:" << logLogger->objectName(); qDebug() << "parent logger:" << logParentLogger; Log4Qt::Logger *qtLogger = Log4Qt::LogManager::qtLogger(); Log4Qt::Logger *qtLogger2 = Log4Qt::Logger::logger("Qt" ); Log4Qt::Logger *qtLogger3 = Log4Qt::LogManager::logger("Qt" ); Log4Qt::Logger *qtParentLogger = qtLogger->parentLogger(); qDebug() << "********** qtLogger **********" ; qDebug() << qtLogger << qtLogger2 << qtLogger3; qDebug() << "name:" << qtLogger->name() << "object name:" << qtLogger->objectName(); qDebug() << "parent logger:" << qtParentLogger; return a.exec(); }
显然,和推断一样。。。通过各种方式获取到的 Logger 其实都是等价的,而且一直存在一个 rootLogger,它是所有 Logger 的 parentLogger。
适用场景 rootLogger 是根,而 logLogger、qtLogger 是基于特定的需求而诞生的,下面重点讲解 qtLogger。
logLogger
logLogger:用于记录内部消息的 logger
也就是说,Log4Qt 除了对外提供日志之外,它内部也用了自己的日志(即:logLogger)来记录消息。关于包内记录,官方有一个简单说明 - Logging within the package。
既然是内部操作,这部分就不做过多赘述了,了解即可。
qtLogger
qtLogger:用于记录由 qDebug()、qWarning()、qCritical() 和 qFatal() 所创建的消息。
默认情况下,这些消息处理是禁用的。可以通过调用 setHandleQtMessages(true)
来启用。一旦启用,所有的消息都将使用 qtLogger() 来记录。
除了使用 rootLogger 之外,我们再额外再使用 qInfo()
输出一条消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <QCoreApplication> #include <qDebug> #include <log4qt/logger.h> #include <log4qt/logmanager.h> #include <log4qt/ttcclayout.h> #include <log4qt/fileappender.h> #include <log4qt/loggerrepository.h> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger (); Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout (); layout->setName ("My Layout" ); layout->activateOptions (); QString file = QCoreApplication::applicationDirPath () + "/info.log" ; Log4Qt::FileAppender *fileAppender = new Log4Qt::FileAppender (layout, file, true ); fileAppender->setName ("My file appender" ); fileAppender->activateOptions (); logger->addAppender (fileAppender); logger->setLevel (Log4Qt::Level::INFO_INT); Log4Qt::LogManager::setHandleQtMessages (true ); logger->info ("This is a info message." ); qInfo () << "This is a info message too." ; logger->removeAllAppenders (); logger->loggerRepository ()->shutdown (); return a.exec (); }
运行程序,然后打开 info.log 文件。可以看到,使用 qInfo()
生成的消息也被输出到文件里了。
Log4Qt 配置 使用环境变量配置 Log4Qt 有4个环境变量:
LOG4QT_DEBUG
控制Log4Qt自身输出日志的级别,即 logLogger() 的 Level 值。如果该值是有效的 Level 字符串,则将的级别设置为该级别。如果该值不是有效的Level 字符串,则使用DEBUG_INT 。否则,使用ERROR_INT
LOG4QT_DEFAULTINITOVERRIDE
是否忽略默认的初始化(除”false”外均忽略)
LOG4QT_CONFIGURATION
用来指定初始化用的配置文件,可在代码中指定的配置文件,也可以在此指定
LOG4QT_CONFIGURATORCLASS
指定用于初始化程序包的配置程序类
使用 QSettings 配置 LOG4QT_CONFIGURATION指定的配置文件,如果不存在,则尝试读取 QSettings 中的 Log4Qt/Properties。
具体参见项目源码:QSettingsInit
下面通过代码设置 QSettings 中的 Log4Qt/Properties 中的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void MyApplication::setupLog4Qt() { QSettings s; QStringList groups = s.childGroups(); if (!groups.contains("Log4Qt" )) { // 将 Log4Qt 的日志级别设置为 INFO s.beginGroup("Log4Qt" ); s.set Value("Debug" , "INFO" ); // 配置日志输出至文件 logger.log ,使用级别 INFO s.beginGroup("Properties" ); s.set Value("log4j.rootLogger" , "INFO, logFile" ); s.set Value("log4j.appender.logFile" , "org.apache.log4j.FileAppender" ); s.set Value("log4j.appender.logFile.file" , "logger.log" ); s.set Value("log4j.appender.logFile.layout" , "org.apache.log4j.TTCCLayout" ); s.set Value("log4j.appender.logFile.layout.dateFormat" , "ISO8601" ); } }
使用 log4qt.properties 配置 读取 log4qt.properties 文件中的内容。
具体参见项目源码:PropertiesInit
在代码中配置 首先编写.properties
文件,扩展最好不要叫.properties
,现在取名为log4qt.conf
文件,编写需要的配置内容。
最后在代码中指定配置文件:
1 2 3 4 5 QString configFile = QCoreApplication ::applicationDirPath () + "/log4qt.conf" ;qDebug () << configFile ;if (QFile ::exists (configFile )) Log4Qt ::PropertyConfigurator ::configureAndWatch (configFile );
放置工作目录 如果上面的配置都不存在,则读取应用工作目录 下的 log4qt.properties 文件。
Log4Qt 日志级别 Log4Qt 定义了一系列的日志级别,每个级别都对应一种特定类型的消息事件。通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。具体参见项目源码:LogLevel
日志级别 日志级别被定义在 Level
类中,由 Level::Value
来表示(按照严重性递增排序):
级别
值
描述
NULL_INT
0
用于未指定的级别
ALL_INT
32
所有级别(包括自定义级别)
TRACE_INT
64
指比 DEBUG 更细粒度的信息事件
DEBUG_INT
96
指细粒度的信息事件(该事件对调试应用程序非常有用)
INFO_INT
128
指信息性消息(该消息突出显示了应用程序在粗粒度级别上的进展)
WARN_INT
150
指具有潜在危害的情况
ERROR_INT
182
指错误事件(该事件可能仍然允许应用程序继续运行)
FATAL_INT
214
指非常严重的错误事件(该事件可能导致应用程序中止)
OFF_INT
255
最高等级,用于关闭日志记录
虽然有这么多级别,但 Log4Qt 只建议使用四个级别,分别是:DEBUG
、INFO
、WARN
、ERROR
。
日志级别的工作方式 关于日志级别,Log4Qt 有一个核心规则:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
如果 p >= q,则在具有级别 q 的 Logger 中,会启用对级别 p 的日志请求。
要设置日志的级别,需要使用 Logger::setLevel(Level level)
。一旦设置,除 level 本身之外,比 level 高的级别日志也会被打印。
假如,要打印 level >= WARN 的消息,可以使用下述方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <QCoreApplication> #include <log4qt/basicconfigurator.h> #include <log4qt/logger.h> #include <log4qt/level.h> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; Log4Qt::BasicConfigurator::configure (); Log4Qt::Logger* logger = Log4Qt::Logger::rootLogger (); logger->setLevel (Log4Qt::Level::WARN_INT); logger->trace ("This is a trace message." ); logger->debug ("This is a debug message." ); logger->info ("This is a info message." ); logger->warn ("This is a warn message." ); logger->error ("This is a error message." ); logger->fatal ("This is a fatal message." ); return a.exec (); }
运行程序,输出如下:
19 [0x000001643d8cfb90] WARN root - This is a warn message. 20 [0x000001643d8cfb90] ERROR root - This is a error message. 20 [0x000001643d8cfb90] FATAL root - This is a fatal message.
除了 WARN 之外,级别比它高的信息也被输出了,而级别比它低的没有被输出。
使用配置文件设置级别 Log4Qt 提供了基于配置文件的级别设置,当想要更改调试级别时,无需再去更改源代码。
注意: 出于兼容性考虑,Log4j 中的的条目依然会被识别,所以 Log4Qt 中的配置完全可以参考 Log4j 来写。
Log4j 根配置语法:
1 log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
level:日志的级别(例如:DEBUG、INFO,也包含自定义级别)。通过在这里定义的级别,可以控制到应用程序中相应级别的日志信息的开关。
appenderName:指定日志信息的输出地(例如:CONSOLE、FILE),可以同时指定多个。
编写一个配置文件 - log4qt.properties
,执行与上例中使用 logger->setLevel(Log4Qt::Level::WARN_INT)
相同的任务:
1 2 3 4 5 6 7 8 9 10 11 log =E:/log4j.rootLogger =WARN, FILElog4j.appender.FILE =org.apache.log4j.FileAppenderlog4j.appender.FILE.File =${log} /out.loglog4j.appender.FILE.layout =org.apache.log4j.PatternLayoutlog4j.appender.FILE.layout.conversionPattern =%m%n
然后,使用下面的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <QCoreApplication> #include <QFile> #include <log4qt/logger.h> #include <log4qt/propertyconfigurator.h> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; QString configFile = QCoreApplication::applicationDirPath () + "/log4qt.properties" ; if (QFile::exists (configFile)) Log4Qt::PropertyConfigurator::configureAndWatch (configFile); Log4Qt::Logger* logger = Log4Qt::Logger::rootLogger (); logger->trace ("This is a trace message." ); logger->debug ("This is a debug message." ); logger->info ("This is a info message." ); logger->warn ("This is a warn message." ); logger->error ("This is a error message." ); logger->fatal ("This is a fatal message." ); return a.exec (); }
运行程序,会生成一个日志文件(路径为 E:/out.log
),内容为:
This is a warn message. This is a error message. This is a fatal message.
可以看到,输出的信息和上述示例中的一样。但是,使用配置文件显然更加灵活。
Log4Qt 日志格式化 Log4Qt 提供了各种布局对象,每个对象可以根据不同的布局对日志数据进行格式化。
通过使用这些 Layout,我们可以根据自己的喜好来格式化日志输出,自由指定日志级别、线程名称、Logger 名称、日期时间、类别等信息。
继承关系图 Log4Qt::Layout 继承关系图:
在该层次结构中,顶级类是 Layout,它是 Log4Qt API 中所有其他布局类的基类。
PatternLayout:根据一个模式字符串输出日志事件
SimpleLayout:输出日志事件的级别和消息
TTCCLayout:输出日志事件的时间、线程名称、Logger 名称和嵌套的诊断上下文信息
这些类扩展了 Layout,并根据提供的模式重写了 format()
方法来构造日志信息。
在内部,PatternLayout 和 TTCCLayout 通过 PatternFormatter 来实现格式化。当 PatternFormatter 解析模式字符串时,它会根据发现的信息创建了一个 PatternConverter 链,每个 PatternConverter 会处理 LoggingEvent 的某个成员。
转换字符 转换说明符以百分号(%
)开始,后跟转换字符。
转换字符:用于指定数据的类型,例如:类别、级别、日期、线程名称。
转换字符
含义
c{section_count}
Logger 名称。参数 section_count
可选,从 logger 名称的末尾处计数,分隔符是 "::"
。
d{format_string}
日期。参数 format_string
可选,被用于 QDateTime::toString()
。
m
消息
p
级别名称
r
启动应用程序的相对日期/时间
t
线程名称
x
NDC(嵌套的诊断上下文)名称
X
MDC(映射的诊断上下文)名称
F
文件名称
M
方法名称
L
行号
l
位置信息
n
平台相关的行分隔符(Windows:\r\n
,Linux:\n
)
%
序列 %%
输出一个百分号 %
PatternLayout 如果想生成基于模式的特定格式的日志信息,那么可以使用 PatternLayout 来进行格式化。具体参见项目源码:PatternLayout
使用 ConversionPattern 枚举 ConversionPattern 定义了两个常用的模式:
枚举
模式字符串
DEFAULT_CONVERSION_PATTERN
"%m,%n"
TTCC_CONVERSION_PATTERN
"%r [%t] %p %c %x - %m%n"
创建一个 PatternLayout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <QCoreApplication> #include <log4qt/logger.h> #include <log4qt/patternlayout.h> #include <log4qt/consoleappender.h> #include <log4qt/loggerrepository.h> int main(int argc, char *argv[]){ QCoreApplication a(argc, argv); Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger(); Log4Qt::PatternLayout *layout = new Log4Qt::PatternLayout(); layout->setHeader("Header" ); layout->setFooter("Footer" ); layout->setName("My Layout" ); layout->activateOptions(); Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET); appender->setName("My Appender" ); appender->activateOptions(); logger->addAppender(appender); logger->setLevel(Log4Qt::Level::DEBUG_INT); logger->debug("Hello, Log4Qt!" ); logger->removeAllAppenders(); logger->loggerRepository()->shutdown(); return a.exec(); }
注意: 除正文之外,还可以为日志消息指定标头和页脚。布局对象的内容类型默认为 text/plain
,如果要自定义布局并制定其他类型,需要重写 Layout 的 contentType()
方法。
默认情况下,转换模式为 DEFAULT_CONVERSION_PATTERN
,输出如下:
Header Hello, Log4Qt! Footer
现在,打开程序中的注释部分:
1 layout->setConversionPattern(Log4Qt ::PatternLayout ::TTCC_CONVERSION_PATTERN )
不仅会输出消息内容,还会输出启动时间、线程名称、logger 名称:
25 [0x0000019586423050] DEBUG root - Hello, Log4Qt!
实际上,这采用的是和 TTCCLayout 相同的模式,从名字就可以看出。
自定义模式字符串 和其他布局相比,PatternLayout 很大的一个优势在于灵活。
除了上述方式之外,还可以通过自定义模式字符串来指定日期时间:
1 layout->setConversionPattern("%d {yyyy-MM-dd hh:mm:ss} - %m%n " );
运行程序,输出如下:
2017-12-22 15:21:52 - Hello, Log4Qt!
除此之外,还可以额外为消息添加一些文本:
1 layout -> setConversionPattern("The output message is: %m%n" );
运行程序,输出如下:
The output message is: Hello, Log4Qt!
通过这种方式,我们可以自由地在转换模式中插入任何文本,使用起来相当方便。
SimpleLayout 和 PatternLayout 不同的是,SimpleLayout 只包含了日志的级别和消息内容。具体参见项目源码:SimpleLayout
创建一个 SimpleLayout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <QCoreApplication> #include <log4qt/logger.h> #include <log4qt/simplelayout.h> #include <log4qt/consoleappender.h> #include <log4qt/loggerrepository.h> int main(int argc, char *argv[]){ QCoreApplication a(argc, argv); Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger(); Log4Qt::SimpleLayout *layout = new Log4Qt::SimpleLayout(); layout->setName("My Layout" ); layout->activateOptions(); Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET); appender->setName("My Appender" ); appender->activateOptions(); logger->addAppender(appender); logger->setLevel(Log4Qt::Level::DEBUG_INT); logger->debug("Hello, Log4Qt!" ); logger->removeAllAppenders(); logger->loggerRepository()->shutdown(); return a.exec(); }
运行程序,输出如下:
DEBUG - Hello, Log4Qt!
可以看到,只包含了日志的级别和消息内容,并没有线程、Logger 等信息。
TTCCLayout TTCC 布局格式由下列字段组成:
Time:时间
Thread:线程
Category:类别
nested diagnostic Context information:嵌套的诊断上下文信息
TTCC 是它们全拼的缩写,因此而得名。另外,这四个字段中的每一个都可以单独启用或禁用。
时间格式取决于 DateFormat:
枚举
日期格式字符串
将被格式化为
NONE
"NONE"
ISO8601
"ISO8601"
yyyy-MM-dd hh:mm:ss.zzz
ABSOLUTE
"ABSOLUTE"
HH:mm:ss.zzz
DATE
"DATE"
MMM YYYY HH:mm:ss.zzzz
RELATIVE
"RELATIVE"
从程序开始时的毫秒数
具体参见项目源码:TTCCLayout
创建一个 TTCCLayout:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <QCoreApplication> #include <log4qt/logger.h> #include <log4qt/ttcclayout.h> #include <log4qt/consoleappender.h> #include <log4qt/loggerrepository.h> int main(int argc, char *argv[]){ QCoreApplication a(argc, argv); Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger(); Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout(); layout->setName("My Layout" ); layout->activateOptions(); Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET); appender->setName("My Appender" ); appender->activateOptions(); logger->addAppender(appender); logger->setLevel(Log4Qt::Level::DEBUG_INT); logger->debug("Hello, Log4Qt!" ); logger->removeAllAppenders(); logger->loggerRepository()->shutdown(); return a.exec(); }
运行程序,输出如下:
20 [0x00000280b2afe920] DEBUG root - Hello, Log4Qt!
倘若要输出日期时间,并且想禁用其中的一些信息,可以根据注释部分进行控制。打开注释部分,再次运行程序,输出如下:
2017-12-22 17:33:28.054 DEBUG - Hello, Log4Qt!
可以看到,新增了日期时间,并且 Logger、线程也被禁用了。
Log4Qt 输出重定向 Log4Qt 支持自定义输出,格式化由 Layout 完成,输出地则由 Appender 控制。
Appender 表示将日志输出到什么地方,常见的 Appender 有 Console(控制台)、File(文件)、数据库等等。
继承关系图 Log4Qt::Appender
继承关系图:
在该层次结构中,顶级类是 Appender,它是 Log4Qt API 中所有其他输出地的基类。
AppenderSkeleton:实现一般 Appender 的功能
通常来讲,自定义输出需要继承 AppenderSkeleton,并实现其中的几个方法。
DebugAppender:将日志事件附加到特定于平台的调试输出(在 Windows 上附加到 Debugger,其它系统上附加到 stderr)
ListAppender:将日志记录事件追加到列表中,供后续处理。
NullAppender:忽略所有要附加的请求
当然,最常用的是以下几个:
WriterAppender:将日志事件附加到 QTextStream
ConsoleAppender:附加到 stdout 或 stderr
FileAppender:将日志事件附加到文件
DailyRollingFileAppender:以指定的频率滚动日志文件
RollingFileAppender:在达到特定大小时滚动日志文件
输出到控制台 在项目开发阶段,往往需要在控制台中输出日志的内容,这对于调试代码来说非常方便。
具体参见项目源码:ConsoleAppender
这时,ConsoleAppender 会十分有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <QCoreApplication> #include <log4qt/logger.h> #include <log4qt/logmanager.h> #include <log4qt/ttcclayout.h> #include <log4qt/consoleappender.h> #include <log4qt/loggerrepository.h> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger (); Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout (); layout->setName ("My Layout" ); layout->activateOptions (); Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender (layout, Log4Qt::ConsoleAppender::STDOUT_TARGET); appender->setName ("My Appender" ); appender->activateOptions (); logger->addAppender (appender); logger->setLevel (Log4Qt::Level::DEBUG_INT); logger->debug ("Hello, Log4Qt!" ); logger->removeAllAppenders (); logger->loggerRepository ()->shutdown (); return a.exec (); }
其中,Logger 用于提供日志记录服务,Layout 用于控制消息的输出格式。
在程序的最后,记得关闭 Logger - 删除之前添加的所有 Appender,并关闭 LoggerRepository(以释放 Appender 持有的资源)。
输出到文件 但在实际的产品阶段,我们更希望将日志输出到指定的文件中,以便后续跟踪程序的行为、定位问题。
这时,就需要用到 FileAppender:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <QCoreApplication> #include <log4qt/logger.h> #include <log4qt/logmanager.h> #include <log4qt/ttcclayout.h> #include <log4qt/fileappender.h> #include <log4qt/loggerrepository.h> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger (); Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout (); layout->setName ("My Layout" ); layout->activateOptions (); QString file = QCoreApplication::applicationDirPath () + "/debug.log" ; Log4Qt::FileAppender *appender = new Log4Qt::FileAppender (layout, file, true ); appender->setName ("My Appender" ); appender->activateOptions (); logger->addAppender (appender); logger->setLevel (Log4Qt::Level::DEBUG_INT); logger->debug ("Hello, Log4Qt!" ); logger->removeAllAppenders (); logger->loggerRepository ()->shutdown (); return a.exec (); }
在构造 FileAppender 时,可以用第三个参数指定文件的打开方式,true 表示以 Append(追加)方式打开,false 表示以 Truncate(截断) 方式打开。除此之外,也可以使用 setAppendFile(bool append)
。
虽然 FileAppender 提供了对日志文件的支持,但都是一些最基本的操作。倘若要实现周期性生成日志文件、限制文件大小和个数等一些更高级的控制,则需要用到其派生类 - DailyRollingFileAppender 和 RollingFileAppender。
以指定的频率滚动日志文件 为了根据日期时间来定位日志,使其更加清晰易查,可以周期性生成日志文件(例如:DAILY_ROLLOVER
指定每天生成一个新文件),这由 DailyRollingFileAppender 来完成。
具体参见项目源码:DailyRollingFileAppender
DatePattern 用于指定日期模式(频率),其有效值包括:
枚举
模式字符串
描述
MINUTELY_ROLLOVER
"'.'yyyy-MM-dd-hh-mm"
每分钟
HOURLY_ROLLOVER
"'.'yyyy-MM-dd-hh"
每小时
HALFDAILY_ROLLOVER
"'.'yyyy-MM-dd-a"
每半天
DAILY_ROLLOVER
(默认值)
"'.'yyyy-MM-dd"
每天
WEEKLY_ROLLOVER
"'.'yyyy-ww"
每周
MONTHLY_ROLLOVER
"'.'yyyy-MM"
每月
注意: DatePattern 中不用处理的文字要放到单引号(''
)中,如上面的 '.'
。
为了方便测试,以最短的间隔(每分钟)生成日志文件:
可以看到,文件名后自动加上了日期时间,很容易区分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <QCoreApplication> #include <QThread> #include <QtDebug> #include <log4qt/logger.h> #include <log4qt/ttcclayout.h> #include <log4qt/dailyrollingfileappender.h> #include <log4qt/loggerrepository.h> int main(int argc, char *argv[]){ QCoreApplication a(argc, argv); qDebug() << "********** Begin **********" ; Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger(); Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout(); layout->setName("My Layout" ); layout->activateOptions(); Log4Qt::DailyRollingFileAppender *appender = new Log4Qt::DailyRollingFileAppender(); appender->setName("My Appender" ); appender->setLayout(layout); appender->setFile(QCoreApplication::applicationDirPath() + "/log.out" ); appender->setImmediateFlush(true ); appender->setThreshold(Log4Qt::Level::INFO_INT); appender->setAppendFile(true ); appender->setDatePattern(Log4Qt::DailyRollingFileAppender::MINUTELY_ROLLOVER); appender->activateOptions(); logger->addAppender(appender); logger->setLevel(Log4Qt::Level::DEBUG_INT); int count = 0 ; while (count < 10 ) { logger->info("Hello, Log4Qt!" ); QThread::sleep(30 ); ++count; } logger->removeAllAppenders(); logger->loggerRepository()->shutdown(); qDebug() << "********** End **********" ; return a.exec(); }
要设置频率,除了可以使用 setDatePattern(DatePattern datePattern)
指定一个枚举值进行设置,还可以在 DailyRollingFileAppender
的构造函数中指定一个模式字符串之外。
1 new Log4Qt::DailyRollingFileAppender (layout, file, "'.'yyyy-MM-dd-hh-mm" );
在达到特定大小时滚动日志文件 随着时间的推移,日志文件会越来越多、越来越大,倘若不进行数量和大小上的限制,最后日志将会占满整个硬盘。
RollingFileAppender 使用 MaxFileSize 和 MaxBackupIndex 来限制日志文件的大小和数量。当产生多个日志文件时,会在日志名称后面加上“.1”、“.2”、… 这样的后缀。
具体参见项目源码:RollingFileAppender
下面,来读取一个文件,并将其内容写入到我们的日志文件中:
在此过程中,限制每个日志文件的最大大小为 10 KB,最大文件数为 5 个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <QCoreApplication> #include <QtDebug> #include <log4qt/logger.h> #include <log4qt/ttcclayout.h> #include <log4qt/RollingFileAppender.h> #include <log4qt/loggerrepository.h> #include <qt_windows.h> #include <QFile> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; qDebug () << "********** Begin **********" ; Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger (); Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout (); layout->setName ("My Layout" ); layout->activateOptions (); Log4Qt::RollingFileAppender *appender = new Log4Qt::RollingFileAppender (); appender->setName ("My Appender" ); appender->setLayout (layout); appender->setFile (QCoreApplication::applicationDirPath () + "/log.out" ); appender->setImmediateFlush (true ); appender->setThreshold (Log4Qt::Level::INFO_INT); appender->setAppendFile (true ); appender->setMaxFileSize ("10KB" ); appender->setMaxBackupIndex (5 ); appender->activateOptions (); logger->addAppender (appender); logger->setLevel (Log4Qt::Level::DEBUG_INT); QString appPath = QCoreApplication::applicationDirPath (); QFile f (appPath + "/QtSrc.log" ) ; if (f.open (QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in (&f) ; QString line; while (!in.atEnd ()) { line = in.readLine (); logger->info (line); } f.close (); } else { qDebug () << "Open failed: " << f.errorString (); } logger->removeAllAppenders (); logger->loggerRepository ()->shutdown (); qDebug () << "********** End **********" ; return a.exec (); }
输出到数据库 我们也可以将日志输出到指定的数据库中,以便后续跟踪程序的行为、定位问题。
这时,就需要用到 DatabaseAppender 。注意:编译Log4Qt时要启用它。
数据库“输出源” DatabaseAppender (append log event into sql table) 数据库“输出布局” DatabaseLayout (put log event into sql table columns)
具体参见项目源码:DatabaseAppender
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <QCoreApplication> #include <QSqlQuery> #include <QtDebug> #include <log4qt/logger.h> #include <log4qt/logmanager.h> #include <log4qt/databaseappender.h> #include <log4qt/databaselayout.h> #include <log4qt/loggerrepository.h> int main (int argc, char *argv[]) { QCoreApplication a (argc, argv) ; const QString connectionName = "MyConnection" ; const QString databaseName = "MyDB.db" ; const QString tableName = "MyTable" ; const QString timeStampField = "TimeStamp" ; const QString loggeNameField = "LoggeName" ; const QString threadNameField = "ThreadName" ; const QString levelField = "Level" ; const QString messageField = "Message" ; const QString createStatement = QString ("create table %1" "(%2 varchar,%3 varchar,%4 varchar,%5 varchar,%6 varchar)" ) .arg (tableName).arg (timeStampField).arg (loggeNameField) .arg (threadNameField).arg (levelField).arg (messageField); qDebug () << "********** Begin **********" ; QSqlDatabase db = QSqlDatabase::addDatabase ("QSQLITE" , connectionName); db.setDatabaseName (databaseName); if (!db.open ()) { qDebug () << "Database open failed." ; return 0 ; } QStringList tables = db.tables (); qDebug () << "Tables: " << db.tables (); if (!tables.contains (tableName)) { qDebug () << "Table does not exist." ; QSqlQuery query (db) ; bool success = query.exec (createStatement); if (!success) { qDebug () << "Create table failed." ; return 0 ; } } Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger (); Log4Qt::DatabaseLayout *layout = new Log4Qt::DatabaseLayout (); layout->setName ("My Layout" ); layout->setTimeStampColumn (timeStampField); layout->setLoggenameColumn (loggeNameField); layout->setThreadNameColumn (threadNameField); layout->setLevelColumn (levelField); layout->setMessageColumn (messageField); layout->activateOptions (); Log4Qt::DatabaseAppender *appender = new Log4Qt::DatabaseAppender (layout); appender->setLayout (layout); appender->setName ("My Appender" ); appender->setConnection (connectionName); appender->setTable (tableName); appender->activateOptions (); logger->addAppender (appender); logger->setLevel (Log4Qt::Level::DEBUG_INT); logger->debug ("Hello, Log4Qt!" ); logger->removeAllAppenders (); logger->loggerRepository ()->shutdown (); qDebug () << "********** End **********" ; return a.exec (); }
使用自己的 Logger 在实际应用中,我们依据项目的需要,需要使用自己定义的logger
方法一.定义logger类 编写 log4qt.properties
,定义自己的 logger 名字,例如下面定义为:MyLogger
。
1 2 log4j.logger.MyLogger = DEBUG, dest
定义类 MyLogger
,其文件 my_logger.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef MY_LOGGER_H #define MY_LOGGER_H #include <QObject> #include <log4qt/logger.h> #include <log4qt/loggerrepository.h> class MyLogger : public QObject{ Q_OBJECT LOG4QT_DECLARE_QCLASS_LOGGER public : MyLogger (){} void shutdown () { logger ()->removeAllAppenders (); logger ()->loggerRepository ()->shutdown (); } }; #endif
使用,.cpp
中代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "********** Begin **********"; // 使用自定义的 logger MyLogger myLogger; // 输出信息 myLogger.logger()->debug("Hello, Log4Qt!"); myLogger.logger()->info("Hello, Qt!"); // 关闭 logger myLogger.shutdown(); qDebug() << "********** End **********"; return a.exec(); }
方法二.通过LogManager::logger获取 编写 log4qt.properties
,定义自己的 logger
名字,例如下面定义为:MyLogger1
及 MyLogger2
。
1 2 3 4 5 6 7 8 9 10 11 log4j.logger.myLogger1 =DEBUG, dest1log4j.appender.dest1 =org.apache.log4j.FileAppenderlog4j.appender.dest1.file =${logPath} /log1.outlog4j.appender.dest1.layout =org.apache.log4j.TTCCLayoutlog4j.logger.myLogger2 =DEBUG, dest2log4j.appender.dest2 =org.apache.log4j.FileAppenderlog4j.appender.dest2.file =${logPath} /log2.outlog4j.appender.dest2.layout =org.apache.log4j.TTCCLayout
代码中获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 Log4Qt::Logger *logger1 = Log4Qt::LogManager::logger ("myLogger1" ); Log4Qt::Logger *logger2 = Log4Qt::LogManager::logger ("myLogger2" ); logger1->debug ("Hello, logger1!"); logger2->debug ("Hello, logger2!"); logger1->removeAllAppenders (); logger1->loggerRepository ()->shutdown (); logger2->removeAllAppenders (); logger2->loggerRepository ()->shutdown ();