在 Qt 开发过程中,调试是不可或缺的环节,而 QDebug 作为 Qt 框架自带的调试输出工具,凭借其简洁易用、功能强大的特点,成为了开发者排查问题的得力助手。无论是基础的变量输出,还是复杂的自定义数据类型打印,QDebug 都能轻松应对。本文将从基础用法到进阶技巧,全面介绍 QDebug 的使用方法。
01 QDebug 基础用法:快速上手输出信息
QDebug 的基础用法非常简单,只需包含相关头文件并调用相应的输出函数,就能快速输出调试信息。
首先,在代码中需要包含
#include
...
int main() {
...
int num = 10;
float score = 95.5;
QString name = "Qt Developer";
qDebug() << "Number:" << num;
qDebug() << "Score:" << score;
qDebug() << "Name:" << name;
...
}
运行上述代码,在控制台中会清晰地输出相应的信息:
Number: 10
Score: 95.5
Name: "Qt Developer"
此外,qDebug() 还支持格式化输出,通过使用占位符来指定输出的格式。常用的占位符有 %d(整数)、%f(浮点数)、%s(字符串)等。
qDebug("Number: %d, Score: %.2f", num, score);
用法和printf相同,输出如下:
Number: 10, Score: 95.50
02 qDebug 输出自定义结构体
在实际开发中,我们经常需要输出自定义结构体的数据,以便查看结构体内部成员的取值。QDebug 支持通过重载 operator<< 运算符来实现自定义结构体的输出。
假设我们有一个 Person 结构体:
struct Person {
QString name;
int age;
QString address;
};
要让 QDebug 能够输出 Person 结构体,只需在结构体外部重载 operator<< 运算符:
QDebug operator<<(QDebug debug, const Person& person)
{
debug << "Person(name:" << person.name << ", age:" << person.age
<< ", address:" << person.address << ")"; return debug;
}
之后,就可以像输出基本数据类型一样输出 Person 结构体变量了:
Person person = {"Tom", 25, "Beijing"};
qDebug() << "Person Info:" << person;
输出结果如下:
Person Info: Person(name: "Tom" , age: 25 , address: "Beijing" )
03 配置日志格式
默认情况下,QDebug 的输出格式比较简单。但在实际开发中,我们可能需要在日志中包含时间、日志级别等信息,以便更好地追踪和分析问题。Qt 提供了相关的方法来配置终端日志格式。
我们可以通过 qSetMessagePattern() 函数来设置日志的输出格式,该函数接受一个字符串作为参数,字符串中可以包含各种占位符来指定日志的组成部分。
常用的占位符有:
%{appname}%{category}日志类别%{file}源文件路径%{function}函数%{line}
源文件中的行%{message}
实际消息%{pid}
PID%{threadid}
当前线程的全局 ID(如果可以获取)%{type}
"debug", "warning", "critical" or "fatal"%{time process}
消息发送时间,以进程启动以来的秒数为单位("process"是字面量)%{time boot}
消息发送时间,以系统启动以来的秒数为单位,如果可以确定("boot"是字面量)。如果无法获取自启动以来的时间,输出结果不确定(参见 QElapsedTimer::msecsSinceReference%{time [format]}
消息发生时的系统时间,通过将 format 传递给 QDateTime::toString ()进行格式化。如果未指定格式,则使用 Qt::ISODate 的格式。
消息发生时的系统时间,通过将 format 传递给 QDateTime::toString ()进行格式化。如果未指定格式,则使用 Qt::ISODate 的格式。
#include
#include
int main(int argc, char *argv[]) { QCoreApplication a(argc, argv);
qSetMessagePattern("{%time} {%type} {%function}:{%line} - {%message}");
qDebug() << "This is a debug message";
qWarning() << "This is a warning message"; qCritical() << "This is a critical message";
return a.exec();
}
输出结果如下:
[10:23:45.678] [debug] main.cpp:10 - This is a debug message[10:23:45.679] [warning] main.cpp:11 - This is a warning message[10:23:45.680] [critical] main.cpp:12 - This is a critical message
04 日志输出重定向
默认情况下,QDebug 的输出会显示在控制台中。但在某些场景下,我们可能需要将日志输出到文件、网络或其他地方,这就需要用到输出重定向功能。Qt 提供了 qInstallMessageHandler() 函数来实现自定义消息处理,从而实现输出重定向。
(一)重定向到文件
将日志输出到文件可以方便地保存日志信息,便于后续分析。以下是将日志重定向到文件的示例代码:
#include
#include
#include
#include
void fileMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
static QFile file("log.txt");
if (!file.isOpen()) {
file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); }
QTextStream out(&file);
QString level;
switch (type) {
case QtDebugMsg: level = "Debug"; break; case QtWarningMsg: level = "Warning"; break; case QtCriticalMsg: level = "Critical"; break; case QtFatalMsg: level = "Fatal"; break;
default: level = "Unknown";
}
out << "[" << QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz") << "] [" << level << "] " << context.file << ":" << context.line << " - " << msg << endl;
file.flush();
}
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
qInstallMessageHandler(fileMessageHandler);
qDebug() << "This message will be written to log.txt"; qWarning() << "This warning will also be written to log.txt";
return a.exec();
}
运行上述代码后,日志信息会被写入到当前目录下的 log.txt 文件中。
(二)重定向到其他设备
除了文件,我们还可以将日志重定向到其他设备,例如网络套接字。只需在自定义的消息处理函数中,将日志信息发送到网络即可。具体实现需要结合网络编程相关知识,这里不再详细展开。
(三)重定向到三方日志库
在大型项目中,我们可能需要更强大的日志管理能力,如日志轮转、多线程安全、远程日志收集等。此时,将 QDebug 输出重定向到三方日志库是一个不错的选择。以下是几个常用的三方日志库推荐:
spdlog
log4cpp
Poco logging
05 日志分类输出
自从Qt5.2开始就加入了日志分类功能,陆陆续续再后续版本也添加了其他特性。要为输出的日志进行分类,需要提供类型参数,所以引入了新的日志宏:qCDebug()、qCInfo()、qCWarning() 和 qCCritical()。这些宏在头文件 QLoggingCategory 中声明。
入门的用法:
QLoggingCategory category("driver.usb");
qCDebug(category) << "a debug message";
#printf用法的格式化qCDebug(category, "a debug message logged into category %s", category.categoryName());
注意类型名称"dirver.usb"的定义存在限制,仅字母数字和点。
看下构造函数:
QLoggingCategory(constchar *category, QtMsgType enableForLevel = QtDebugMsg)
需要提供一个分类的字符串名称和输出限制级别。默认值所有级别的日志都会输出。如果指定了 Warning,那么比这个级别低的日志将不会输出。比如 Debug类型的就不会输出。
另外提供了两个宏方便声明 QLoggingCategory 静态变量。
// in a header
Q_DECLARE_LOGGING_CATEGORY(driverUsb)
// in one source file
Q_LOGGING_CATEGORY(driverUsb, "driver.usb")
宏定义如下,相当于头文件里声明了一个全局函数,源文件实现这个函数,返回一个静态的 QLoggingCategory 类型变量。
#define Q_DECLARE_LOGGING_CATEGORY(name) \externconst QLoggingCategory &name();#define Q_LOGGING_CATEGORY(name, ...) \const QLoggingCategory &name() \ { \staticconst QLoggingCategory category(__VA_ARGS__); \return category; \ }
同时日志分类之后,可以全局配置,限制指定模块日志是否输出。比如:
QLoggingCategory::setFilterRules("*.debug=false\n""driver.usb.debug=true");
日志的输出规则提供多种方式配置,优先级如下:
[QLibraryInfo::DataPath]/qtlogging.ini
QtProject/qtlogging.ini
setFilterRules()
QT_LOGGING_CONF(环境变量,指定配置文件的位置)
QT_LOGGING_RULES(环境变量,多条规则分号隔开)
日志格式化的时候也可以配置日志分类:
qSetMessagePattern("%{category} %{message}");