如何重定向qDebug,qWarning,qCritical等输出?


84

我在qDebug() <<调试输出中使用了很多语句。有什么跨平台的方法可以将调试输出重定向到文件,而无需使用Shell脚本?我猜想open()dup2()将在Linux中完成这项工作,但可以在Windows中与MinGW一起编译吗?

也许有Qt方法可以做到?

Answers:


120

您必须使用qInstallMsgHandler函数安装消息处理程序,然后才能QTextStream调试消息写入文件中。这是一个示例示例:

#include <QtGlobal>
#include <stdio.h>
#include <stdlib.h>

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);
        abort();
    }
}

int main(int argc, char **argv)
{
    qInstallMessageHandler(myMessageOutput); // Install the handler
    QApplication app(argc, argv);
    ...
    return app.exec();
}

摘自qInstallMsgHandler(我仅添加了评论)的文档:

在上面的示例中,该函数myMessageOutput使用stderr您可能想要替换为其他文件流的函数,或者完全重写该函数!

一旦你写并安装此功能,您所有的qDebug(以及qWarningqCritical等),消息将在处理程序被重定向到文件,您写信。


3
嘿,非常感谢。它不仅使我可以将调试输出重定向到文件,还可以让我打印更多有用的信息,例如时间戳记:)
Septagram 2011年

2
@Septagram:是的。您可以在处理程序本身中添加一些有用的消息。你甚至可以输出不同的消息到不同的文件的基础上,你用什么qDebugqWarningqCritical等!
Nawaz

1
顺便说一下,执行实际输出的回调-void myMessageOutput(QtMsgType type,const char * msg)-以什么编码接收消息?
Septagram

8
文档链接和API有所更改。qInstallMsgHandler已被弃用,并qInstallMessageHandler在Qt5中由(相同想法)取代。对于5.0 qInstallMsgHandler,请访问qt-project.org/doc/qt-5.0/qtcore/…,并且也在qInstallMessageHandler那里。对于5.1,qInstallMsgHandler已完全删除。
詹森·C

1
@Aditya:在Qt4中,回调仅接受两个参数。因此,您可以使用此功能:void myMessageOutput(QtMsgType type, const char *msg) { ... }
Nawaz

19

这里开始,所有的荣誉都归于精神

#include <QApplication>
#include <QtDebug>
#include <QFile>
#include <QTextStream>

void myMessageHandler(QtMsgType type, const QMessageLogContext &, const QString & msg)
{
    QString txt;
    switch (type) {
    case QtDebugMsg:
        txt = QString("Debug: %1").arg(msg);
        break;
    case QtWarningMsg:
        txt = QString("Warning: %1").arg(msg);
    break;
    case QtCriticalMsg:
        txt = QString("Critical: %1").arg(msg);
    break;
    case QtFatalMsg:
        txt = QString("Fatal: %1").arg(msg);
    break;
    }
    QFile outFile("log");
    outFile.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream ts(&outFile);
    ts << txt << endl;
}

int main( int argc, char * argv[] )
{
    QApplication app( argc, argv );
    qInstallMessageHandler(myMessageHandler);   
    ...
    return app.exec();
}

案例QtFatalMsg:... abort(); //它会在写日志之前退出
raidsan 2012年

从QT 5开始,qInstallMessageHandler应该代替qInstallMsgHandler更改消息处理程序。
2016年

此消息处理程序不是线程安全的。如果两个线程同时发送日志消息,则会丢失日志消息(outFile.open()对于其中一个线程将返回false)。您可以在尝试打开文件之前锁定QMutex,然后在关闭文件后将其解锁。这是最简单的方法,但是会引入线程争用。否则,您将需要查看开销较低的线程安全消息队列...并且使用框架可能会更好。
安东尼·海沃德,

9

这是一个挂钩默认消息处理程序的工作示例。

谢谢@Ross Rogers!

// -- main.cpp

// Get the default Qt message handler.
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0);

void myCustomMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // Handle the messages!

    // Call the default handler.
    (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}

int main(int argc, char *argv[])
{
    qInstallMessageHandler(myCustomMessageHandler);

    QApplication a(argc, argv);

    qDebug() << "Wello Horld!";

    return 0;
}

8

如果应用是从Qt Creator运行的,那么这是一个跨平台的解决方案,可以登录到控制台,并且可以在debug.log编译和作为独立应用运行时登录到文件。

main.cpp

#include <QApplication>
#include <QtGlobal>
#include <QtDebug>
#include <QTextStream>
#include <QTextCodec>
#include <QLocale>
#include <QTime>
#include <QFile>   

const QString logFilePath = "debug.log";
bool logToFile = false;
    
void customMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QHash<QtMsgType, QString> msgLevelHash({{QtDebugMsg, "Debug"}, {QtInfoMsg, "Info"}, {QtWarningMsg, "Warning"}, {QtCriticalMsg, "Critical"}, {QtFatalMsg, "Fatal"}});
    QByteArray localMsg = msg.toLocal8Bit();
    QTime time = QTime::currentTime();
    QString formattedTime = time.toString("hh:mm:ss.zzz");
    QByteArray formattedTimeMsg = formattedTime.toLocal8Bit();
    QString logLevelName = msgLevelHash[type];
    QByteArray logLevelMsg = logLevelName.toLocal8Bit();

    if (logToFile) {
        QString txt = QString("%1 %2: %3 (%4)").arg(formattedTime, logLevelName, msg,  context.file);
        QFile outFile(logFilePath);
        outFile.open(QIODevice::WriteOnly | QIODevice::Append);
        QTextStream ts(&outFile);
        ts << txt << endl;
        outFile.close();
    } else {
        fprintf(stderr, "%s %s: %s (%s:%u, %s)\n", formattedTimeMsg.constData(), logLevelMsg.constData(), localMsg.constData(), context.file, context.line, context.function);
        fflush(stderr);
    }

    if (type == QtFatalMsg)
        abort();
}

int main(int argc, char *argv[])
{
    QByteArray envVar = qgetenv("QTDIR");       //  check if the app is ran in Qt Creator

    if (envVar.isEmpty())
        logToFile = true;

    qInstallMessageHandler(customMessageOutput); // custom message handler for debugging

    QApplication a(argc, argv);
    // ...and the rest of 'main' follows

日志格式由QString("%1 %2: %3 (%4)").arg...(对于文件)和fprintf(stderr, "%s %s: %s (%s:%u, %s)\n"...(对于控制台)处理。

灵感:https//gist.github.com/polovik/10714049


我看到您在每个日志事件中都调用“ outFile.close()”。我可以省略吗?
分叉

我不建议在此设置中使用它,因为您每次都打开日志文件,因此应将其关闭。但是您可以通过某种方式更改算法,即在应用程序的初始化中只打开一次日志文件。这样,您只需要在应用退出时将其关闭一次即可。
神经递质

1
谢谢!这很有帮助。
亚伦

此消息处理程序不是线程安全的。如果两个线程同时发送日志消息,则会丢失日志消息(outFile.open()对于其中一个线程将返回false)。您可以在尝试打开文件之前锁定QMutex,然后在关闭文件后将其解锁。这是最简单的方法,但是会引入线程争用。否则,您将需要查看开销较小的线程安全消息队列...并且使用框架可能会更好!
安东尼·海沃德,

我确实同意您的意见,这远非完美。但是它大部分时间都在工作。无论如何,欢迎任何修改!
Neuro递质

6

好吧,我想说的是,当您需要将调试输出重定向到不同于stderr的任何东西时,便是您可以考虑使用某种日志记录工具的时候。如果您觉得自己需要,我建议从库中使用QxtLogger“ QxtLogger类是易于使用,易于扩展的日志记录工具。”Qxt


0

这是一个简单的线程安全的惯用Qt示例,用于同时登录stderr和登录:

无效的messageHandler(QtMsgType类型,const QMessageLogContext&上下文,const QString&消息)
{
    静态QMutex互斥锁;
    QMutexLocker lock(&mutex);

    静态QFile logFile(LOGFILE_LOCATION);
    静态布尔logFileIsOpen = logFile.open(QIODevice :: Append | QIODevice :: Text);

    std :: cerr << qPrintable(qFormatLogMessage(type,context,message))<< std :: endl;

    如果(logFileIsOpen){
        logFile.write(qFormatLogMessage(type,context,message).toUtf8()+'\ n');
        logFile.flush();
    }
}

qInstallMessageHandler(messageHandler)按照其他答案中所述安装它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.