1. 概述
在 Qt 应用程序开发中,有效的日志处理是快速排查故障的关键部分,特别是在终端机器上,收集日志进行分析几乎是唯一的手段,所以这一节我们结合Qt的日志模块来设计一个简单的日志类,实现以下几个目标:
-
日志级别控制
-
自定义日志内容
-
日志输出到文件
2. 实现
2.1 日志级别控制
Qt的日志模块本身已经具备这一能力了,qInfo、qDebug、qWarning、qCritical和qFatal代表了5个不同的日志级别:
函数 | 级别 | 备注 |
---|---|---|
qInfo | 提示信息 | |
qDebug | 调试信息 | |
qWarning | 警告信息 | |
qCritical | 严重错误 | |
qFatal | 致命错误 | 调用后会导致程序退出 |
// PS:这5个函数都有C风格和C++风格两种调用方法:
qDebug("this is a %c style debug log", 'c');
qDebug() << "this is a " << "cpp style " << "log";
然后使用QLoggingCategory来控制日志的输出,这里有四种方法:
第一种是获取程序默认的category对象,然后调用setEnable函数来设置:
// 禁用debug信息
// 五种级别对应QtDebugMsg、QtInfoMsg、QtWarningMsg、QtCriticalMsg和QtFatalMsg
QLoggingCategory::defaultCategory()->setEnabled(QtDebugMsg, false);
第二种是调用静态的setFilteRules函数:
// 禁用debug信息
// 五种级别对应debug、info、warning、critical和fatal
// 可以同时设置多个级别,用\n分割
QLoggingCategory::setFilterRules("*.debug=false");
第三种是定义QT_LOGGING_CONF环境变量,值指向一个配置文件,而内容是过滤规则,比如:
[Rules]
*=true
*.debug=false
第四种是定义QT_LOGGING_RULES环境变量,值是过滤规则,比如:
*=false;*.debug=true
注意:环境变量的设置会覆盖setFilterRules设置,而QLoggingCategory这个类也有更强大的功能,比如自定义Category,感兴趣的可以参考Qt文档。
2.2 自定义日志内容
可以使用Qt的qSetMessagePattern函数来自定义日志内容,其定义如下:
void qSetMessagePattern(const QString &pattern)
而pattern是一串由多个占位符组成的字符串,比如:
QString example="[%{time yyyy-MM-dd hh:mm:ss} %{type}] %{message}";
qSetMessagePattern(example);
// 输出的日志内容类似:
// [2024-09-29 02:06:23 debug] test logger
所有支持的占位符如下:
占位符 | 描述 |
---|---|
%{appname} | 程序名称 |
%{category} | 日志类别 |
%{file} | 文件路径 |
%{function} | 函数 |
%{line} | 源码行 |
%{message} | 消息正文 |
%{pid} | 进程号 |
%{threadid} | 线程号 |
%{type} | “debug”, “warning”, “critical”, “fatal” |
%{time process} | 自进程启动到消息打印时的秒数 |
%{time boot} | 自系统启动到消息打印时的秒数 |
%{time [format]} | 自定义格式的时间(同QDatetime的toString函数) |
而另一种方法是定义QT_MESSAGE_PATTERN环境变量,内容与qSetMessagePattern是一样的,但环境变量的优先级更高。
2.3 日志输出到文件
Qt提供了qInstallMessageHandler来安装一个日志拦截器,在拦截器里操作日志内容。由于拦截器会覆盖上面提到日志级别控制和日志内容格式,所以我们只在release模式下使用,而debug模式下仍旧使用QLoggingCategory::setFilterRules和qSetMessagePattern来控制。
创建一个logger模块,包含Logger类:
logger
- logger.pri
- logger.h
- logger.cpp
定义一个静态函数,在该函数中完成所有跟日志相关的设置,这样调用就很方便了:
// logger.h
class Logger {
public:
static void enable();
}
// logger.cpp
void Logger::enable() {
qInstallMessageHandler(logHandler);
}
在logHandler函数中进行日志级别控制、日志内容格式定义和输出到文件等操作:
void logHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
static QMutex mutex;
QMutexLocker locker(&mutex);
// only output critical log under release mode
if (QtCriticalMsg != type) return;
// create log directory
QString dirPath = QCoreApplication::applicationDirPath() + "/logs/";
QDir dir(dirPath);
if ((!dir.exists(dirPath)) && (!dir.mkpath(dirPath))) {
return;
}
QString finalMsg = QString("[%1 %2 %3 %4] %5")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz"))
.arg(type)
.arg(context.file)
.arg(context.line)
.arg(msg);
// output file
QFile file(dirPath + QDateTime::currentDateTime().toString("yyMMddhh") + ".log");
file.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream stream(&file);
stream << finalMsg << "\r\n";
file.flush();
file.close();
}
由于release模式默认不显示日志中的文件、行数和函数信息,需要在pro文件或pri文件中添加一个配置:
CONFIG(release, debug|release) {
DEFINES += QT_MESSAGELOGCONTEXT
}
这样,一个小而美的日志处理模块就完成了。
PS: 代码已经开源在github:linqiaqun/music-player: A cross platform music player (github.com) 欢迎star/fork/issue