Wie leite ich die Ausgabe von qDebug, qWarning, qCritical usw. um?

84

Ich verwende viele qDebug() <<Anweisungen für die Debug-Ausgabe. Gibt es eine plattformübergreifende Möglichkeit, diese Debug-Ausgabe in eine Datei umzuleiten, ohne auf Shell-Skripte zurückzugreifen? Ich vermute, dass open () und dup2 () die Arbeit unter Linux erledigen, aber wird es mit MinGW unter Windows kompiliert funktionieren?

Und vielleicht gibt es einen Qt-Weg, dies zu tun?

Septagramm
quelle

Antworten:

120

Sie müssen einen Nachrichtenhandler mithilfe der qInstallMsgHandlerFunktion installieren und können dann QTextStreamdie Debug- Nachricht in eine Datei schreiben . Hier ist ein Beispielbeispiel:

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

Entnommen aus dem Dokument von qInstallMsgHandler(ich habe nur die Kommentare hinzugefügt):

In dem obigen Beispiel die Funktion myMessageOutputAnwendungen , stderrdie Sie vielleicht mit einem anderen Dateistrom ersetzen oder komplett neu schreiben die Funktion!

Sobald Sie diese Funktion schreiben und installieren, alle qDebug(sowie qWarning, qCriticaletc) Nachrichten würden auf die Datei , die Sie schriftlich in dem Handler weitergeleitet.

Nawaz
quelle
3
Hey, vielen Dank.
Dadurch
2
@Septagram: Genau. Sie können einige nützliche Nachrichten im Hanlder selbst hinzufügen. und Sie können sogar Ausgabe unterschiedliche Nachrichten an verschiedene Dateien, basierend auf , was Sie verwenden qDebug, qWarning, qCriticalund so weiter!
Nawaz
1
Übrigens, der Rückruf, der die eigentliche Ausgabe ausführt - void myMessageOutput (Typ QtMsgType, const char * msg) - in welcher Codierung erhält er eine Nachricht?
Septagramm
8
Die Dokumentationslinks und die API haben sich etwas geändert. qInstallMsgHandlerwurde qInstallMessageHandlerin Qt5 veraltet und durch (gleiche Idee) ersetzt. Für 5.0 qInstallMsgHandlergibt es unter qt-project.org/doc/qt-5.0/qtcore/… und qInstallMessageHandlerist auch da. Für 5.1 qInstallMsgHandlerwurde komplett entfernt.
Jason C
1
@Aditya: In Qt4 akzeptiert der Rückruf nur zwei Argumente. So können Sie dies verwenden:void myMessageOutput(QtMsgType type, const char *msg) { ... }
Nawaz
19

Von hier aus geht alle Ehre an den Geist .

#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();
}
Sandeep Datta
quelle
case QtFatalMsg: ... abort (); // es wird beendet, bevor das Protokoll geschrieben wird
raidsan
Ab QT 5 qInstallMessageHandlersollte verwendet werden, anstatt qInstallMsgHandlerden Nachrichtenhandler zu ändern.
SuB
Dieser Nachrichtenhandler ist nicht threadsicher. Sie verlieren Protokollnachrichten, wenn sie von zwei Threads gleichzeitig gesendet werden (outFile.open () gibt für einen der Threads false zurück). Sie können einen QMutex sperren, bevor Sie versuchen, die Datei zu öffnen, und den Mutex nach dem Schließen der Datei entsperren. Dies ist der einfachste Ansatz, führt jedoch zu Thread-Konflikten. Andernfalls müssen Sie sich die Thread-sichere Nachrichtenwarteschlange mit geringem Overhead ansehen ... und Sie sollten möglicherweise ein Framework verwenden.
Anthony Hayward
9

Hier ist ein funktionierendes Beispiel für das Einbinden des Standardnachrichtenhandlers.

Vielen Dank an 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;
}
Andrew
quelle
8

Hier ist eine plattformübergreifende Lösung, um sich bei der Konsole anzumelden, wenn die App von Qt Creator ausgeführt wurde, und bei der debug.logDatei, wenn sie kompiliert und als eigenständige App ausgeführt wird.

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

Die Protokollformatierung wird von QString("%1 %2: %3 (%4)").arg...(für die Datei) und fprintf(stderr, "%s %s: %s (%s:%u, %s)\n"...(für die Konsole) durchgeführt.

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

Neurotransmitter
quelle
Ich sehe, dass Sie in jedem Protokollereignis "outFile.close ()" aufrufen. Darf ich es weglassen?
Diverger
Ich empfehle es in diesem Setup nicht, da Sie die Protokolldatei jedes Mal öffnen und sie daher geschlossen werden sollte. Sie können den Algorithmus jedoch so ändern, dass die Protokolldatei nur einmal beim Start der App geöffnet wird. Auf diese Weise müssen Sie es nur einmal schließen, wenn die App beendet wird.
Neurotransmitter
1
Vielen Dank! Es ist sehr hilfreich.
Aaron
Dieser Nachrichtenhandler ist nicht threadsicher. Sie verlieren Protokollnachrichten, wenn sie von zwei Threads gleichzeitig gesendet werden (outFile.open () gibt für einen der Threads false zurück). Sie können einen QMutex sperren, bevor Sie versuchen, die Datei zu öffnen, und den Mutex nach dem Schließen der Datei entsperren. Dies ist der einfachste Ansatz, führt jedoch zu Thread-Konflikten. Andernfalls müssen Sie sich die Thread-sichere Nachrichtenwarteschlange mit geringem Overhead ansehen ... und Sie sollten möglicherweise ein Framework verwenden!
Anthony Hayward
Ich stimme Ihnen zu - es ist alles andere als perfekt. Aber es macht seine Arbeit die meiste Zeit. Änderungen sind auf jeden Fall willkommen!
Neurotransmitter
6

Nun, ich würde sagen, dass der Moment, in dem Sie Ihre Debug-Ausgabe auf etwas anderes als stderr umleiten müssen, der Moment ist, in dem Sie über ein Protokollierungstool nachdenken könnten. Wenn Sie der Meinung sind, dass Sie eines benötigen, würde ich die Verwendung QxtLogger( "Die QxtLogger-Klasse ist ein einfach zu verwendendes und einfach zu erweiterndes Protokollierungswerkzeug." ) Aus der QxtBibliothek empfehlen .

Piotr Dobrogost
quelle
0

Hier ist ein einfaches, thread-sicheres, idiomatisches Qt-Beispiel, in dem Sie sich sowohl in stderrals auch in einer Datei anmelden können :

void messageHandler (Typ QtMsgType, const QMessageLogContext & context, const QString & message)
{
    statischer QMutex-Mutex;
    QMutexLocker-Sperre (& mutex);

    statische QFile logFile (LOGFILE_LOCATION);
    statisch bool logFileIsOpen = logFile.open (QIODevice :: Append | QIODevice :: Text);

    std :: cerr << qPrintable (qFormatLogMessage (Typ, Kontext, Nachricht)) << std :: endl;

    if (logFileIsOpen) {
        logFile.write (qFormatLogMessage (Typ, Kontext, Nachricht) .toUtf8 () + '\ n');
        logFile.flush ();
    }}
}}

Installieren Sie es mit qInstallMessageHandler(messageHandler)wie in anderen Antworten beschrieben.

mrts
quelle