Come reindirizzare l'output qDebug, qWarning, qCritical ecc?


85

Sto usando molte qDebug() <<istruzioni per l'output di debug. Esiste un modo multipiattaforma per reindirizzare l'output di debug a un file, senza ricorrere a script di shell? Immagino che open () e dup2 () faranno il lavoro in Linux, ma funzionerà compilato con MinGW in Windows?

E forse c'è un modo Qt per farlo?

Risposte:


121

Devi installare un gestore di messaggi usando la qInstallMsgHandlerfunzione, quindi puoi usare QTextStreamper scrivere il messaggio di debug in un file. Ecco un esempio di esempio:

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

Tratto dal doc di qInstallMsgHandler(ho solo aggiunto i commenti):

Nell'esempio sopra, la funzione myMessageOutputusa stderrche potresti voler sostituire con qualche altro flusso di file, o riscrivere completamente la funzione!

Dopo aver scritto e installato questa funzione, tutti i tuoi messaggi qDebug(così come qWarning, qCriticalecc.) Verranno reindirizzati al file su cui stai scrivendo nel gestore.


3
Ehi, grazie mille. Non solo mi consente di reindirizzare l'output di debug a un file, ma mi consente anche di stampare informazioni più utili, come un timestamp :)
Septagram

2
@Septagram: esatto. Puoi aggiungere alcuni messaggi utili nello stesso hanlder; e si può anche uscita messaggi diversi a diversi file, sulla base di quello che si utilizza qDebug, qWarning, qCriticale così via!
Nawaz

1
A proposito, il callback che fa l'output effettivo - void myMessageOutput (tipo QtMsgType, const char * msg) - in quale codifica riceve un messaggio?
Septagram

8
I collegamenti alla documentazione e l'API sono leggermente cambiati. qInstallMsgHandlerè stato deprecato e sostituito da qInstallMessageHandler(stessa idea) in Qt5. Per 5.0 qInstallMsgHandlerè su qt-project.org/doc/qt-5.0/qtcore/… ed qInstallMessageHandlerè anche lì. Per 5.1, è qInstallMsgHandlerstato rimosso completamente.
Jason C

1
@ Aditya: In Qt4, il callback richiede solo due argomenti. Quindi puoi usare questo:void myMessageOutput(QtMsgType type, const char *msg) { ... }
Nawaz

19

Da qui tutto il merito va allo spirito .

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

case QtFatalMsg: ... abort (); // si chiuderà prima di scrivere il log
raidsan

Inizia da QT 5, qInstallMessageHandlerdovrebbe essere usato invece di qInstallMsgHandlercambiare gestore di messaggi.
SuB

Questo gestore di messaggi non è thread-safe. Perderai i messaggi di log se vengono inviati da due thread contemporaneamente (outFile.open () restituirà false per uno dei thread). È possibile bloccare un QMutex prima di provare ad aprire il file, quindi sbloccare il mutex dopo aver chiuso il file. Questo è l'approccio più semplice ma introdurrà la contesa tra i thread. In caso contrario, è necessario esaminare l'accodamento di messaggi thread-safe a basso overhead ... e potrebbe essere meglio utilizzare un framework.
Anthony Hayward,

9

Ecco un esempio funzionante di hook del gestore di messaggi predefinito.

Grazie @ 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

Ecco una soluzione multipiattaforma per accedere alla console, se l'app è stata eseguita da Qt Creator, e al debug.logfile, quando viene compilato ed eseguito come app standalone.

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

La formattazione del registro è gestita da QString("%1 %2: %3 (%4)").arg...(per il file) e fprintf(stderr, "%s %s: %s (%s:%u, %s)\n"...(per console).

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


Vedo che chiami "outFile.close ()" in ogni evento di log. Posso ometterlo?
diverger

Non lo consiglio in questa configurazione, poiché stai aprendo il file di registro ogni volta e quindi dovrebbe essere chiuso. Ma puoi cambiare l'algoritmo in un modo, quel file di registro viene aperto solo una volta all'inizializzazione dell'app. In questo modo, dovrai chiuderlo solo una volta, quando l'app viene chiusa.
Neurotrasmettitore

1
Grazie! È molto utile.
Aaron

Questo gestore di messaggi non è thread-safe. Perderai i messaggi di log se vengono inviati da due thread contemporaneamente (outFile.open () restituirà false per uno dei thread). È possibile bloccare un QMutex prima di provare ad aprire il file, quindi sbloccare il mutex dopo aver chiuso il file. Questo è l'approccio più semplice ma introdurrà la contesa tra i thread. In caso contrario, dovrai esaminare l'accodamento di messaggi thread-safe a basso overhead ... e potresti essere meglio usare un framework!
Anthony Hayward,

Sono d'accordo con te - è tutt'altro che perfetto. Ma fa il suo lavoro la maggior parte del tempo. Ad ogni modo, eventuali modifiche sono benvenute!
Neurotrasmettitore

6

Bene, direi che il momento in cui hai bisogno di reindirizzare il tuo output di debug a qualcosa di diverso da stderr è quando potresti pensare a qualche strumento di registrazione. Se ritieni di averne bisogno, ti consiglio di usare QxtLogger( "La classe QxtLogger è uno strumento di registrazione facile da usare, facile da estendere." ) Dalla Qxtlibreria.


0

Ecco un semplice esempio di Qt idiomatico thread-safe per registrare sia in stderrche file:

void messageHandler (tipo QtMsgType, const QMessageLogContext e contesto, const QString e messaggio)
{
    mutex QMutex statico;
    QMutexLocker lock (& ​​mutex);

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

    std :: cerr << qPrintable (qFormatLogMessage (tipo, contesto, messaggio)) << std :: endl;

    if (logFileIsOpen) {
        logFile.write (qFormatLogMessage (tipo, contesto, messaggio) .toUtf8 () + '\ n');
        logFile.flush ();
    }
}

Installalo con qInstallMessageHandler(messageHandler)come descritto in altre risposte.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.