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

105

我在调试输出中使用了很多qDebug() <<语句。有没有跨平台的方法可以将调试输出重定向到文件中,而不需要使用shell脚本?我猜想在Linux中使用open()dup2()可以完成这项工作,但是在Windows中使用MinGW编译后能否正常工作呢?

或许有一种Qt方式可以实现它吗?

7个回答

146

你需要使用qInstallMessageHandler函数安装一个消息处理程序,然后可以使用QTextStreamdebug消息写入文件。这是一个示例:

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

以下内容摘自qInstallMessageHandler的文档(我只加了注释):

在上面的示例中,函数myMessageOutput使用了stderr,您可能希望将其替换为其他文件流或完全重写此函数!

一旦您编写并安装了此函数,所有您的qDebug(以及qWarningqCritical等)消息都将被重定向到处理程序中写入的文件。


4
非常感谢。这不仅让我可以将调试输出重定向到文件中,还能够打印更有用的信息,比如时间戳 :) - Septagram
2
@Septagram:没错。你可以在处理程序本身中添加一些有用的消息;并且你甚至可以根据你使用的 qDebugqWarningqCritical 等输出不同的消息到不同的文件! - Nawaz
1
顺便问一下,实际输出的回调 - void myMessageOutput(QtMsgType type, const char *msg) - 接收到的消息是用什么编码方式? - Septagram
9
文档链接和 API 有所改变。在 Qt5 中,qInstallMsgHandler 已被弃用并替换为 qInstallMessageHandler(意思相同)。对于版本 5.0,qInstallMsgHandler 可在 http://qt-project.org/doc/qt-5.0/qtcore/qtglobal.html#qInstallMsgHandler 找到,同时也提供了 qInstallMessageHandler。但是在版本 5.1 中,qInstallMsgHandler 被完全删除了。 - Jason C
1
@Aditya:在Qt4中,回调函数只需要两个参数。因此您可以使用以下代码: void myMessageOutput(QtMsgType type, const char *msg) { ... } - Nawaz
显示剩余2条评论

29

内容来源于这里,感谢spirit的贡献。

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

1
case QtFatalMsg:...abort(); // 在写日志之前就会退出 - raidsan
1
从QT 5开始,应该使用qInstallMessageHandler而不是qInstallMsgHandler来更改消息处理程序。 - SuB
3
这个消息处理器不是线程安全的。如果两个线程同时发送日志消息(outFile.open()将对其中一个线程返回false),则会丢失日志消息。在尝试打开文件之前,您可以锁定QMutex,然后在关闭文件后解锁互斥体。这是最简单的方法,但会引入线程争用。否则,您需要查看低开销的线程安全消息队列......并且最好使用框架。 - Anthony Hayward
1
不错的解决方案。为了避免每次打开文件的开销,在main()函数内打开文件并实例化QTextStream。并将QTextStream作为一个静态变量放在函数外部。 - Paul Masri-Stone
这个版本存在一个错误:致命错误消息没有被打印出来!我已经发布了一个答案来修复这个问题,并且还融入了@PaulMasri-Stone的建议。 - Fabio says Reinstate Monica

13

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

感谢 @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;
}

11

以下是一个跨平台的解决方案,用于在从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"...(用于控制台)处理。

Inspiration:https://gist.github.com/polovik/10714049


我看到你在每个日志事件中都调用了 "outFile.close()"。我可以省略它吗? - diverger
我不建议在这种设置中这样做,因为每次打开日志文件,所以应该关闭它。但是你可以改变算法的方式,使日志文件只在应用程序初始化时打开一次。这样,当应用程序退出时,你只需要关闭它一次。 - Neurotransmitter
1
谢谢!非常有帮助。 - Aaron
此消息处理程序不是线程安全的。如果两个线程同时发送日志消息(outFile.open()将对其中一个线程返回false),则您将丢失这些消息。在尝试打开文件之前,您可以锁定QMutex,然后在关闭文件后解锁互斥量。这是最简单的方法,但它会引入线程争用。否则,您需要查看低开销的线程安全消息队列...并且您可能更好地使用框架! - Anthony Hayward
我同意你的看法——它远非完美。但大部分时间它都能完成它的工作。无论如何,任何修改都是受欢迎的! - Neurotransmitter

6

我认为当你需要将调试输出重定向到与stderr不同的地方时,你可以考虑一些日志记录工具。如果你感觉需要一个,我推荐使用QxtLogger ("QxtLogger类是一个易于使用、易于扩展的日志记录工具。")从Qxt库中。


6

这是一个简单的、线程安全的典型Qt示例,用于将日志记录到stderr和文件中:

void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
    static QMutex mutex;
    QMutexLocker lock(&mutex);
static QFile logFile(LOGFILE_LOCATION); static bool logFileIsOpen = logFile.open(QIODevice::Append | QIODevice::Text);
std::cerr << qPrintable(qFormatLogMessage(type, context, message)) << std::endl;
if (logFileIsOpen) { logFile.write(qFormatLogMessage(type, context, message).toUtf8() + '\n'); logFile.flush(); } }

通过在其他答案中描述的方式使用qInstallMessageHandler(messageHandler)进行安装。


你打开文件后不应该关闭它吗? - Curtwagner1984
1
@Curtwagner1984,QFile的析构函数会自动关闭文件。 - mrts
@mrts 如果它不是静态的,那就是这种情况。 - Rudy B
1
@RudyB,不会的。静态对象(所有具有静态存储的对象,而不仅是局部静态对象)的析构函数在main()退出或显式调用标准C库函数exit()时被调用。大多数实现中,当main()终止时,会调用exit()。所以一切都很好。 - mrts

0
基于 Autodidact's answer,有一些改动:
  • 那个答案中存在一个恶心的bug:因为每个case只设置了txt的值,而txt只在switch之后打印,所以QtFatalMsg会在打印任何内容之前abort()。这意味着致命错误不会被记录!我的版本修复了这个问题。
  • 与其每次记录消息时都打开文件并实例化QTextStream,不如在程序启动时只设置一次。这样可以减少记录消息的开销,正如Paul Masri-Stone在评论中建议的那样 in a comment
#include <QApplication>
#include <QtDebug>
#include <QFile>
#include <QTextStream>

static QTextStream output_ts;

void myMessageHandler(QtMsgType type, const QMessageLogContext&, const QString& msg)
{
    switch (type) {
    case QtDebugMsg:
        output_ts << QString("Debug: %1").arg(msg) << endl;
        break;
    case QtWarningMsg:
        output_ts << QString("Warning: %1").arg(msg) << endl;
        break;
    case QtCriticalMsg:
        output_ts << QString("Critical: %1").arg(msg) << endl;
        break;
    case QtFatalMsg:
        output_ts << QString("Fatal: %1").arg(msg) << endl;
        abort();
    }
}

int main(int argc, char* argv[])
{
    QString logfilePath = QStringLiteral("C:\\mydir\\log.txt");
    QFile outFile(logfilePath);
    outFile.open(QIODevice::WriteOnly | QIODevice::Append);
    output_ts.setDevice(&outFile);
    qInstallMessageHandler(messageHandler);
    
    QApplication app(argc, argv);
    
    ...
    return app.exec();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接