多读书多实践,勤思考善领悟

Log4Qt使用教程(Log4j for C++ 构建系统日志)

本文于1081天之前发表,文中内容可能已经过时。

日志是一个优秀系统不可或缺的组成部分,利用它我们可以记录系统中所产生的所有行为。本文围绕 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 源码根目录
LOG4QT_ROOT_PATH = $$PWD/../Log4Qt-master

# 指定编译项目时应该被搜索的 #include 目录
INCLUDEPATH += $$LOG4QT_ROOT_PATH/src \
$$LOG4QT_ROOT_PATH/src/log4qt \
$$LOG4QT_ROOT_PATH/include \
$$LOG4QT_ROOT_PATH/include/log4qt

# 将 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 源码根目录
LOG4QT_ROOT_PATH = $$PWD/../Log4Qt-master

# 指定链接到项目中的库列表
LIBS += -L$$PWD/../Libs -llog4qt

# 指定编译项目时应该被搜索的 #include 目录
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();

// 获取根 logger
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 // MY_LOGGER_H

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();

// 使用自定义的 logger
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
#define LOG4QT_DECLARE_STATIC_LOGGER(FUNCTION, CLASS) \
static Log4Qt::Logger *FUNCTION() \ { \
static Log4Qt::Logger * p_logger(Log4Qt::Logger::logger(#CLASS )); \
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 // MY_LOGGER_H

输出如下:

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
{
// QReadLocker locker(&mObjectGuard); // Constant for object lifetime
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();

// 获取 rootLogger
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;

// 获取 logLogger
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;

// 获取 qtLogger
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);

// 创建一个 layout
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout();
layout->setName("My Layout");
layout->activateOptions();

// 创建一个 fileAppender(将日志内容输出到文件中)
// true 以 Append 方式打开文件,false 以 Truncate 方式打开文件。
QString file = QCoreApplication::applicationDirPath() + "/info.log";
Log4Qt::FileAppender *fileAppender = new Log4Qt::FileAppender(layout, file, true);
fileAppender->setName("My file appender");
fileAppender->activateOptions();
// 在 root logger 上添加 fileAppender
logger->addAppender(fileAppender);

// 设置级别为 info
logger->setLevel(Log4Qt::Level::INFO_INT);
// 允许处理 Qt 消息
Log4Qt::LogManager::setHandleQtMessages(true);
// 输出信息
logger->info("This is a info message.");
qInfo() << "This is a info message too.";

// 关闭 root logger
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.setValue("Debug", "INFO");

// 配置日志输出至文件 logger.log,使用级别 INFO
s.beginGroup("Properties");
s.setValue("log4j.rootLogger", "INFO, logFile");
s.setValue("log4j.appender.logFile", "org.apache.log4j.FileAppender");
s.setValue("log4j.appender.logFile.file", "logger.log");
s.setValue("log4j.appender.logFile.layout", "org.apache.log4j.TTCCLayout");
s.setValue("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 只建议使用四个级别,分别是:DEBUGINFOWARNERROR

日志级别的工作方式

关于日志级别,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();

// 获取 rootLogger
Log4Qt::Logger* logger = Log4Qt::Logger::rootLogger();

// 设置日志级别为 WARN
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
# 定义 rootLogger
log=E:/
log4j.rootLogger=WARN, FILE

# 定义 fileAppender
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=${log}/out.log

# 为 fileAppender 定义 layout
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.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);

// 获取 rootLogger
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 继承关系图:

img

在该层次结构中,顶级类是 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);

// 创建一个 PatternLayout(根据模式字符串输出日志事件)
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
Log4Qt::PatternLayout *layout = new Log4Qt::PatternLayout();
layout->setHeader("Header"); // 设置标头信息
layout->setFooter("Footer"); // 设置页脚信息
layout->setName("My Layout");
// layout->setConversionPattern(Log4Qt::PatternLayout::TTCC_CONVERSION_PATTERN);
layout->activateOptions();

// 创建一个 ConsoleAppender(将日志内容输出到控制台上)
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);

// 创建一个 SimpleLayout(输出日志的级别和消息内容)
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
Log4Qt::SimpleLayout *layout = new Log4Qt::SimpleLayout();
layout->setName("My Layout");
layout->activateOptions();

// 创建一个 ConsoleAppender(将日志内容输出到控制台上)
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);

// 创建一个 TTCCLayout(输出时间、线程、Logger 以及消息内容)
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout();
layout->setName("My Layout");
// layout->setCategoryPrefixing(false); // 禁用 logger 名称
// layout->setContextPrinting(false); // 禁用嵌套的上下文信息
// layout->setThreadPrinting(false); // 禁用线程名
// layout->setDateFormat("ISO8601"); // 设置日期格式
layout->activateOptions();

// 创建一个 ConsoleAppender(将日志内容输出到控制台上)
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 继承关系图:

img

在该层次结构中,顶级类是 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);

// 创建一个 TTCCLayout(输出时间、线程、Logger 以及消息内容)
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout();
layout->setName("My Layout");
layout->activateOptions();

// 创建一个 ConsoleAppender(将日志内容输出到控制台上)
Log4Qt::ConsoleAppender *appender = new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET);
appender->setName("My Appender");
appender->activateOptions();
// 在 logger 上添加 appender
logger->addAppender(appender);

// 设置级别为 DEBUG
logger->setLevel(Log4Qt::Level::DEBUG_INT);

// 输出信息
logger->debug("Hello, Log4Qt!");

// 关闭 logger
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);

// 创建一个 TTCCLayout(输出时间、线程、Logger 以及消息内容)
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();
Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout();
layout->setName("My Layout");
layout->activateOptions();

// 创建一个 FileAppender(将日志内容输出到文件中)
QString file = QCoreApplication::applicationDirPath() + "/debug.log";
Log4Qt::FileAppender *appender = new Log4Qt::FileAppender(layout, file, true);
appender->setName("My Appender");
appender->activateOptions();
// 在 logger 上添加 appender
logger->addAppender(appender);

// 设置级别为 DEBUG
logger->setLevel(Log4Qt::Level::DEBUG_INT);

// 输出信息
logger->debug("Hello, Log4Qt!");

// 关闭 logger
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 **********";

// 使用 rootLogger 打印日志
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();

// 创建一个 TTCCLayout(输出时间、线程、Logger 以及消息内容)
Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout();
layout->setName("My Layout");
layout->activateOptions();

// 创建一个 DailyRollingFileAppender(以指定的频率滚动)
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); // 设置阈值级别为 INFO
appender->setAppendFile(true); // 追加的方式

// 等价于 appender->setDatePattern("'.'yyyy-MM-dd-hh-mm");
appender->setDatePattern(Log4Qt::DailyRollingFileAppender::MINUTELY_ROLLOVER); // 日期模式

appender->activateOptions();

// 在 logger 上添加 appender
logger->addAppender(appender);

// 设置级别为 DEBUG
logger->setLevel(Log4Qt::Level::DEBUG_INT);

int count = 0;
while (count < 10) {
// 输出信息
logger->info("Hello, Log4Qt!");
QThread::sleep(30);
++count;
}

// 关闭 logger
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 **********";

// 使用 rootLogger 打印日志
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();

// 创建一个 TTCCLayout(输出时间、线程、Logger 以及消息内容)
Log4Qt::TTCCLayout *layout = new Log4Qt::TTCCLayout();
layout->setName("My Layout");
layout->activateOptions();

// 创建一个 RollingFileAppender(滚动多个文件)
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); // 设置阈值级别为 INFO
appender->setAppendFile(true); // 追加的方式
// 等价于 appender->setMaximumFileSize(10 * 1024);
appender->setMaxFileSize("10KB"); // 在滚动之前设置文件的最大大小
appender->setMaxBackupIndex(5); // 设置备份索引

appender->activateOptions();

// 在 logger 上添加 appender
logger->addAppender(appender);

// 设置级别为 DEBUG
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
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;
}
}

// 创建一个 layout(用于控制日志输出格式)
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();

// 创建一个 appender(将日志内容输出到数据库)
Log4Qt::DatabaseAppender *appender = new Log4Qt::DatabaseAppender(layout);
appender->setLayout(layout);
appender->setName("My Appender");
appender->setConnection(connectionName);
appender->setTable(tableName);
appender->activateOptions();

// 在 logger 上添加 appender
logger->addAppender(appender);

// 设置级别为 DEBUG
logger->setLevel(Log4Qt::Level::DEBUG_INT);

// 输出信息
logger->debug("Hello, Log4Qt!");

// 关闭 rootLogger
logger->removeAllAppenders();
logger->loggerRepository()->shutdown();

qDebug() << "********** End **********";

return a.exec();
}

使用自己的 Logger

在实际应用中,我们依据项目的需要,需要使用自己定义的logger

方法一.定义logger类

编写 log4qt.properties,定义自己的 logger 名字,例如下面定义为:MyLogger

1
2
# 定义 MyLogger
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(){}

// 关闭 logger
void shutdown() {
logger()->removeAllAppenders();
logger()->loggerRepository()->shutdown();
}
};

#endif // MY_LOGGER_H

使用,.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 名字,例如下面定义为:MyLogger1MyLogger2

1
2
3
4
5
6
7
8
9
10
11
# 定义 myLogger1
log4j.logger.myLogger1=DEBUG, dest1
log4j.appender.dest1=org.apache.log4j.FileAppender
log4j.appender.dest1.file=${logPath}/log1.out
log4j.appender.dest1.layout=org.apache.log4j.TTCCLayout

# 定义 myLogger2
log4j.logger.myLogger2=DEBUG, dest2
log4j.appender.dest2=org.apache.log4j.FileAppender
log4j.appender.dest2.file=${logPath}/log2.out
log4j.appender.dest2.layout=org.apache.log4j.TTCCLayout

代码中获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取 Logger
Log4Qt::Logger *logger1 = Log4Qt::LogManager::logger("myLogger1");
Log4Qt::Logger *logger2 = Log4Qt::LogManager::logger("myLogger2");

// 打印信息
logger1->debug("Hello, logger1!");
logger2->debug("Hello, logger2!");

// 关闭 logger
logger1->removeAllAppenders();
logger1->loggerRepository()->shutdown();
logger2->removeAllAppenders();
logger2->loggerRepository()->shutdown();