Jak przekierować wyjście qDebug, qWarning, qCritical itp.?


85

Używam wielu qDebug() <<instrukcji do debugowania. Czy istnieje międzyplatformowy sposób przekierowania wyników debugowania do pliku bez uciekania się do skryptów powłoki? Zgaduję, że open () i dup2 () wykonają zadanie w Linuksie, ale czy będą działać skompilowane z MinGW w Windows?

A może jest na to sposób Qt?

Odpowiedzi:


121

Musisz zainstalować program obsługi komunikatów za pomocą qInstallMsgHandlerfunkcji, a następnie możesz użyć go QTextStreamdo zapisania komunikatu debugowania do pliku. Oto przykładowy przykład:

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

Zaczerpnięte z dokumentu qInstallMsgHandler(dodałem tylko komentarze):

W powyższym przykładzie funkcja myMessageOutputużywa, stderrktórą możesz chcieć zastąpić innym strumieniem plików lub całkowicie ponownie napisać funkcję!

Raz piszesz i zainstalować tę funkcję, wszystkie qDebug(jak również qWarning, qCriticaletc) wiadomości zostaną przekierowane do pliku piszesz w module obsługi.


3
Hej, wielkie dzięki. Nie tylko pozwoli mi to przekierować wyniki debugowania do pliku, ale także pozwoli mi wydrukować bardziej przydatne informacje, takie jak znacznik czasu :)
— Septagram,

2
@Septagram: Dokładnie. Możesz dodać przydatne wiadomości w samym hanlderze; a może nawet wyjść różne komunikaty do różnych plików, w oparciu o to, czego używasz qDebug, qWarning, qCriticali tak dalej!
— Nawaz

1
Nawiasem mówiąc, wywołanie zwrotne, które wykonuje rzeczywisty wynik - void myMessageOutput (typ QtMsgType, const char * msg) - w jakim kodowaniu otrzymuje wiadomość?
— Septagram

8
Linki do dokumentacji i API nieco się zmieniły. qInstallMsgHandlerzostał przestarzały i zastąpiony przez qInstallMessageHandler(ten sam pomysł) w Qt5. Wersja 5.0 qInstallMsgHandlerjest dostępna pod adresem qt-project.org/doc/qt-5.0/qtcore/ ... i też qInstallMessageHandlertam jest. W wersji 5.1 qInstallMsgHandlerzostał całkowicie usunięty.
— Jason C,

1
@Aditya: W Qt4 wywołanie zwrotne przyjmuje tylko dwa argumenty. Możesz więc użyć tego:void myMessageOutput(QtMsgType type, const char *msg) { ... }
— Nawaz

19

Od tutaj wszystko zasługa ducha .

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

sprawa QtFatalMsg: ... abort (); // zakończy się przed zapisaniem dziennika
— raidsan

Zacznij od QT 5, qInstallMessageHandlerzamiast qInstallMsgHandlerzmieniać obsługę komunikatów.
— SuB

Ten program obsługi wiadomości nie jest bezpieczny wątkowo. Utracisz komunikaty dziennika, jeśli zostaną wysłane przez dwa wątki w tym samym czasie (metoda outFile.open () zwróci wartość false dla jednego z wątków). Możesz zablokować QMutex przed próbą otwarcia pliku, a następnie odblokować mutex po zamknięciu pliku. Jest to najprostsze podejście, ale wprowadzi rywalizację wątków. W przeciwnym razie będziesz musiał przyjrzeć się kolejkowaniu wiadomości z niskim narzutem, bezpiecznym wątkowo ... i możesz lepiej korzystać z frameworka.
— Anthony Hayward

9

Oto działający przykład przechwytywania domyślnego programu obsługi komunikatów.

Dziękuję @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

Oto wieloplatformowe rozwiązanie do logowania się do konsoli, jeśli aplikacja została uruchomiona z Qt Creator, oraz do debug.logpliku, gdy jest kompilowana i uruchamiana jako samodzielna aplikacja.

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

Formatowanie dziennika jest obsługiwane przez QString("%1 %2: %3 (%4)").arg...(dla pliku) i fprintf(stderr, "%s %s: %s (%s:%u, %s)\n"...(dla konsoli).

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


Widzę, że wywołujesz „outFile.close ()” w każdym zdarzeniu dziennika. Czy mogę to pominąć?
— rozbieżność

Nie polecam tego w tej konfiguracji, ponieważ za każdym razem otwierasz plik dziennika i dlatego powinien być zamknięty. Ale możesz zmienić algorytm w taki sposób, że ten plik dziennika jest otwierany tylko raz podczas inicjalizacji aplikacji. W ten sposób wystarczy, że zamkniesz go raz, gdy aplikacja zostanie zamknięta.
— Neurotransmiter

1
Dzięki! To bardzo pomocne.
— Aaron,

Ten program obsługi wiadomości nie jest bezpieczny wątkowo. Utracisz komunikaty dziennika, jeśli zostaną wysłane przez dwa wątki w tym samym czasie (metoda outFile.open () zwróci wartość false dla jednego z wątków). Możesz zablokować QMutex przed próbą otwarcia pliku, a następnie odblokować mutex po zamknięciu pliku. Jest to najprostsze podejście, ale wprowadzi rywalizację wątków. W przeciwnym razie będziesz musiał przyjrzeć się kolejkowaniu wiadomości z niskim narzutem, bezpiecznym wątkowo ... i możesz lepiej używać frameworka!
— Anthony Hayward

Zgadzam się z tobą - to dalekie od doskonałości. Ale przez większość czasu spełnia swoje zadanie. W każdym razie wszelkie modyfikacje są mile widziane!
— Neurotransmitter,

6

Cóż, powiedziałbym, że moment, w którym musisz przekierować wyjście debugowania na coś innego niż stderr, jest momentem, w którym możesz pomyśleć o jakimś narzędziu do logowania. Jeśli czujesz, że go potrzebujesz, polecam użycie QxtLogger( "Klasa QxtLogger jest łatwym w użyciu, łatwym do rozszerzenia narzędziem do logowania." ) Z Qxtbiblioteki.


0

Oto prosty, bezpieczny wątkowo idiomatyczny przykład Qt, w którym można logować stderri zapisywać :

void messageHandler (typ QtMsgType, const QMessageLogContext & context, const QString & message)
{
    statyczny mutex QMutex;
    Blokada QMutexLocker (& mutex);

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

    std :: cerr << qPrintable (qFormatLogMessage (typ, kontekst, komunikat)) << std :: endl;

    if (logFileIsOpen) {
        logFile.write (qFormatLogMessage (typ, kontekst, wiadomość) .toUtf8 () + '\ n');
        logFile.flush ();
    }
}

Zainstaluj go, qInstallMessageHandler(messageHandler)jak opisano w innych odpowiedziach.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.