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?

Septagram
źródło

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.

Nawaz
źródło
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();
}
Sandeep Datta
źródło
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;
}
Andrzej
źródło
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 .

Neurotransmitter
źródło
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.

Piotr Dobrogost
źródło
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.

mrts
źródło