一个完整的软件(系统)都要有一套优雅的日志方案。
发现问题
在使用Qt开发软件(系统)过程中,出于多种目的,开发者难免需要记录程序运行过程中的一些状态信息,也就是我们常说的日志。Qt 中的日志模块(qInstallMessageHandler
)配置繁杂,对于日志设计新手来说很不友好,虽然官方给出了Demo,但其繁杂程度依旧不减。
使用Golang
、Python
等语言开发过程序的开发者,应该都听说过 Loguru
,它是在流行语言领域优雅日志方案的佼佼者。在Qt C++ 开发中有没有Loguru
或者和它媲美的日志方案呢?
问题调研
在寻找好的日志记录方案前,有必要先了解清楚两个问题,我们为什么要记录日志?日志记录的要素有哪些?
为什么要记录日志?
1.开发调试
目的是开发期调试程序使用,这种日志量比较大,且没有什么实质性的意义,只应该出现在开发期,而不应该在项目上线之后输出。
2.记录用户行为
这种类型的日志,记录用户的操作行为,用于大数据分析,比如监控、风控、推荐等等。这种日志,一般是给其他团队分析使用,而且可能是多个团队,因此一般会有一定的格式要求,开发者应该按照这个格式来记录,便于其他团队的使用。当然,要记录哪些行为、操作,一般也是约定好的,因此,开发者主要是执行的角色。
3.记录程序运行情况
记录程序的运行状况,特别是非预期的行为、异常情况,这种日志,主要是给开发、维护人员使用。什么时候记录,记录什么内容,完全取决于开发人员,开发者具有高度自主性。
4.记录系统、机器状况
比如网络请求、系统CPU、内存、IO使用情况等等,这种日志主要是给运维人员使用,生成各种更直观的展现形式,配合预警系统,在系统出问题时及时预警。
日志记录的要素有哪些?
每条日志都可以被当作一个事件(event),记录了该事件发生时各种信息,这些信息大概有以下几类。
1.时间
这个时间通常是指事件发生的时间,并不是日志被打印的时间。它通常用标准时间或时间戳的形式输出。
2.位置
这个位置一般有模块位置、文件位置、函数位置、代码所在行等等,根据敏感程度的不同,我们输出位置的级别也不同。
3.级别
级别也就是日志的重要程度,主要用于不同的环境(测试、生产)下,打印不同级别的日志;不同级别的日志产生不同级别的监控报警。
4.内容
内容也就是我们要展示给日志阅读者和使用者的信息,要简明扼要地描述这个模块、函数、文件等发生了什么事情。
5.唯一标识
在大型集群系统、分布式系统中,为了快速定位日志输出点,我们需要唯一标识作为日志的主体。
6.格式化
将上述的信息按照固定的格式打印出来,或输出到文件中,就是日志的格式化。这样做的目的不仅仅是方便阅读,还能方便后期编写日志解析程序。
7.其他
根据程序、业务等特点,还可以在日志中记录(包括但不限于):错误次数
、处理进度
、IP地址
等等。
解决问题
明白了上述内容,我们可以在遵循 使用框架或模块
、不能出错
、避免敏感信息
、合理归档分类
等原则的情况下,选择一个合适的框架来达到我们的目的。
Easylogging++
如同 Loguru 一样,在 Qt C++ 中也可以有很优雅的日志方案,它就是集简单、轻量、高效、可配置等优点于一身的 **Easylogging++**。
虽然也有 C++ 版本的 Loguru ,但它的功能显然对 Qt 的支持不是很友好。于是我们选用了通用性较强的 Easylogging++ 来达到我们的目的。
入门知识
日志等级
常用的日志等级,由高到低,依次为 Error 、 Debug 、 Warning 、 Info 。
使用方式
1 | LOG(日志等级) << “日志内容”; |
支持的 Qt 变量类型
* | * | * | * | * | * |
---|---|---|---|---|---|
QString |
QByteArray |
QLatin |
QList |
QVector |
QQueue |
QSet |
QPair |
QMap |
QMultiMap |
QHash |
QMultiHash |
QLinkedList |
QStack |
QChar |
q[u]int[64] |
使用教程
1)下载或克隆 Easylogging++ 仓库源码
Github地址: https://github.com/amrayn/easyloggingpp
2) 原生 C++ 方式使用
将
src
文件夹下的easylogging++.cc
和easylogging++.h
拷贝到要应用的项目目录;初始化并输出一条日志
1
2
3
4
5
6
7
8
9
10
11
12// *.cpp
// 导入头文件并初始化
INITIALIZE_EASYLOGGINGPP
// 输出日志
int main(int argc, const char* argv[])
{
LOG(INFO) << "This is a log generated by Easylogging++.";
}注意事项
- 所有要输出的变量类型必须为标准 C++ 格式
- 初始化必须在
*.cpp
文件中进行
3)Qt 调用方式
将
src
文件夹下的easylogging++.cc
和easylogging++.h
拷贝到要应用的项目目录;配置
*.pro
文件1
2
3
4
5
6
7
8
9
10// *.pro
// 写入配置
DEFINES += ELPP_QT_LOGGING \
ELPP_FEATURE_ALL \
ELPP_STL_LOGGING \
ELPP_STRICT_SIZE_CHECK ELPP_UNICODE \
ELPP_MULTI_LOGGER_SUPPORT \
ELPP_THREAD_SAFE
初始化并输出一条日志
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// *.cpp
// 导入头文件并初始化
INITIALIZE_EASYLOGGINGPP
LogDemo::LogDemo(QWidget *parent) :
QWidget(parent),
ui(new Ui::LogDemo)
{
ui->setupUi(this);
InitLogger();
}
LogDemo::~LogDemo()
{
delete ui;
}
/** 日志初始化
* @brief LogDemo::InitLogger
*/
void LogDemo::InitLogger()
{
QString s = "This is a log generated by Easylogging++.";
LOG(INFO) << s;
}注意事项
*.pro
必须加入相应配置- 所有要输出的变量类型可以为 受支持的Qt格式或标准 C++ 格式
- 初始化必须在
*.cpp
文件中进行
更多拓展
- 独立配置文件
- 自定义 Logger
- 多线程
- 导出成动态链接库
- …