¿Cómo redirigir la salida de qDebug, qWarning, qCritical, etc.?


84

Estoy usando muchas qDebug() <<declaraciones para la salida de depuración. ¿Existe alguna forma multiplataforma en la que pueda redirigir esa salida de depuración a un archivo, sin recurrir a scripts de shell? Supongo que open () y dup2 () harán el trabajo en Linux, pero ¿funcionará compilado con MinGW en Windows?

¿Y tal vez hay una forma Qt de hacerlo?

Respuestas:


120

Debe instalar un controlador de mensajes usando la qInstallMsgHandlerfunción, y luego, puede usarlo QTextStreampara escribir el mensaje de depuración en un archivo. Aquí hay un ejemplo de muestra:

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

Tomado del documento de qInstallMsgHandler(solo agregué los comentarios):

En el ejemplo anterior, la función myMessageOutpututiliza stderrlo que podría querer reemplazar con algún otro flujo de archivos, ¡o reescribir completamente la función!

Una vez que escriba e instale esta función, todos sus mensajes qDebug(así como qWarning, qCriticaletc.) serán redirigidos al archivo en el que está escribiendo en el controlador.


3
Oye, muchas gracias. No solo me permitirá redirigir la salida de depuración a un archivo, sino que también me permitirá imprimir información más útil, como una marca de tiempo :)
Septagram

2
@Septagram: Exactamente. Puede agregar algunos mensajes útiles en el propio administrador; e incluso se puede mensajes de salida diferentes para diferentes archivos, en base a lo que se utiliza qDebug, qWarning, qCriticaly así sucesivamente!
Nawaz

1
Por cierto, la devolución de llamada que hace la salida real - void myMessageOutput (tipo QtMsgType, const char * msg) - ¿en qué codificación recibe un mensaje?
Septagrama

8
Los enlaces de documentación y la API han cambiado un poco. qInstallMsgHandlerfue desaprobado y reemplazado por qInstallMessageHandler(la misma idea) en Qt5. Para 5.0 qInstallMsgHandlerestá en qt-project.org/doc/qt-5.0/qtcore/… y también qInstallMessageHandlerestá allí. Para 5.1, qInstallMsgHandlerse eliminó por completo.
Jason C

1
@Aditya: En Qt4, la devolución de llamada solo toma dos argumentos. Entonces puedes usar esto:void myMessageOutput(QtMsgType type, const char *msg) { ... }
Nawaz

19

Desde aquí todo el crédito va al espíritu .

#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 (); // se cerrará antes de escribir el registro
raidsan

Comenzar desde QT 5, qInstallMessageHandlerdebe usarse en lugar de qInstallMsgHandlercambiar el controlador de mensajes.
SuB

Este controlador de mensajes no es seguro para subprocesos. Perderá los mensajes de registro si son enviados por dos subprocesos al mismo tiempo (outFile.open () devolverá falso para uno de los subprocesos). Puede bloquear un QMutex antes de intentar abrir el archivo, luego desbloquear el mutex después de cerrar el archivo. Este es el enfoque más simple pero introducirá la contención de hilos. De lo contrario, tendrá que buscar en la cola de mensajes seguros para subprocesos de baja sobrecarga ... y es posible que sea mejor usar un marco.
Anthony Hayward

9

A continuación, se muestra un ejemplo práctico de cómo conectar el controlador de mensajes predeterminado.

¡Gracias @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

Aquí hay una solución multiplataforma para iniciar sesión en la consola, si la aplicación se ejecutó desde Qt Creator, y en el debug.logarchivo, cuando se compila y se ejecuta como una aplicación independiente.

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

El formato del registro lo manejan QString("%1 %2: %3 (%4)").arg...(para el archivo) y fprintf(stderr, "%s %s: %s (%s:%u, %s)\n"...(para la consola).

Inspiración: https://gist.github.com/polovik/10714049 .


Veo que llamas "outFile.close ()" en cada evento de registro. ¿Puedo omitirlo?
diverger

No lo recomiendo en esta configuración, ya que está abriendo el archivo de registro cada vez y, por lo tanto, debería cerrarse. Pero puede cambiar el algoritmo de alguna manera, ese archivo de registro se abre solo una vez en el inicio de la aplicación. De esta manera, solo tendrá que cerrarlo una vez, cuando la aplicación esté saliendo.
Neurotransmisor

1
¡Gracias! Es muy útil.
Aaron

Este controlador de mensajes no es seguro para subprocesos. Perderá los mensajes de registro si son enviados por dos subprocesos al mismo tiempo (outFile.open () devolverá falso para uno de los subprocesos). Puede bloquear un QMutex antes de intentar abrir el archivo, luego desbloquear el mutex después de cerrar el archivo. Este es el enfoque más simple pero introducirá la contención de hilos. De lo contrario, tendrá que buscar en la cola de mensajes seguros para subprocesos de baja sobrecarga ... ¡y es posible que sea mejor usar un marco!
Anthony Hayward

Estoy de acuerdo contigo, está lejos de ser perfecto. Pero hace su trabajo la mayor parte del tiempo. De todos modos, ¡cualquier modificación es bienvenida!
Neurotransmisor

6

Bueno, yo diría que el momento en que necesita redirigir su salida de depuración a algo diferente a stderr es cuando podría pensar en alguna herramienta de registro. Si cree que necesita uno, le recomendaría usar QxtLogger( "La clase QxtLogger es una herramienta de registro fácil de usar y fácil de ampliar" ) de la Qxtbiblioteca.


0

Aquí hay un ejemplo de Qt idiomático simple y seguro para subprocesos para iniciar sesión stderry archivar:

void messageHandler (tipo QtMsgType, const QMessageLogContext & context, const QString & message)
{
    mutex estático de QMutex;
    Bloqueo de QMutexLocker (& mutex);

    Archivo de registro de QFile estático (LOGFILE_LOCATION);
    static bool logFileIsOpen = logFile.open (QIODevice :: Append | QIODevice :: Text);

    std :: cerr << qPrintable (qFormatLogMessage (tipo, contexto, mensaje)) << std :: endl;

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

Instálelo qInstallMessageHandler(messageHandler)como se describe en otras respuestas.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.