工作中为了方便调试,常常需要加入一些打印。常用 Qt 中的 QDebug / QWarning,C 和 C++ 中的 printf / cout 等等,又或者是三方库提供的标准打印接口。
大部分时候,由于这些打印相当不统一(格式和位置),并且因为 Qt 作为 GUI 框架,调试信息实在不应该直接置于 UI 之上。
接下来介绍一种能统一和标准化所有标准打印的方法( 所谓标准打印即标准输出 stdout 等),并且能够动态配置。
关键函数
QT4下
typedef void (*QtMsgHandler)(QtMsgType, const char *);
Q_CORE_EXPORT QtMsgHandler qInstallMsgHandler(QtMsgHandler);
QT5下
typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
Q_CORE_EXPORT QtMessageHandler qInstallMessageHandler(QtMessageHandler);
对于 Qt 自身的打印,捕获起来比较容易,使用 qInstallMsgHandler 或 qInstallMessageHandler() 安装一个消息处理器,它指向一个函数,其函数签名如下第一行所示. 该函数能够捕获由 Qt Debug 产生的各种类型的打印消息,然后可以在此函数集中处理。
对于三方库,只要他是标准输出,我们就可以使用一些技巧来捕获它:
这里我们需要借助一个C库函数 freopen(),其声明如下:
FILE *freopen(const char * restrict filename, const char * restrict mode, FILE * restrict stream);
该函数用于重定向输入输出流。它可以在不改变代码原貌的情况下改变输入输出环境,但使用时应当保证流是可靠的。
样例
接下来实现一个完整的,能够处理所有情况的例子:
ShowInfo类
#include "showinfo.h"
#include <cstdio>
using namespace std;
ShowInfo::ShowInfo(QObject *parent) :
QObject(parent), watchedStdoutFile(qApp->applicationDirPath() + "/test" + "/stdout")
{
edit = new QTextEdit();
stdoutFileDir = qApp->applicationDirPath() + "/test";
lineCount = 0;
fileSize = 0;
initialized = false;
count = 1;
initialized = false;
watcher = new QTimer(qApp);
}
void ShowInfo::myUpdate(){
int len = watchedStdoutFile.size();
if (len != fileSize) {
fileSize = watchedStdoutFile.size();
QString watchedMsg = QString::fromLocal8Bit(watchedStdoutFile.readAll());
if (!watchedMsg.isEmpty()) {
QStringList list = watchedMsg.split('\n');
foreach (QString msg,list) {
msg = msg.trimmed();
QString time = QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh:mm:ss:zzz] ");
if (!msg.isEmpty()) msg = time + msg;
edit->append(msg);
if (!edit->textCursor().hasSelection()) edit->moveCursor(QTextCursor::End);
if (++lineCount > 50000) {
lineCount = 0;
edit->clear();
}
}
}
}
return;
}
void ShowInfo::initializeDebugEnveriment()
{
if (!initialized) {
qRegisterMetaType<QTextCursor>("QTextCursor");
QPalette palette = edit->palette();
palette.setBrush(QPalette::Highlight, QColor(0, 120, 230));
edit->setPalette(palette);
edit->setReadOnly(true);
edit->setWindowTitle(QString("test窗口"));
edit->setWindowFlags(Qt::WindowStaysOnTopHint);
edit->resize(800, 500);
edit->show();
if (!QDir().exists(stdoutFileDir)) QDir().mkpath(stdoutFileDir);
std::freopen((stdoutFileDir + "/stdout").toLocal8Bit().data(), "w", stdout);
if (!watchedStdoutFile.open(QIODevice::ReadOnly | QIODevice::Text)){
initialized = false;
return;
}
connect(watcher,SIGNAL(timeout()), this, SLOT(myUpdate()));
watcher->start(100);
initialized = true;
}
}
void ShowInfo::countPlus(){
qDebug() << "This is Qt Debug message! Count:" << count++;
qWarning() << "Warning: " << count++;
}
void ShowInfo::countPlus2(){
std::printf("This is printf stdout message! Count: %d", count++);
std::fflush(stdout);
}
main.cpp
ShowInfo * sInfo = NULL;
void outputMessage(QtMsgType type, const char *msg)
{
QString time = QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh:mm:ss:zzz] ");
sInfo->edit->append(time + QString(msg));
if (!sInfo->edit->textCursor().hasSelection()) sInfo->edit->moveCursor(QTextCursor::End);
if (++sInfo->lineCount > 50000) {
sInfo->lineCount = 0;
sInfo->edit->clear();
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
sInfo = new ShowInfo();
sInfo->initializeDebugEnveriment();
//注册MessageHandler
qInstallMsgHandler (outputMessage);
QTimer timer;
QObject::connect(&timer, SIGNAL(timeout()), sInfo, SLOT(countPlus()));
timer.start(1000);
QTimer otherTimer;
QObject::connect(&otherTimer, SIGNAL(timeout()), sInfo, SLOT(countPlus2()));
otherTimer.start(1500);
return a.exec();
}
运行结果
结束
基于这个例子 ,日志信息就可以自由保存了。