Qt 日志输出
Qt学习(3)日志输出
普通的打印输出
用 QtCreator 开发 Qt 程序时, 经常需要向控制台打印一些参数。有时候是查看对象的属性是否被正确设置,有时候是查看程序是否执行了某一段代码,或者执行了多少次这一段代码。尽管使用调试模式可以一行一行的查看代码的执行情况,也可以看到执行代码后变量的相应值,但是 Qt 的实现采用了 D 指针,它隐藏了代码的实现,在查看变量的值时不是非常的方便(另外在 Windows 平台下打开调试模式经常会出现打开 cdb 程序异常缓慢,卡在 为 ABI 'x86-windows-msvc2015-pe-64bit' 启动调试器 'CdbEngine'
, 不清楚具体的原因是什么)。
初看变量面板其实很难看到 objectName 是不是已经设置成了 test,因为没法知道存储 objectName 属性的变量名叫什么,查看 Qt setObjectName 的源代码:
// qobject.cpp
void QObject::setObjectName(const QString &name)
{
Q_D(QObject);
if (!d->extraData)
d->extraData = new QObjectPrivate::ExtraData;
if (d->extraData->objectName != name) {
d->extraData->objectName = name;
emit objectNameChanged(d->extraData->objectName, QPrivateSignal());
}
}
看到实际上 objectName 这一属性实际上是存放在 d 指针的 extraData 对象里面。
为了方便的看到属性的值,可以把属性值输出到控制台界面:
#include <QCoreApplication>
#include <QDebug>
// #include <iostream>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QObject obj;
obj.setObjectName( "test" );
// std::cout << obj.objectName().toLocal8Bit().constData(); // 显示到标准输出流(不是Creator集成的控制台)
qDebug() << obj.objectName();
return a.exec();
}
首先包含 QDebug 头文件(.pro 文件中加入 Qt += core),然后在程序中使用 qDebug()
及流运算符,就可以把 objectName
输出到控制台。
这里的 qDebug
函数实际上是 qlogging.h
中定义的宏,类似的还有 qInfo
、 qFatal
等:
// qlogging.h
#define qDebug QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug
#define qInfo QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).info
#define qWarning QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).warning
#define qCritical QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).critical
#define qFatal QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).fatal
格式化输出
个人觉得直接在程序中输出某些属性,其实对于调试简单的逻辑代码时候非常方便,所以在程序中加入了大量的 qDebug 用来输出调试信息,然而当输出信息很多的时候,很难从一堆信息中找到需要的信息,还没法定位输出信息的位置。之前不知道 Qt 的输出机制,就在每个调用 qDebug()
的地方加上一段位置信息,比如:
qDebug() << "[Debug] MyObject::test " << obj.objectName();
每个输出都要自己加上一些额外信息,一般类名或者函数名都不会变化,所以可以当作位置信息加到要输出的前面,但是你可能注意到了,代码所在的行数经常会变化,因此没办法把行数添加到输出里。
另外,如果想要在打印输出的时候加上时间戳,那怎么办呢?最开始我想可以在调用 qDebug()
的位置加上 QDateTime,后来想想还是算了,太麻烦了。
为了格式化输出,加上一些额外信息,可以使用 qSetMessagePattern(const QString &) 函数或者设置 QT_MESSAGE_PATTERN 环境变量来定制自己的输出格式,它可以修改信息处理器的默认输出,以下是官网的两种方法(可用的占位符请参考官方文档):
或者可以在 main 函数中加入以下代码:
qSetMessagePattern( "[%{time yyyyMMdd h:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}" );
在项目的构建环境中添加环境变量
QT_MESSAGE_PATTERN = [%{time yyyyMMdd h:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}
在控制台上可以看到 qDebug() << obj.objectName()
的输出变成
输出会显示文件名和行号,这是因为当前项目的构建模式选择的是 Debug
,如果选择的是 Release
模式,那么在发布正式版程序时是看不到文件名和行号的(当然也包括函数名),文件名或函数名会显示为 unknown,行号会显示为 0。
如果觉得这些格式并不好看,可以看看这篇文章,用 qt 做出漂亮的调试输出。
QT_MESSAGE_PATTERN 的优先级要比 qSetMessagePattern 的函数调用优先级高:
The pattern can also be changed at runtime by setting the QT_MESSAGE_PATTERN environment variable; if both qSetMessagePattern() is called and QT_MESSAGE_PATTERN is set, the environment variable takes precedence.
可以通过修改 QT_MESSAGE_PATTERN 环境变量在运行时修改输出格式;如果调用 qSetMessagePattern 的同时又设置了 QT_MESSAGE_PATTERN,那么这个环境变量将会生效。
另一种格式化的方式
此外还有一种格式化输出信息的方式,使用 qInstallMessageHandler
注册一个自定义的消息处理器替换掉 默认的 QtMessageHandler:
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtInfoMsg:
fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtWarningMsg:
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtCriticalMsg:
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtFatalMsg:
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
}
}
int main(int argc, char *argv[])
{
qInstallMessageHandler( myMessageOutput );
QCoreApplication a(argc, argv);
...
}
通过 qInstallMessageHandler(QtMessageHandler)
函数注册一个自定义的 MessageHandler,QtMessageHandler 的定义如下:
typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
可以看到,QtMessageHandler 并不是一个类,而是一个函数指针,函数原型满足 (QtMsgType, const QMessageLogContext &, const QString &)
的函数都可以作为 QtMessageHandler 参数传递个 qInstallMessageHandler
函数中。
注册 MessageHandler 后,输出调试信息,
// int main() {
...
QObject dobj;
dobj.setObjectName( "Debug Test" );
qDebug() << dobj.objectName();
QObject wobj;
wobj.setObjectName( "Warning Test" );
qWarning() << wobj.objectName();
QObject cobj;
cobj.setObjectName( "Critical Test" );
qCritical() << cobj.objectName();
QObject iobj;
iobj.setObjectName( "Info Test" );
qInfo() << iobj.objectName();
// QObject fobj;
// fobj.setObjectName( "Fatal Test" );
// qFatal(fobj.objectName().toLocal8Bit().constData());
// }
这里的输出格式和 myMessageOutput
函数中 fprintf
函数的格式是一致的,然而请注意,在主函数里,qSetMessagePattern
函数是没有注释掉的,然而输出忽略掉了 qSetMessagePattern
设置的格式。
导出调试信息到日志中
以上的方法虽然可以格式化输出,但是调试输出的信息只会在控制台中显示,如果想要把输出信息导出到日志文件中怎么办呢?需要自己写个类(或者定义个新的宏),把 qDebug()
替换成自己定义的函数吗?其实是不需要的,我们可以在自定义的消息处理器中将信息输出到文件中。你可能已经注意到了,官方的示例中是把所有信息用 fprintf
输出到标准错误流 stderr 中的,我们只需要改变输出流到文件中就好了。
这里我打算结合 qSetMessagePattern
和 QtMessageHandler
来实现输出调试信息到文件中。
在设置了 qSetMessagePattern
后,要想使用设置好的格式,需要调用 qFormatLogMessage()
:
Custom message handlers can use qFormatLogMessage() to take pattern into account.
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QString formatMsg = qFormatLogMessage( type, context, msg );
if( type < msgLevel ) {
return;
}
std::cout << formatMsg.toLocal8Bit().constData() << std::endl;
}
输出如下:
要把输出信息导出到日志文件中,只需要将数据导入到文件输出流就行了。
// Log::setLogFile {
file.setFileName( name );
file.open( QIODevice::WriteOnly | QIODevice::Append );
outstream.setDevice( &file );
outstream.setCodec( QTextCodec::codecForName("UTF-8") );
// }
// Log::myMessageOutput {
...
outstream << formatMsg.toLocal8Bit().constData() << "\r\n";
outstream.flush();
// }
我在使用测试程序时,发现 log.txt 文件中一直没有内容,在 outstream 输出后加上 flush 操作,就可以看到 log.txt 文件中的内容了,和之前在控制台中打印出来的是一样的:
[20180620 15:01:43.325 中国标准时间 D] main.cpp:main:93 -- "Debug Test"
[20180620 15:01:43.328 中国标准时间 W] main.cpp:main:96 -- "Warning Test"
[20180620 15:01:43.330 中国标准时间 C] main.cpp:main:99 -- "Critical Test"
[20180620 15:01:43.331 中国标准时间 I] main.cpp:main:102 -- "Info Test"
代码
代码存放在 github
参考
http://wiki.qt.io/D-Pointer/zh
https://blog.csdn.net/liang19890820/article/details/51838379
https://woboq.com/blog/nice-debug-output-with-qt.html
https://www.cnblogs.com/lvchaoshun/p/7806248.html
https://blog.csdn.net/liang19890820/article/details/51839233
http://doc.qt.io/qt-5/qtglobal.html#qSetMessagePattern
http://doc.qt.io/qt-5/qdebug.html
http://doc.qt.io/qt-5/qtglobal.html#qInstallMessageHandler
Qt 日志输出的更多相关文章
- Qt 日志输出文件
在Qt开发过程当中经常使用qDebug等一些输出来调试程序,但是到了正式发布的时候,都会被注释或者删除,采用日志输出来代替. 做过项目的童鞋可能都使用过日志功能,以便有异常错误能够快速跟踪.定 ...
- Qt之日志输出文件
在Qt开发过程当中经常使用qDebug等一些输出来调试程序,但是到了正式发布的时候,都会被注释或者删除,采用日志输出来代替. 做过项目的童鞋可能都使用过日志功能,以便有异常错误能够快速跟踪.定 ...
- Qt之日志输出窗口
来源:http://blog.sina.com.cn/s/blog_a6fb6cc90101guz0.html 继上节所讲,Qt可以很容易的将一些日志信息保存到文件中,那么日志信息如何输出到窗口呢? ...
- Qt编写调试日志输出类带网络转发(开源)
用qt开发商业程序已经九年了,陆陆续续开发过至少几十个程序,除了一些算不算项目的小工具外,大部分的程序都需要有个日志的输出功能,希望可以将程序的运行状态存储到文本文件或者数据库或者做其他处理等,qt对 ...
- Xcode 8 日志输出乱码问题
更新到Xcode 8的同学应该都遇到了这个问题:用Xcode 8运行项目,日志会疯狂的刷,就像下面这种图一样:
- 第一课 ionic 日志输出
写程序的首要问题就是要打印日志,因为只有将日志输出才能真正了解程序的运行状态. 日志输出有两种方式 1.console输出 console.log("测试一下") console. ...
- C#中使用Log4net日志输出到本地文件、Textbox或Listview
网上很多配置log4net的方法,但是排行靠前的 根本就没有说明清除,导致浪费了两个小时来搞清楚如何配置,真是无语,特写此文,给那些刚接触log4net的朋友 1.参考链接:http://blog.s ...
- Haproxy安装配置及日志输出问题
简介: 软件负载均衡一般通过两种方式来实现:基于操作系统的软负载实现和基于第三方应用的软负载实现.LVS就是基于Linux操作系统实现的一种软负载,HAProxy就是开源的并且基于第三应用实现的软负载 ...
- 使用Monitor调试Unity3D Android程序日志输出(非DDMS和ADB)
使用Monitor调试Unity3D Android程序日志输出(非DDMS和ADB) http://www.cnblogs.com/mrkelly/p/4015245.html 以往调试Androi ...
随机推荐
- inline 内联函数
1.目的: 引入内联函数的目的是为了解决程序中函数调用的效率问题. 函数的引入可以减少程序的目标代码,实现程序代码和数据的共享.但是,函数调用也会带来降低效率的问题,因为调用函数实际上将程序执行顺序转 ...
- 怎么用Shell连接VirtualBox Linux虚拟机,在Mac电脑上
问题描述 由于VirtualBox采用桥接的方式连接网络,所以不能在Mac上直接访问虚拟机. 解决思路和办法 由于不能直连,但VirtualBox支持端口转发功能,可以设定转发规则,绑定宿主机和虚拟机 ...
- python---scipy模块
一 简单介绍 SciPy是基于NumPy开发的高级模块,它提供了许多数学算法和函数的实现,用于解决科学计算中的一些标准问题.例如数值积分和微分方程求解,扩展的矩阵计算,最优化,概率分布和统计函数,甚 ...
- MySQL 5.0的my.cnf配置选项(另外一种方式分类整理)
一. mysqld程序--目录和文件 basedir = path 使用给定目录作为根目录(安装目录). Show variables like “basedir” //数据库中查看目录 da ...
- 老男孩Day4作业:员工信息查询系统
1.作业需求: (1).工信息表程序,实现增删改查操作: (2).可进行模糊查询,语法至少支持下面3种: select name,age from staff_table where ...
- C++在WINdow桌面绘制文字图形
[起因] 最近碰到一个项目,需要在电脑左面显示一些信息,因此在网上找了一些资料,成功实现在桌面绘制信息. [代码] #include "stdafx.h" #include < ...
- vue.js组件之j间的通讯一 子组件接受父祖件数据
Vue2.0的三种常用传值方式.父传子.子传父.非父子组件传值 在Vue的框架开发的项目过程中,经常会用到组件来管理不同的功能,有一些公共的组件会被提取出来.这时必然会产生一些疑问和需求?比如一个组件 ...
- 学习C/C++需要掌握哪些知识
初级阶段 1.C语言 数据类型.变量.内存布局.指针基础: 字符串.一维数组.二维数组: 一级指针,二级指针,三级指针,N级指针概念,指针数组和数组指针: 结构体.文件的使用: 动态库的封装和设计: ...
- Html背景图
<table style="height: 210px;" ><tbody><tr><td style="background- ...
- centos7初始优化
第1章 优化 1.1 修改yum源 epel源 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Cen ...