【写在前面】
很多时候,我们为了方便调试,常常需要加入一些打印。
例如 Qt 中的 QDebug,C 和 C++ 中的 printf / cout 等等,又或者是三方库提供的标准打印接口。
然而大部分时候,这些打印相当不统一(格式和位置),并且因为 Qt 作为 GUI 框架,调试信息实在不应该直接置于 UI 之上。
因此,需要一种能统一和标准化所有标准打印的方法( 所谓标准打印即标准输出 stdout 等),并且能够动态配置。
【正文开始】
- 对于 Qt 自身的打印,捕获起来相当容易:
我们使用 qInstallMessageHandler() 安装一个消息处理器,它指向一个函数,其函数签名如下第一行所示:
-
typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
-
Q_CORE_EXPORT QtMessageHandler qInstallMessageHandler(QtMessageHandler);
该函数能够捕获由 Qt Debug 产生的各种类型的打印消息,然后可以在此函数集中处理。
- 对于三方库,只要他是标准输出,我们就可以使用一些技巧来捕获它:
这里我们需要借助一个C库函数 freopen(),其声明如下:
FILE *freopen(const char * restrict filename, const char * restrict mode, FILE * restrict stream);
该函数用于重定向输入输出流。它可以在不改变代码原貌的情况下改变输入输出环境,但使用时应当保证流是可靠的。
有了这些函数,接下来我们整合一下,来实现一个完整的,能够处理所有情况的函数:
-
static void initializeDebugEnveriment()
-
{
-
static
bool initialized =
false;
-
static QTextEdit *edit =
new QTextEdit;
-
static
int lineCount =
0;
-
static
const QString stdoutFileDir = qApp->
applicationDirPath() +
"/cache";
-
-
static QTimer *watcher =
new
QTimer(qApp);
-
static quint64 fileSize =
0;
-
static QFile watchedStdoutFile(stdoutFileDir + "/stdout");
-
-
if (!initialized) {
-
qRegisterMetaType<QTextCursor>(
"QTextCursor");
-
-
auto palette = edit->
palette();
-
palette.
setBrush(QPalette::Highlight,
QColor(
0,
120,
215));
-
edit->
setPalette(palette);
-
edit->
setReadOnly(
true);
-
edit->
setWindowTitle(
QStringLiteral(
"调试窗口"));
-
edit->
setWindowFlag(Qt::WindowStaysOnTopHint);
-
edit->
resize(
700,
500);
-
edit->
show();
-
-
if (!
QDir().
exists(stdoutFileDir))
QDir().
mkpath(stdoutFileDir);
-
-
std::
freopen((stdoutFileDir +
"/stdout").
toLocal8Bit().
data(),
"w", stdout);
-
watchedStdoutFile.
open(QIODevice::ReadOnly);
-
-
watcher->
start(
100);
-
QObject::
connect(watcher, &QTimer::timeout, watcher, []{
-
if (watchedStdoutFile.
size() != fileSize) {
-
fileSize = watchedStdoutFile.
size();
-
auto watchedMsg = QString::
fromLocal8Bit(watchedStdoutFile.
readAll());
-
if (!watchedMsg.
isEmpty()) {
-
auto list = watchedMsg.
split(
'\n');
-
for (
auto msg:
qAsConst(list)) {
-
msg = msg.
trimmed();
-
auto 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();
-
}
-
}
-
}
-
}
-
});
-
-
initialized =
true;
-
}
-
-
static
auto myMsgHandler = [](QtMsgType,
const QMessageLogContext &,
const QString &msg) ->
void {
-
auto time = QDateTime::
currentDateTime().
toString(
"[yyyy-MM-dd-hh:mm:ss:zzz] ");
-
edit->
append(time + msg);
-
if (!edit->
textCursor().
hasSelection()) edit->
moveCursor(QTextCursor::End);
-
if (++lineCount >
50000) {
-
lineCount =
0;
-
edit->
clear();
-
}
-
};
-
-
qInstallMessageHandler(myMsgHandler);
-
}
这里我用一个 QTextEdit 来显示所有捕获的打印,但这不是必要的,可以根据自己需求来改造。
首先,因为要将标准输出重定向到文件,所以我们要创建一个文件夹用于缓存其输出。
接着,将 stdout 重定向至一个文件中( 实际上可以是任意输出流 ),然后创建一个定时器用于监视其文件大小变化。
如果监视文件大小改变,则将内容读入( 即打印内容 ),并添加到 textEdit 中显示出来,此时就完成了标准打印的捕获。
并且,这种方法能够捕获三方库的内部打印消息。
最后,我添加了一段模拟代码来测试一下:
-
int main(int argc, char *argv[])
-
{
-
QApplication app(argc, argv);
-
-
initializeDebugEnveriment();
-
-
QTimer timer;
-
QObject::
connect(&timer, &QTimer::timeout, &timer, []{
-
static
int count =
1;
-
qDebug() <<
"This is Qt Debug message! Count:" << count++;
-
});
-
timer.
start(
1000);
-
-
QTimer otherTimer;
-
QObject::
connect(&otherTimer, &QTimer::timeout, &otherTimer, []{
-
static
int count =
1;
-
printf(
"This is printf stdout message! Count: %d", count++);
-
fflush(stdout);
-
});
-
otherTimer.
start(
1000);
-
-
return app.
exec();
-
}
效果图如下:
【结语】
现在有了这个函数,我们还可以动态控制打印窗口,即控制 textEdit 是否显示。
另外提一点,如果嫌弃麻烦,可以直接 CONFIG += console,直接使用控制台打印。当然这个方法的缺点则是不能运行时配置,并且启动时会有一个控制台窗口。
最后,源码地址:
转载:https://blog.csdn.net/u011283226/article/details/127950587