ChatGPT解决这个技术问题 Extra ChatGPT

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

我使用很多 qDebug() << 语句进行调试输出。是否有任何跨平台的方式可以将调试输出重定向到文件,而无需使用 shell 脚本?我猜 open()dup2() 将在 Linux 中完成这项工作,但它可以在 Windows 中使用 MinGW 编译吗?

也许有一种 Qt 方法可以做到这一点?


M
Marco A.

您必须使用 qInstallMsgHandler 函数安装消息处理程序,然后,您可以使用 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();
}

取自 qInstallMsgHandler 的文档(我只添加了评论):

QtMsgHandler qInstallMessageHandler(QtMsgHandler 处理程序)

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

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


嘿,非常感谢。它不仅可以让我将调试输出重定向到文件,还可以让我打印更多有用的信息,比如时间戳 :)
@Septagram:没错。您可以在处理程序本身中添加一些有用的消息;您甚至可以根据您使用的 qDebugqWarningqCritical 等将不同的消息输出到不同的文件!
顺便说一句,执行实际输出的回调 - void myMessageOutput(QtMsgType type, const char *msg) - 它以什么编码接收消息?
文档链接和 API 发生了一些变化。在 Qt5 中,qInstallMsgHandler 已被弃用并被 qInstallMessageHandler(相同的想法)取代。对于 5.0,qInstallMsgHandler 位于 qt-project.org/doc/qt-5.0/qtcore/…qInstallMessageHandler 也在那里。对于 5.1,qInstallMsgHandler 被完全删除。
@Aditya:在 Qt4 中,回调只接受两个参数。所以你可以使用这个:void myMessageOutput(QtMsgType type, const char *msg) { ... }
4
4 revs, 3 users 94%

here 所有功劳归于 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();
}

案例 QtFatalMsg:...abort(); // 它会在写日志之前退出
从 QT 5 开始,应使用 qInstallMessageHandler 而不是 qInstallMsgHandler 来更改消息处理程序。
此消息处理程序不是线程安全的。如果两个线程同时发送日志消息,您将丢失日志消息(其中一个线程的 outFile.open() 将返回 false)。您可以在尝试打开文件之前锁定 QMutex,然后在关闭文件后解锁互斥锁。这是最简单的方法,但会引入线程争用。您需要查看低开销线程安全消息队列,否则您可能会更好地使用框架。
很好的解决方案。为避免每次打开文件的开销,请打开文件并在 main() 中实例化 QTextStream。并使 QTextStream 成为函数之外的 static 变量。
A
Andrew

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

谢谢@罗斯罗杰斯!

// -- 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;
}

N
Neurotransmitter

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

主.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()”。我可以省略吗?
我不建议在此设置中使用它,因为您每次都打开日志文件,因此应该关闭它。但是您可以通过某种方式更改算法,该日志文件仅在应用程序的初始化时打开一次。这样,您只需在应用退出时将其关闭一次。
谢谢!它非常有帮助。
此消息处理程序不是线程安全的。如果两个线程同时发送日志消息,您将丢失日志消息(其中一个线程的 outFile.open() 将返回 false)。您可以在尝试打开文件之前锁定 QMutex,然后在关闭文件后解锁互斥锁。这是最简单的方法,但会引入线程争用。您将需要查看低开销线程安全消息队列,否则您可能会更好地使用框架!
我同意你的看法——它远非完美。但它大部分时间都在做它的工作。无论如何,欢迎任何修改!
I
Ivan Romanov

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


m
mrts

这是一个简单的、线程安全的惯用 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,QFile 析构函数自动关闭文件。
@mrts 如果它是非静态的,那就是这种情况。